|
|
@ -0,0 +1,436 @@ |
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// This modules handles the options for Graph3d.
|
|
|
|
//
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
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', |
|
|
|
'showGrid', |
|
|
|
'showPerspective', |
|
|
|
'showShadow', |
|
|
|
'keepAspectRatio', |
|
|
|
'verticalRatio', |
|
|
|
'dotSizeRatio', |
|
|
|
'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
|
|
|
|
*/ |
|
|
|
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
|
|
|
|
*/ |
|
|
|
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]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 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. |
|
|
|
*/ |
|
|
|
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.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; |
|
|
|
} |
|
|
|
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 |
|
|
|
*/ |
|
|
|
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; |