diff --git a/lib/graph3d/DataGroup.js b/lib/graph3d/DataGroup.js index 34eeb889..050cca03 100644 --- a/lib/graph3d/DataGroup.js +++ b/lib/graph3d/DataGroup.js @@ -1,6 +1,9 @@ var DataSet = require('../DataSet'); var DataView = require('../DataView'); var Range = require('./Range'); +var Filter = require('./Filter'); +var Settings = require('./Settings'); +var Point3d = require('./Point3d'); /** @@ -51,6 +54,8 @@ DataGroup.prototype.initializeData = function(graph3d, rawData, style) { if (data.length == 0) return; + this.style = style; + // unsubscribe from the dataTable if (this.dataSet) { this.dataSet.off('*', this._onChange); @@ -102,6 +107,27 @@ DataGroup.prototype.initializeData = function(graph3d, rawData, style) { this._setRangeDefaults(valueRange, graph3d.defaultValueMin, graph3d.defaultValueMax); this.valueRange = valueRange; } + + // Initialize data filter if a filter column is provided + var table = this.getDataTable(); + if (table[0].hasOwnProperty('filter')) { + if (this.dataFilter === undefined) { + this.dataFilter = new Filter(this, 'filter', graph3d); + this.dataFilter.setOnLoadCallback(function() { graph3d.redraw(); }); + } + } + + + var dataPoints; + if (this.dataFilter) { + // apply filtering + dataPoints = this.dataFilter._getDataPoints(); + } + else { + // no filtering. load all data + dataPoints = this._getDataPoints(this.getDataTable()); + } + return dataPoints; }; @@ -290,4 +316,174 @@ DataGroup.prototype.getDataSet = function() { }; +/** + * Return all data values as a list of Point3d objects + */ +DataGroup.prototype.getDataPoints = function(data) { + var dataPoints = []; + + for (var i = 0; i < data.length; i++) { + var point = new Point3d(); + point.x = data[i][this.colX] || 0; + point.y = data[i][this.colY] || 0; + point.z = data[i][this.colZ] || 0; + point.data = data[i]; + + if (this.colValue !== undefined) { + point.value = data[i][this.colValue] || 0; + } + + var obj = {}; + obj.point = point; + obj.bottom = new Point3d(point.x, point.y, this.zRange.min); + obj.trans = undefined; + obj.screen = undefined; + + dataPoints.push(obj); + } + + return dataPoints; +}; + + +/** + * Copy all values from the data table to a matrix. + * + * The provided values are supposed to form a grid of (x,y) positions. + * @private + */ +DataGroup.prototype.initDataAsMatrix = function(data) { + // TODO: store the created matrix dataPoints in the filters instead of + // reloading each time. + var x, y, i, obj; + + // create two lists with all present x and y values + var dataX = this.getDistinctValues(this.colX, data); + var dataY = this.getDistinctValues(this.colY, data); + + var dataPoints = this.getDataPoints(data); + + // create a grid, a 2d matrix, with all values. + var dataMatrix = []; // temporary data matrix + for (i = 0; i < dataPoints.length; i++) { + obj = dataPoints[i]; + + // TODO: implement Array().indexOf() for Internet Explorer + var xIndex = dataX.indexOf(obj.point.x); + var yIndex = dataY.indexOf(obj.point.y); + + if (dataMatrix[xIndex] === undefined) { + dataMatrix[xIndex] = []; + } + + dataMatrix[xIndex][yIndex] = obj; + } + + // fill in the pointers to the neighbors. + for (x = 0; x < dataMatrix.length; x++) { + for (y = 0; y < dataMatrix[x].length; y++) { + if (dataMatrix[x][y]) { + dataMatrix[x][y].pointRight = (x < dataMatrix.length-1) ? dataMatrix[x+1][y] : undefined; + dataMatrix[x][y].pointTop = (y < dataMatrix[x].length-1) ? dataMatrix[x][y+1] : undefined; + dataMatrix[x][y].pointCross = + (x < dataMatrix.length-1 && y < dataMatrix[x].length-1) ? + dataMatrix[x+1][y+1] : + undefined; + } + } + } + + return dataPoints; +} + + +/** + * Return common information, if present + */ +DataGroup.prototype.getInfo = function() { + var dataFilter = this.dataFilter; + if (!dataFilter) return undefined; + + return dataFilter.getLabel() + ': ' + dataFilter.getSelectedValue(); +}; + + +/** + * Reload the data + */ +DataGroup.prototype.reload = function() { + if (this.dataTable) { + this.setData(this.dataTable); + } +}; + + +/** + * Filter the data based on the current filter + * + * @param {Array} data + * @returns {Array} dataPoints Array with point objects which can be drawn on + * screen + */ +DataGroup.prototype._getDataPoints = function (data) { + var dataPoints = []; + + if (this.style === Settings.STYLE.GRID || this.style === Settings.STYLE.SURFACE) { + dataPoints = this.initDataAsMatrix(data); + } + else { // 'dot', 'dot-line', etc. + this._checkValueField(data); + dataPoints = this.getDataPoints(data); + + if (this.style === Settings.STYLE.LINE) { + // Add next member points for line drawing + for (var i = 0; i < dataPoints.length; i++) { + if (i > 0) { + dataPoints[i - 1].pointNext = dataPoints[i]; + } + } + } + } + + return dataPoints; +}; + + +/** + * Check if the state is consistent for the use of the value field. + * + * Throws if a problem is detected. + * @private + */ +DataGroup.prototype._checkValueField = function (data) { + + var hasValueField = this.style === Settings.STYLE.BARCOLOR + || this.style === Settings.STYLE.BARSIZE + || this.style === Settings.STYLE.DOTCOLOR + || this.style === Settings.STYLE.DOTSIZE; + + if (!hasValueField) { + return; // No need to check further + } + + + // Following field must be present for the current graph style + if (this.colValue === undefined) { + throw new Error('Expected data to have ' + + ' field \'style\' ' + + ' for graph style \'' + this.style + '\'' + ); + } + + // The data must also contain this field. + // Note that only first data element is checked. + if (data[0][this.colValue] === undefined) { + throw new Error('Expected data to have ' + + ' field \'' + this.colValue + '\' ' + + ' for graph style \'' + this.style + '\'' + ); + } +}; + + module.exports = DataGroup; diff --git a/lib/graph3d/Filter.js b/lib/graph3d/Filter.js index 0a4ac584..f57b7f86 100644 --- a/lib/graph3d/Filter.js +++ b/lib/graph3d/Filter.js @@ -8,7 +8,7 @@ var DataView = require('../DataView'); * @param {Graph} graph The graph */ function Filter (dataGroup, column, graph) { - this.data = dataGroup.getDataSet(); + this.dataGroup = dataGroup; this.column = column; this.graph = graph; // the parent graph @@ -133,8 +133,8 @@ Filter.prototype._getDataPoints = function(index) { f.column = this.column; f.value = this.values[index]; - var dataView = new DataView(this.data,{filter: function (item) {return (item[f.column] == f.value);}}).get(); - dataPoints = this.graph._getDataPoints(dataView); + var dataView = new DataView(this.dataGroup.getDataSet(), {filter: function (item) {return (item[f.column] == f.value);}}).get(); + dataPoints = this.dataGroup._getDataPoints(dataView); this.dataPoints[index] = dataPoints; } diff --git a/lib/graph3d/Graph3d.js b/lib/graph3d/Graph3d.js index 7ce25fd5..7c820930 100755 --- a/lib/graph3d/Graph3d.js +++ b/lib/graph3d/Graph3d.js @@ -2,7 +2,6 @@ var Emitter = require('emitter-component'); var util = require('../util'); var Point3d = require('./Point3d'); var Point2d = require('./Point2d'); -var Filter = require('./Filter'); var Slider = require('./Slider'); var StepNumber = require('./StepNumber'); var Settings = require('./Settings'); @@ -159,7 +158,6 @@ function Graph3d(container, data, options) { this.colY = undefined; this.colZ = undefined; this.colValue = undefined; - this.colFilter = undefined; // TODO: customize axis range @@ -319,76 +317,28 @@ Graph3d.prototype._calcTranslations = function(points) { /** - * Check if the state is consistent for the use of the value field. - * - * Throws if a problem is detected. + * Transfer min/max values to the Graph3d instance. */ -Graph3d.prototype._checkValueField = function (data) { - - var hasValueField = this.style === Graph3d.STYLE.BARCOLOR - || this.style === Graph3d.STYLE.BARSIZE - || this.style === Graph3d.STYLE.DOTCOLOR - || this.style === Graph3d.STYLE.DOTSIZE; - - if (!hasValueField) { - return; // No need to check further - } - - - // Following field must be present for the current graph style - if (this.colValue === undefined) { - throw new Error('Expected data to have ' - + ' field \'style\' ' - + ' for graph style \'' + this.style + '\'' - ); - } - - // The data must also contain this field. - // Note that only first data element is checked. - if (data[0][this.colValue] === undefined) { - throw new Error('Expected data to have ' - + ' field \'' + this.colValue + '\' ' - + ' for graph style \'' + this.style + '\'' - ); - } -}; - - -Graph3d.prototype._initializeData = function(rawData, style) { - this.dataGroup.initializeData(this, rawData, style); - - // Transfer min/max values to the Graph3d instance. +Graph3d.prototype._initializeRanges = function() { // TODO: later on, all min/maxes of all datagroups will be combined here - this.xRange = this.dataGroup.xRange; - this.yRange = this.dataGroup.yRange; - this.zRange = this.dataGroup.zRange; - this.valueRange = this.dataGroup.valueRange; + var dg = this.dataGroup; + this.xRange = dg.xRange; + this.yRange = dg.yRange; + this.zRange = dg.zRange; + this.valueRange = dg.valueRange; // Values currently needed but which need to be sorted out for // the multiple graph case. - this.xStep = this.dataGroup.xStep; - this.yStep = this.dataGroup.yStep; - this.zStep = this.dataGroup.zStep; - this.xBarWidth = this.dataGroup.xBarWidth; - this.yBarWidth = this.dataGroup.yBarWidth; - this.colX = this.dataGroup.colX; - this.colY = this.dataGroup.colY; - this.colZ = this.dataGroup.colZ; - this.colValue = this.dataGroup.colValue; - - // Check if a filter column is provided - var data = this.dataGroup.getDataTable(); - - if (data[0].hasOwnProperty('filter')) { - // Only set this field if it's actually present - this.colFilter = 'filter'; + this.xStep = dg.xStep; + this.yStep = dg.yStep; + this.zStep = dg.zStep; + this.xBarWidth = dg.xBarWidth; + this.yBarWidth = dg.yBarWidth; + this.colX = dg.colX; + this.colY = dg.colY; + this.colZ = dg.colZ; + this.colValue = dg.colValue; - var me = this; - if (this.dataFilter === undefined) { - this.dataFilter = new Filter(this.dataGroup, this.colFilter, this); - this.dataFilter.setOnLoadCallback(function() {me.redraw();}); - } - } // set the scale dependent on the ranges. this._setScale(); @@ -581,10 +531,14 @@ Graph3d.prototype._resizeCanvas = function() { this.frame.filter.style.width = (this.frame.canvas.clientWidth - 2 * 10) + 'px'; }; + /** - * Start animation + * Start animation, if requested and filter present */ Graph3d.prototype.animationStart = function() { + // start animation when option is true + if (!this.animationAutoStart || !this.dataGroup.dataFilter) return; + if (!this.frame.filter || !this.frame.filter.slider) throw new Error('No animation available'); @@ -649,19 +603,9 @@ Graph3d.prototype.getCameraPosition = function() { */ Graph3d.prototype._readData = function(data) { // read the data - this._initializeData(data, this.style); - + this.dataPoints = this.dataGroup.initializeData(this, data, this.style); - if (this.dataFilter) { - // apply filtering - this.dataPoints = this.dataFilter._getDataPoints(); - } - else { - // no filtering. load all data - this.dataPoints = this._getDataPoints(this.dataGroup.getDataTable()); - } - - // draw the filter + this._initializeRanges(); this._redrawFilter(); }; @@ -675,11 +619,7 @@ Graph3d.prototype.setData = function (data) { this._readData(data); this.redraw(); - - // start animation when option is true - if (this.animationAutoStart && this.dataFilter) { - this.animationStart(); - } + this.animationStart(); }; /** @@ -691,16 +631,11 @@ Graph3d.prototype.setOptions = function (options) { this.animationStop(); Settings.setOptions(options, this); - this.setPointDrawingMethod(); this._setSize(this.width, this.height); this.setData(this.dataGroup.getDataTable()); - - // start animation when option is true - if (this.animationAutoStart && this.dataFilter) { - this.animationStart(); - } + this.animationStart(); }; @@ -934,39 +869,44 @@ Graph3d.prototype._redrawLegend = function() { * Redraw the filter */ Graph3d.prototype._redrawFilter = function() { - this.frame.filter.innerHTML = ''; + var dataFilter = this.dataGroup.dataFilter; + var filter = this.frame.filter; + filter.innerHTML = ''; - if (this.dataFilter) { - var options = { - 'visible': this.showAnimationControls - }; - var slider = new Slider(this.frame.filter, options); - this.frame.filter.slider = slider; + if (!dataFilter) { + filter.slider = undefined; + return; + } - // TODO: css here is not nice here... - this.frame.filter.style.padding = '10px'; - //this.frame.filter.style.backgroundColor = '#EFEFEF'; + var options = { + 'visible': this.showAnimationControls + }; + var slider = new Slider(filter, options); + filter.slider = slider; - slider.setValues(this.dataFilter.values); - slider.setPlayInterval(this.animationInterval); + // TODO: css here is not nice here... + filter.style.padding = '10px'; + //this.frame.filter.style.backgroundColor = '#EFEFEF'; - // create an event handler - var me = this; - var onchange = function () { - var index = slider.getIndex(); + slider.setValues(dataFilter.values); + slider.setPlayInterval(this.animationInterval); - me.dataFilter.selectValue(index); - me.dataPoints = me.dataFilter._getDataPoints(); + // create an event handler + var me = this; + var onchange = function () { + var dataFilter = me.dataGroup.dataFilter; + var index = slider.getIndex(); - me.redraw(); - }; - slider.setOnChangeCallback(onchange); - } - else { - this.frame.filter.slider = undefined; - } + dataFilter.selectValue(index); + me.dataPoints = dataFilter._getDataPoints(); + + me.redraw(); + }; + + slider.setOnChangeCallback(onchange); }; + /** * Redraw the slider */ @@ -981,19 +921,20 @@ Graph3d.prototype._redrawSlider = function() { * Redraw common information */ Graph3d.prototype._redrawInfo = function() { - if (this.dataFilter) { - var ctx = this._getContext(); + var info = this.dataGroup.getInfo(); + if (info === undefined) return; - ctx.font = '14px arial'; // TODO: put in options - ctx.lineStyle = 'gray'; - ctx.fillStyle = 'gray'; - ctx.textAlign = 'left'; - ctx.textBaseline = 'top'; + var ctx = this._getContext(); - var x = this.margin; - var y = this.margin; - ctx.fillText(this.dataFilter.getLabel() + ': ' + this.dataFilter.getSelectedValue(), x, y); - } + ctx.font = '14px arial'; // TODO: put in options + ctx.lineStyle = 'gray'; + ctx.fillStyle = 'gray'; + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + + var x = this.margin; + var y = this.margin; + ctx.fillText(info, x, y); };