Browse Source

Merge pull request #2182 from wimrijnders/PR13

Graph3d: Added fields with special handling to default settings.
codeClimate
yotamberk 8 years ago
committed by GitHub
parent
commit
5d63ae0588
2 changed files with 272 additions and 130 deletions
  1. +251
    -130
      lib/graph3d/Graph3d.js
  2. +21
    -0
      lib/graph3d/StepNumber.js

+ 251
- 130
lib/graph3d/Graph3d.js View File

@ -12,6 +12,21 @@ var StepNumber = require('./StepNumber');
// Definitions private to module
// -----------------------------------------------------------------------------
/// 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
};
/**
* Field names in the options hash which are of relevance to the user.
*
@ -32,9 +47,9 @@ var OPTIONKEYS = [
'showGrid',
'showPerspective',
'showShadow',
'showAnimationControls',
'keepAspectRatio',
'verticalRatio',
'showAnimationControls',
'animationInterval',
'animationPreload',
'animationAutoStart',
@ -76,19 +91,37 @@ var DEFAULTS = {
showPerspective : true,
showShadow : false,
keepAspectRatio : true,
verticalRatio : 0.5, // 0.1 to 1.0, where 1.0 results in a 'cube'
animationInterval: 1000, // milliseconds
animationPreload : false,
verticalRatio : 0.5, // 0.1 to 1.0, where 1.0 results in a 'cube'
showAnimationControls: undefined, // auto by default
animationInterval : 1000, // milliseconds
animationPreload : false,
animationAutoStart : undefined, // auto by default
axisColor : '#4D4D4D',
gridColor : '#D3D3D3',
xCenter : '55%',
yCenter : '50%'
yCenter : '50%',
// Following not in OPTIONKEYS because they require special handling,
style : Graph3d.STYLE.DOT,
tooltip : false,
showLegend : undefined, // auto by default (based on graph style)
backgroundColor : undefined,
// Following not in defaults (yet) but present in user settings
// These will be initialized as 'undefined'
//'showAnimationControls',
//'animationAutoStart'
dataColor : {
fill : '#7DC1FF',
stroke : '#3267D2',
strokeWidth: 1 // px
},
cameraPosition : {
horizontal: 1.0,
vertical : 0.5,
distance : 1.7
}
};
@ -138,6 +171,7 @@ function safeCopy(src, dst, fields) {
// Class Graph3d
// -----------------------------------------------------------------------------
/**
* @constructor Graph3d
* Graph3d displays data in 3d.
@ -160,6 +194,8 @@ function Graph3d(container, data, options) {
this.dataTable = null; // The original data table
this.dataPoints = null; // The table with point objects
// create a frame and canvas
this.create();
//
// Start Settings
@ -169,24 +205,19 @@ function Graph3d(container, data, options) {
forceCopy(DEFAULTS, this, OPTIONKEYS);
// Following are internal fields, not part of the user settings
this.margin = 10; // px
this.showGrayBottom = false; // TODO: this does not work correctly
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.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?
// Handle the more complex ('special') fields
this._setSpecialSettings(DEFAULTS, this);
// The rest of the fields.
// These require special attention in some way
// TODO: handle these
this.showLegend = undefined; // auto by default (based on graph style)
this.style = Graph3d.STYLE.DOT;
this.camera = new Camera();
this.camera.setArmRotation(1.0, 0.5);
this.camera.setArmLength(1.7);
this.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window?
// the column indexes
this.colX = undefined;
@ -210,21 +241,10 @@ function Graph3d(container, data, options) {
this.yBarWidth = 1;
// TODO: customize axis range
// colors
this.dataColor = {
fill: '#7DC1FF',
stroke: '#3267D2',
strokeWidth: 1 // px
};
//
// End Settings
//
// create a frame and canvas
this.create();
// apply options (also when undefined)
this.setOptions(options);
@ -381,11 +401,100 @@ Graph3d.prototype._calcTranslations = function(points, sort) {
};
// -----------------------------------------------------------------------------
// Methods for handling settings
// -----------------------------------------------------------------------------
/**
* 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) {
Graph3d.prototype._setBackgroundColor = function(backgroundColor, dst) {
var fill = 'white';
var stroke = 'gray';
var strokeWidth = 1;
@ -400,34 +509,92 @@ Graph3d.prototype._setBackgroundColor = function(backgroundColor) {
if (backgroundColor.stroke !== undefined) stroke = backgroundColor.stroke;
if (backgroundColor.strokeWidth !== undefined) strokeWidth = backgroundColor.strokeWidth;
}
else if (backgroundColor === undefined) {
// use use defaults
}
else {
throw new Error('Unsupported type of backgroundColor');
}
this.frame.style.backgroundColor = fill;
this.frame.style.borderColor = stroke;
this.frame.style.borderWidth = strokeWidth + 'px';
this.frame.style.borderStyle = 'solid';
dst.frame.style.backgroundColor = fill;
dst.frame.style.borderColor = stroke;
dst.frame.style.borderWidth = strokeWidth + 'px';
dst.frame.style.borderStyle = 'solid';
};
/// 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.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
* contains three parameters:
* - horizontal {Number}
* The horizontal rotation, between 0 and 2*PI.
* Optional, can be left undefined.
* - vertical {Number}
* The vertical rotation, between 0 and 0.5*PI
* if vertical=0.5*PI, the graph is shown from the
* top. Optional, can be left undefined.
* - distance {Number}
* The (normalized) distance of the camera to the
* center of the graph, a value between 0.71 and 5.0.
* Optional, can be left undefined.
*/
Graph3d.prototype.setCameraPosition = function(pos) {
this._setCameraPosition(pos, this);
this.redraw();
};
// -----------------------------------------------------------------------------
// End methods for handling settings
// -----------------------------------------------------------------------------
/**
* Retrieve the style index from given styleName
* @param {string} styleName Style name such as 'dot', 'grid', 'dot-line'
@ -579,13 +746,11 @@ Graph3d.prototype._dataInitialize = function (rawData, style) {
this.colX = 'x';
this.colY = 'y';
this.colZ = 'z';
this.colValue = 'style';
this.colFilter = 'filter';
// check if a filter column is provided
if (data[0].hasOwnProperty('filter')) {
this.colFilter = 'filter'; // Bugfix: only set this field if it's actually present!
if (this.dataFilter === undefined) {
this.dataFilter = new Filter(rawData, this.colFilter, this);
this.dataFilter.setOnLoadCallback(function() {me.redraw();});
@ -643,17 +808,15 @@ Graph3d.prototype._dataInitialize = function (rawData, style) {
if (this.zMax <= this.zMin) this.zMax = this.zMin + 1;
this.zStep = (this.defaultZStep !== undefined) ? this.defaultZStep : (this.zMax-this.zMin)/5;
if (this.colValue !== undefined) {
// Bugfix: Only handle field 'style' if it's actually present
if (data[0].hasOwnProperty('style')) {
this.colValue = 'style';
var valueRange = this.getColumnRange(data,this.colValue);
this.valueMin = (this.defaultValueMin !== undefined) ? this.defaultValueMin : valueRange.min;
this.valueMax = (this.defaultValueMax !== undefined) ? this.defaultValueMax : valueRange.max;
if (this.valueMax <= this.valueMin) this.valueMax = this.valueMin + 1;
}
// these styles default to having legends
var isLegendGraphStyle = this.style === Graph3d.STYLE.DOTCOLOR || this.style === Graph3d.STYLE.DOTSIZE;
this.showLegend = (this.defaultShowLegend !== undefined) ? this.defaultShowLegend : isLegendGraphStyle;
// set the scale dependent on the ranges.
this._setScale();
};
@ -743,6 +906,30 @@ Graph3d.prototype._getDataPoints = function (data) {
}
}
else { // 'dot', 'dot-line', etc.
// Bugfix: ensure value field is present in data if expected
var hasValueField = this.style === Graph3d.STYLE.BARCOLOR
|| this.style === Graph3d.STYLE.BARSIZE
|| this.style === Graph3d.STYLE.DOTCOLOR
|| this.style === Graph3d.STYLE.DOTSIZE;
if (hasValueField) {
if (this.colValue === undefined) {
throw new Error('Expected data to have '
+ ' field \'style\' '
+ ' for graph style \'' + this.style + '\''
);
}
if (data[0][this.colValue] === undefined) {
throw new Error('Expected data to have '
+ ' field \'' + this.colValue + '\' '
+ ' for graph style \'' + this.style + '\''
);
}
}
// copy all values from the google data table to a list with Point3d objects
for (i = 0; i < data.length; i++) {
point = new Point3d();
@ -901,37 +1088,6 @@ Graph3d.prototype._resizeCenter = function() {
}
};
/**
* Set the rotation and distance of the camera
* @param {Object} pos An object with the camera position. The object
* contains three parameters:
* - horizontal {Number}
* The horizontal rotation, between 0 and 2*PI.
* Optional, can be left undefined.
* - vertical {Number}
* The vertical rotation, between 0 and 0.5*PI
* if vertical=0.5*PI, the graph is shown from the
* top. Optional, can be left undefined.
* - distance {Number}
* The (normalized) distance of the camera to the
* center of the graph, a value between 0.71 and 5.0.
* Optional, can be left undefined.
*/
Graph3d.prototype.setCameraPosition = function(pos) {
if (pos === undefined) {
return;
}
if (pos.horizontal !== undefined && pos.vertical !== undefined) {
this.camera.setArmRotation(pos.horizontal, pos.vertical);
}
if (pos.distance !== undefined) {
this.camera.setArmLength(pos.distance);
}
this.redraw();
};
/**
@ -995,17 +1151,10 @@ Graph3d.prototype.setOptions = function (options) {
// Handle the parameters which can be simply copied over
safeCopy(options, this, OPTIONKEYS);
// Handle the rest of the parameters
if (options.showLegend !== undefined) this.defaultShowLegend = options.showLegend;
if (options.style !== undefined) {
var styleNumber = this._getStyleNumber(options.style);
if (styleNumber !== -1) {
this.style = styleNumber;
}
}
if (options.tooltip !== undefined) this.showTooltip = options.tooltip;
// Handle the more complex ('special') fields
this._setSpecialSettings(options, this);
// Handle the rest of the parameters
if (options.xBarWidth !== undefined) this.defaultXBarWidth = options.xBarWidth;
if (options.yBarWidth !== undefined) this.defaultYBarWidth = options.yBarWidth;
if (options.xMin !== undefined) this.defaultXMin = options.xMin;
@ -1019,34 +1168,6 @@ Graph3d.prototype.setOptions = function (options) {
if (options.zMax !== undefined) this.defaultZMax = options.zMax;
if (options.valueMin !== undefined) this.defaultValueMin = options.valueMin;
if (options.valueMax !== undefined) this.defaultValueMax = options.valueMax;
if (options.backgroundColor !== undefined) this._setBackgroundColor(options.backgroundColor);
if (options.cameraPosition !== undefined) cameraPosition = options.cameraPosition;
if (cameraPosition !== undefined) {
this.camera.setArmRotation(cameraPosition.horizontal, cameraPosition.vertical);
this.camera.setArmLength(cameraPosition.distance);
}
// colors
if (options.dataColor) {
if (typeof options.dataColor === 'string') {
this.dataColor.fill = options.dataColor;
this.dataColor.stroke = options.dataColor;
}
else {
if (options.dataColor.fill) {
this.dataColor.fill = options.dataColor.fill;
}
if (options.dataColor.stroke) {
this.dataColor.stroke = options.dataColor.stroke;
}
if (options.dataColor.strokeWidth !== undefined) {
this.dataColor.strokeWidth = options.dataColor.strokeWidth;
}
}
}
}
this.setSize(this.width, this.height);

+ 21
- 0
lib/graph3d/StepNumber.js View File

@ -35,6 +35,17 @@ function StepNumber(start, end, step, prettyStep) {
this.setRange(start, end, step, prettyStep);
};
/**
* Check for input values, to prevent disasters from happening
*
* Source: http://stackoverflow.com/a/1830844
*/
StepNumber.prototype.isNumeric = function(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
};
/**
* Set a new range: start, end and step.
*
@ -45,6 +56,16 @@ function StepNumber(start, end, step, prettyStep) {
* To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
*/
StepNumber.prototype.setRange = function(start, end, step, prettyStep) {
if (!this.isNumeric(start)) {
throw new Error('Parameter \'start\' is not numeric; value: ' + start);
}
if (!this.isNumeric(end)) {
throw new Error('Parameter \'end\' is not numeric; value: ' + start);
}
if (!this.isNumeric(step)) {
throw new Error('Parameter \'step\' is not numeric; value: ' + start);
}
this._start = start ? start : 0;
this._end = end ? end : 0;

Loading…
Cancel
Save