////////////////////////////////////////////////////////////////////////////////
|
|
// 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 in fields) {
|
|
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 in fields) {
|
|
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;
|