vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

487 lines
11 KiB

////////////////////////////////////////////////////////////////////////////////
// This modules handles the options for Graph3d.
//
////////////////////////////////////////////////////////////////////////////////
var util = require('../util');
var Camera = require('./Camera');
var Point3d = require('./Point3d');
// enumerate the available styles
var STYLE = {
BAR : 0,
BARCOLOR: 1,
BARSIZE : 2,
DOT : 3,
DOTLINE : 4,
DOTCOLOR: 5,
DOTSIZE : 6,
GRID : 7,
LINE : 8,
SURFACE : 9
};
// The string representations of the styles
var STYLENAME = {
'dot' : STYLE.DOT,
'dot-line' : STYLE.DOTLINE,
'dot-color': STYLE.DOTCOLOR,
'dot-size' : STYLE.DOTSIZE,
'line' : STYLE.LINE,
'grid' : STYLE.GRID,
'surface' : STYLE.SURFACE,
'bar' : STYLE.BAR,
'bar-color': STYLE.BARCOLOR,
'bar-size' : STYLE.BARSIZE
};
/**
* Field names in the options hash which are of relevance to the user.
*
* Specifically, these are the fields which require no special handling,
* and can be directly copied over.
*/
var OPTIONKEYS = [
'width',
'height',
'filterLabel',
'legendLabel',
'xLabel',
'yLabel',
'zLabel',
'xValueLabel',
'yValueLabel',
'zValueLabel',
'showXAxis',
'showYAxis',
'showZAxis',
'showGrid',
'showPerspective',
'showShadow',
'keepAspectRatio',
'verticalRatio',
'dotSizeRatio',
'dotSizeMinFraction',
'dotSizeMaxFraction',
'showAnimationControls',
'animationInterval',
'animationPreload',
'animationAutoStart',
'axisColor',
'gridColor',
'xCenter',
'yCenter',
];
/**
* Field names in the options hash which are of relevance to the user.
*
* Same as OPTIONKEYS, but internally these fields are stored with
* prefix 'default' in the name.
*/
var PREFIXEDOPTIONKEYS = [
'xBarWidth',
'yBarWidth',
'valueMin',
'valueMax',
'xMin',
'xMax',
'xStep',
'yMin',
'yMax',
'yStep',
'zMin',
'zMax',
'zStep'
];
// Placeholder for DEFAULTS reference
var DEFAULTS = undefined;
/**
* Check if given hash is empty.
*
* Source: http://stackoverflow.com/a/679937
*
* @param {object} obj
* @returns {boolean}
*/
function isEmpty(obj) {
for(var prop in obj) {
if (obj.hasOwnProperty(prop))
return false;
}
return true;
}
/**
* Make first letter of parameter upper case.
*
* Source: http://stackoverflow.com/a/1026087
*
* @param {string} str
* @returns {string}
*/
function capitalize(str) {
if (str === undefined || str === "" || typeof str != "string") {
return str;
}
return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
* Add a prefix to a field name, taking style guide into account
*
* @param {string} prefix
* @param {string} fieldName
* @returns {string}
*/
function prefixFieldName(prefix, fieldName) {
if (prefix === undefined || prefix === "") {
return fieldName;
}
return prefix + capitalize(fieldName);
}
/**
* Forcibly copy fields from src to dst in a controlled manner.
*
* A given field in dst will always be overwitten. If this field
* is undefined or not present in src, the field in dst will
* be explicitly set to undefined.
*
* The intention here is to be able to reset all option fields.
*
* Only the fields mentioned in array 'fields' will be handled.
*
* @param {object} src
* @param {object} dst
* @param {array<string>} fields array with names of fields to copy
* @param {string} [prefix] prefix to use for the target fields.
*/
function forceCopy(src, dst, fields, prefix) {
var srcKey;
var dstKey;
for (var i = 0; i < fields.length; ++i) {
srcKey = fields[i];
dstKey = prefixFieldName(prefix, srcKey);
dst[dstKey] = src[srcKey];
}
}
/**
* Copy fields from src to dst in a safe and controlled manner.
*
* Only the fields mentioned in array 'fields' will be copied over,
* and only if these are actually defined.
*
* @param {object} src
* @param {object} dst
* @param {array<string>} fields array with names of fields to copy
* @param {string} [prefix] prefix to use for the target fields.
*/
function safeCopy(src, dst, fields, prefix) {
var srcKey;
var dstKey;
for (var i = 0; i < fields.length; ++i) {
srcKey = fields[i];
if (src[srcKey] === undefined) continue;
dstKey = prefixFieldName(prefix, srcKey);
dst[dstKey] = src[srcKey];
}
}
/**
* Initialize dst with the values in src.
*
* src is the hash with the default values.
* A reference DEFAULTS to this hash is stored locally for
* further handling.
*
* For now, dst is assumed to be a Graph3d instance.
* @param {object} src
* @param {object} dst
*/
function setDefaults(src, dst) {
if (src === undefined || isEmpty(src)) {
throw new Error('No DEFAULTS passed');
}
if (dst === undefined) {
throw new Error('No dst passed');
}
// Remember defaults for future reference
DEFAULTS = src;
// Handle the defaults which can be simply copied over
forceCopy(src, dst, OPTIONKEYS);
forceCopy(src, dst, PREFIXEDOPTIONKEYS, 'default');
// Handle the more complex ('special') fields
setSpecialSettings(src, dst);
// Following are internal fields, not part of the user settings
dst.margin = 10; // px
dst.showGrayBottom = false; // TODO: this does not work correctly
dst.showTooltip = false;
dst.onclick_callback = null;
dst.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window?
}
/**
*
* @param {object} options
* @param {object} dst
*/
function setOptions(options, dst) {
if (options === undefined) {
return;
}
if (dst === undefined) {
throw new Error('No dst passed');
}
if (DEFAULTS === undefined || isEmpty(DEFAULTS)) {
throw new Error('DEFAULTS not set for module Settings');
}
// Handle the parameters which can be simply copied over
safeCopy(options, dst, OPTIONKEYS);
safeCopy(options, dst, PREFIXEDOPTIONKEYS, 'default');
// Handle the more complex ('special') fields
setSpecialSettings(options, dst);
}
/**
* Special handling for certain parameters
*
* 'Special' here means: setting requires more than a simple copy
*
* @param {object} src
* @param {object} dst
*/
function setSpecialSettings(src, dst) {
if (src.backgroundColor !== undefined) {
setBackgroundColor(src.backgroundColor, dst);
}
setDataColor(src.dataColor, dst);
setStyle(src.style, dst);
setShowLegend(src.showLegend, dst);
setCameraPosition(src.cameraPosition, dst);
// As special fields go, this is an easy one; just a translation of the name.
// Can't use this.tooltip directly, because that field exists internally
if (src.tooltip !== undefined) {
dst.showTooltip = src.tooltip;
}
if (src.onclick != undefined) {
dst.onclick_callback = src.onclick;
}
if (src.tooltipStyle !== undefined) {
util.selectiveDeepExtend(['tooltipStyle'], dst, src);
}
}
/**
* Set the value of setting 'showLegend'
*
* This depends on the value of the style fields, so it must be called
* after the style field has been initialized.
*
* @param {boolean} showLegend
* @param {object} dst
*/
function setShowLegend(showLegend, dst) {
if (showLegend === undefined) {
// If the default was auto, make a choice for this field
var isAutoByDefault = (DEFAULTS.showLegend === undefined);
if (isAutoByDefault) {
// these styles default to having legends
var isLegendGraphStyle = dst.style === STYLE.DOTCOLOR
|| dst.style === STYLE.DOTSIZE;
dst.showLegend = isLegendGraphStyle;
} else {
// Leave current value as is
}
} else {
dst.showLegend = showLegend;
}
}
/**
* Retrieve the style index from given styleName
* @param {string} styleName Style name such as 'dot', 'grid', 'dot-line'
* @return {number} styleNumber Enumeration value representing the style, or -1
* when not found
*/
function getStyleNumberByName(styleName) {
var number = STYLENAME[styleName];
if (number === undefined) {
return -1;
}
return number;
}
/**
* Check if given number is a valid style number.
*
* @param {string | number} style
* @return {boolean} true if valid, false otherwise
*/
function checkStyleNumber(style) {
var valid = false;
for (var n in STYLE) {
if (STYLE[n] === style) {
valid = true;
break;
}
}
return valid;
}
/**
*
* @param {string | number} style
* @param {Object} dst
*/
function setStyle(style, dst) {
if (style === undefined) {
return; // Nothing to do
}
var styleNumber;
if (typeof style === 'string') {
styleNumber = getStyleNumberByName(style);
if (styleNumber === -1 ) {
throw new Error('Style \'' + style + '\' is invalid');
}
} else {
// Do a pedantic check on style number value
if (!checkStyleNumber(style)) {
throw new Error('Style \'' + style + '\' is invalid');
}
styleNumber = style;
}
dst.style = styleNumber;
}
/**
* Set the background styling for the graph
* @param {string | {fill: string, stroke: string, strokeWidth: string}} backgroundColor
* @param {Object} dst
*/
function setBackgroundColor(backgroundColor, dst) {
var fill = 'white';
var stroke = 'gray';
var strokeWidth = 1;
if (typeof(backgroundColor) === 'string') {
fill = backgroundColor;
stroke = 'none';
strokeWidth = 0;
}
else if (typeof(backgroundColor) === 'object') {
if (backgroundColor.fill !== undefined) fill = backgroundColor.fill;
if (backgroundColor.stroke !== undefined) stroke = backgroundColor.stroke;
if (backgroundColor.strokeWidth !== undefined) strokeWidth = backgroundColor.strokeWidth;
}
else {
throw new Error('Unsupported type of backgroundColor');
}
dst.frame.style.backgroundColor = fill;
dst.frame.style.borderColor = stroke;
dst.frame.style.borderWidth = strokeWidth + 'px';
dst.frame.style.borderStyle = 'solid';
}
/**
*
* @param {string | Object} dataColor
* @param {Object} dst
*/
function setDataColor(dataColor, dst) {
if (dataColor === undefined) {
return; // Nothing to do
}
if (dst.dataColor === undefined) {
dst.dataColor = {};
}
if (typeof dataColor === 'string') {
dst.dataColor.fill = dataColor;
dst.dataColor.stroke = dataColor;
}
else {
if (dataColor.fill) {
dst.dataColor.fill = dataColor.fill;
}
if (dataColor.stroke) {
dst.dataColor.stroke = dataColor.stroke;
}
if (dataColor.strokeWidth !== undefined) {
dst.dataColor.strokeWidth = dataColor.strokeWidth;
}
}
}
/**
*
* @param {Object} cameraPosition
* @param {Object} dst
*/
function setCameraPosition(cameraPosition, dst) {
var camPos = cameraPosition;
if (camPos === undefined) {
return;
}
if (dst.camera === undefined) {
dst.camera = new Camera();
}
dst.camera.setArmRotation(camPos.horizontal, camPos.vertical);
dst.camera.setArmLength(camPos.distance);
}
module.exports.STYLE = STYLE;
module.exports.setDefaults = setDefaults;
module.exports.setOptions = setOptions;
module.exports.setCameraPosition = setCameraPosition;