diff --git a/lib/graph3d/Graph3d.js b/lib/graph3d/Graph3d.js index ccbba1d1..1d8515d6 100644 --- a/lib/graph3d/Graph3d.js +++ b/lib/graph3d/Graph3d.js @@ -7,21 +7,11 @@ var Camera = require('./Camera'); var Filter = require('./Filter'); var Slider = require('./Slider'); var StepNumber = require('./StepNumber'); +var Settings = require('./Settings'); /// enumerate the available styles -Graph3d.STYLE = { - BAR : 0, - BARCOLOR: 1, - BARSIZE : 2, - DOT : 3, - DOTLINE : 4, - DOTCOLOR: 5, - DOTSIZE : 6, - GRID : 7, - LINE : 8, - SURFACE : 9 -}; +Graph3d.STYLE = Settings.STYLE; /** @@ -135,7 +125,7 @@ function Graph3d(container, data, options) { // create a frame and canvas this.create(); - this._setDefaults(); + Settings.setDefaults(this); // the column indexes this.colX = undefined; @@ -302,32 +292,6 @@ Graph3d.prototype._calcTranslations = function(points, sort) { }; - - - -/** - * 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 - */ -Graph3d.prototype._getStyleNumber = function(styleName) { - switch (styleName) { - case 'dot': return Graph3d.STYLE.DOT; - case 'dot-line': return Graph3d.STYLE.DOTLINE; - case 'dot-color': return Graph3d.STYLE.DOTCOLOR; - case 'dot-size': return Graph3d.STYLE.DOTSIZE; - case 'line': return Graph3d.STYLE.LINE; - case 'grid': return Graph3d.STYLE.GRID; - case 'surface': return Graph3d.STYLE.SURFACE; - case 'bar': return Graph3d.STYLE.BAR; - case 'bar-color': return Graph3d.STYLE.BARCOLOR; - case 'bar-size': return Graph3d.STYLE.BARSIZE; - } - - return -1; -}; - /** * Determine the indexes of the data columns, based on the given style and data * @param {DataSet} data @@ -855,7 +819,7 @@ Graph3d.prototype.setOptions = function (options) { this.animationStop(); - this._setOptions(options); + Settings.setOptions(options, this); this.setSize(this.width, this.height); @@ -2216,335 +2180,9 @@ function getMouseY (event) { // ----------------------------------------------------------------------------- -// Methods for handling settings +// Public methods for specific settings // ----------------------------------------------------------------------------- - -/** - * 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', - 'showGrid', - 'showPerspective', - 'showShadow', - 'keepAspectRatio', - 'verticalRatio', - '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' -]; - - -/** - * Make first letter of parameter upper case. - * - * Source: http://stackoverflow.com/a/1026087 - */ -function capitalize(str) { - if (str === undefined || str === "") { - return str; - } - - return str.charAt(0).toUpperCase() + str.slice(1); -} - - -/** - * Add a prefix to a field name, taking style guide into account - */ -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 fields array with names of fields to copy - * @param prefix optional; 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 fields array with names of fields to copy - * @param prefix optional; 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]; - } -} - - - -Graph3d.prototype._setDefaults = function() { - - // Handle the defaults which can be simply copied over - forceCopy(DEFAULTS, this, OPTIONKEYS); - forceCopy(DEFAULTS, this, PREFIXEDOPTIONKEYS, 'default'); - - // Handle the more complex ('special') fields - this._setSpecialSettings(DEFAULTS, this); - - // Following are internal fields, not part of the user settings - this.margin = 10; // px - this.showGrayBottom = false; // TODO: this does not work correctly - this.showTooltip = false; - this.dotSizeRatio = 0.02; // size of the dots as a fraction of the graph width - this.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window? -}; - - -Graph3d.prototype._setOptions = function(options) { - if (options === undefined) { - return; - } - - // Handle the parameters which can be simply copied over - safeCopy(options, this, OPTIONKEYS); - safeCopy(options, this, PREFIXEDOPTIONKEYS, 'default'); - - // Handle the more complex ('special') fields - this._setSpecialSettings(options, this); -}; - - -/** - * Special handling for certain parameters - * - * 'Special' here means: setting requires more than a simple copy - */ -Graph3d.prototype._setSpecialSettings = function(src, dst) { - if (src.backgroundColor !== undefined) { - this._setBackgroundColor(src.backgroundColor, dst); - } - - this._setDataColor(src.dataColor, dst); - this._setStyle(src.style, dst); - this._setShowLegend(src.showLegend, dst); - this._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; - } -}; - - -/** - * 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. - */ -Graph3d.prototype._setShowLegend = function(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 = this.style === Graph3d.STYLE.DOTCOLOR - || this.style === Graph3d.STYLE.DOTSIZE; - - this.showLegend = isLegendGraphStyle; - } else { - // Leave current value as is - } - } else { - dst.showLegend = showLegend; - } -}; - - -Graph3d.prototype._setStyle = function(style, dst) { - if (style === undefined) { - return; // Nothing to do - } - - var styleNumber; - - if (typeof style === 'string') { - styleNumber = this._getStyleNumber(style); - - if (styleNumber === -1 ) { - throw new Error('Style \'' + style + '\' is invalid'); - } - } else { - // Do a pedantic check on style number value - var valid = false; - for (var n in Graph3d.STYLE) { - if (Graph3d.STYLE[n] === style) { - valid = true; - break; - } - } - - if (!valid) { - 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 - */ -Graph3d.prototype._setBackgroundColor = function(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'; -}; - - -Graph3d.prototype._setDataColor = function(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; - } - } -}; - - -Graph3d.prototype._setCameraPosition = function(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); -}; - - -// -// Public methods for specific settings -// - /** * Set the rotation and distance of the camera * @param {Object} pos An object with the camera position. The object @@ -2562,13 +2200,13 @@ Graph3d.prototype._setCameraPosition = function(cameraPosition, dst) { * Optional, can be left undefined. */ Graph3d.prototype.setCameraPosition = function(pos) { - this._setCameraPosition(pos, this); + Settings.setCameraPosition(pos, this); this.redraw(); }; // ----------------------------------------------------------------------------- -// End methods for handling settings +// End public methods for specific settings // ----------------------------------------------------------------------------- diff --git a/lib/graph3d/Settings.js b/lib/graph3d/Settings.js new file mode 100644 index 00000000..c8663a18 --- /dev/null +++ b/lib/graph3d/Settings.js @@ -0,0 +1,388 @@ +//////////////////////////////////////////////////////////////////////////////// +// This modules handles the options for Graph3d. +// +//////////////////////////////////////////////////////////////////////////////// + +// 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', + 'showGrid', + 'showPerspective', + 'showShadow', + 'keepAspectRatio', + 'verticalRatio', + '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' +]; + + +/** + * Make first letter of parameter upper case. + * + * Source: http://stackoverflow.com/a/1026087 + */ +function capitalize(str) { + if (str === undefined || str === "") { + return str; + } + + return str.charAt(0).toUpperCase() + str.slice(1); +} + + +/** + * Add a prefix to a field name, taking style guide into account + */ +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 fields array with names of fields to copy + * @param prefix optional; 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 fields array with names of fields to copy + * @param prefix optional; 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]; + } +} + + +function setDefaults(dst) { + // Handle the defaults which can be simply copied over + forceCopy(DEFAULTS, dst, OPTIONKEYS); + forceCopy(DEFAULTS, dst, PREFIXEDOPTIONKEYS, 'default'); + + // Handle the more complex ('special') fields + setSpecialSettings(DEFAULTS, 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.dotSizeRatio = 0.02; // size of the dots as a fraction of the graph width + dst.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window? +} + + +function setOptions(options, dst) { + if (options === undefined) { + return; + } + + // 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 + */ +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; + } +} + + +/** + * 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. + */ +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. + * + * @return 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; +} + + +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 + */ +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'; +} + + +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; + } + } +} + + +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;