|
|
@ -1,3 +1,11 @@ |
|
|
|
var Emitter = require('emitter-component'); |
|
|
|
var DataSet = require('../DataSet'); |
|
|
|
var DataView = require('../DataView'); |
|
|
|
var Point3d = require('./Point3d'); |
|
|
|
var Point2d = require('./Point2d'); |
|
|
|
var Filter = require('./Filter'); |
|
|
|
var StepNumber = require('./StepNumber'); |
|
|
|
|
|
|
|
/** |
|
|
|
* @constructor Graph3d |
|
|
|
* Graph3d displays data in 3d. |
|
|
@ -715,19 +723,6 @@ Graph3d.prototype._getDataPoints = function (data) { |
|
|
|
return dataPoints; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Append suffix 'px' to provided value x |
|
|
|
* @param {int} x An integer value |
|
|
|
* @return {string} the string value of x, followed by the suffix 'px' |
|
|
|
*/ |
|
|
|
Graph3d.px = function(x) { |
|
|
|
return x + 'px'; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Create the main frame for the Graph3d. |
|
|
|
* This function is executed once when a Graph3d object is created. The frame |
|
|
@ -2455,458 +2450,6 @@ G3DpreventDefault = function (event) { |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* @prototype Point3d |
|
|
|
* @param {Number} x |
|
|
|
* @param {Number} y |
|
|
|
* @param {Number} z |
|
|
|
*/ |
|
|
|
function Point3d(x, y, z) { |
|
|
|
this.x = x !== undefined ? x : 0; |
|
|
|
this.y = y !== undefined ? y : 0; |
|
|
|
this.z = z !== undefined ? z : 0; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Subtract the two provided points, returns a-b |
|
|
|
* @param {Point3d} a |
|
|
|
* @param {Point3d} b |
|
|
|
* @return {Point3d} a-b |
|
|
|
*/ |
|
|
|
Point3d.subtract = function(a, b) { |
|
|
|
var sub = new Point3d(); |
|
|
|
sub.x = a.x - b.x; |
|
|
|
sub.y = a.y - b.y; |
|
|
|
sub.z = a.z - b.z; |
|
|
|
return sub; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Add the two provided points, returns a+b |
|
|
|
* @param {Point3d} a |
|
|
|
* @param {Point3d} b |
|
|
|
* @return {Point3d} a+b |
|
|
|
*/ |
|
|
|
Point3d.add = function(a, b) { |
|
|
|
var sum = new Point3d(); |
|
|
|
sum.x = a.x + b.x; |
|
|
|
sum.y = a.y + b.y; |
|
|
|
sum.z = a.z + b.z; |
|
|
|
return sum; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Calculate the average of two 3d points |
|
|
|
* @param {Point3d} a |
|
|
|
* @param {Point3d} b |
|
|
|
* @return {Point3d} The average, (a+b)/2 |
|
|
|
*/ |
|
|
|
Point3d.avg = function(a, b) { |
|
|
|
return new Point3d( |
|
|
|
(a.x + b.x) / 2, |
|
|
|
(a.y + b.y) / 2, |
|
|
|
(a.z + b.z) / 2 |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Calculate the cross product of the two provided points, returns axb |
|
|
|
* Documentation: http://en.wikipedia.org/wiki/Cross_product
|
|
|
|
* @param {Point3d} a |
|
|
|
* @param {Point3d} b |
|
|
|
* @return {Point3d} cross product axb |
|
|
|
*/ |
|
|
|
Point3d.crossProduct = function(a, b) { |
|
|
|
var crossproduct = new Point3d(); |
|
|
|
|
|
|
|
crossproduct.x = a.y * b.z - a.z * b.y; |
|
|
|
crossproduct.y = a.z * b.x - a.x * b.z; |
|
|
|
crossproduct.z = a.x * b.y - a.y * b.x; |
|
|
|
|
|
|
|
return crossproduct; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Rtrieve the length of the vector (or the distance from this point to the origin |
|
|
|
* @return {Number} length |
|
|
|
*/ |
|
|
|
Point3d.prototype.length = function() { |
|
|
|
return Math.sqrt( |
|
|
|
this.x * this.x + |
|
|
|
this.y * this.y + |
|
|
|
this.z * this.z |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* @prototype Point2d |
|
|
|
*/ |
|
|
|
Point2d = function (x, y) { |
|
|
|
this.x = x !== undefined ? x : 0; |
|
|
|
this.y = y !== undefined ? y : 0; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* @class Filter |
|
|
|
* |
|
|
|
* @param {DataSet} data The google data table |
|
|
|
* @param {Number} column The index of the column to be filtered |
|
|
|
* @param {Graph} graph The graph |
|
|
|
*/ |
|
|
|
function Filter (data, column, graph) { |
|
|
|
this.data = data; |
|
|
|
this.column = column; |
|
|
|
this.graph = graph; // the parent graph
|
|
|
|
|
|
|
|
this.index = undefined; |
|
|
|
this.value = undefined; |
|
|
|
|
|
|
|
// read all distinct values and select the first one
|
|
|
|
this.values = graph.getDistinctValues(data.get(), this.column); |
|
|
|
|
|
|
|
// sort both numeric and string values correctly
|
|
|
|
this.values.sort(function (a, b) { |
|
|
|
return a > b ? 1 : a < b ? -1 : 0; |
|
|
|
}); |
|
|
|
|
|
|
|
if (this.values.length > 0) { |
|
|
|
this.selectValue(0); |
|
|
|
} |
|
|
|
|
|
|
|
// create an array with the filtered datapoints. this will be loaded afterwards
|
|
|
|
this.dataPoints = []; |
|
|
|
|
|
|
|
this.loaded = false; |
|
|
|
this.onLoadCallback = undefined; |
|
|
|
|
|
|
|
if (graph.animationPreload) { |
|
|
|
this.loaded = false; |
|
|
|
this.loadInBackground(); |
|
|
|
} |
|
|
|
else { |
|
|
|
this.loaded = true; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Return the label |
|
|
|
* @return {string} label |
|
|
|
*/ |
|
|
|
Filter.prototype.isLoaded = function() { |
|
|
|
return this.loaded; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Return the loaded progress |
|
|
|
* @return {Number} percentage between 0 and 100 |
|
|
|
*/ |
|
|
|
Filter.prototype.getLoadedProgress = function() { |
|
|
|
var len = this.values.length; |
|
|
|
|
|
|
|
var i = 0; |
|
|
|
while (this.dataPoints[i]) { |
|
|
|
i++; |
|
|
|
} |
|
|
|
|
|
|
|
return Math.round(i / len * 100); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Return the label |
|
|
|
* @return {string} label |
|
|
|
*/ |
|
|
|
Filter.prototype.getLabel = function() { |
|
|
|
return this.graph.filterLabel; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Return the columnIndex of the filter |
|
|
|
* @return {Number} columnIndex |
|
|
|
*/ |
|
|
|
Filter.prototype.getColumn = function() { |
|
|
|
return this.column; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Return the currently selected value. Returns undefined if there is no selection |
|
|
|
* @return {*} value |
|
|
|
*/ |
|
|
|
Filter.prototype.getSelectedValue = function() { |
|
|
|
if (this.index === undefined) |
|
|
|
return undefined; |
|
|
|
|
|
|
|
return this.values[this.index]; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Retrieve all values of the filter |
|
|
|
* @return {Array} values |
|
|
|
*/ |
|
|
|
Filter.prototype.getValues = function() { |
|
|
|
return this.values; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Retrieve one value of the filter |
|
|
|
* @param {Number} index |
|
|
|
* @return {*} value |
|
|
|
*/ |
|
|
|
Filter.prototype.getValue = function(index) { |
|
|
|
if (index >= this.values.length) |
|
|
|
throw 'Error: index out of range'; |
|
|
|
|
|
|
|
return this.values[index]; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Retrieve the (filtered) dataPoints for the currently selected filter index |
|
|
|
* @param {Number} [index] (optional) |
|
|
|
* @return {Array} dataPoints |
|
|
|
*/ |
|
|
|
Filter.prototype._getDataPoints = function(index) { |
|
|
|
if (index === undefined) |
|
|
|
index = this.index; |
|
|
|
|
|
|
|
if (index === undefined) |
|
|
|
return []; |
|
|
|
|
|
|
|
var dataPoints; |
|
|
|
if (this.dataPoints[index]) { |
|
|
|
dataPoints = this.dataPoints[index]; |
|
|
|
} |
|
|
|
else { |
|
|
|
var f = {}; |
|
|
|
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); |
|
|
|
|
|
|
|
this.dataPoints[index] = dataPoints; |
|
|
|
} |
|
|
|
|
|
|
|
return dataPoints; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Set a callback function when the filter is fully loaded. |
|
|
|
*/ |
|
|
|
Filter.prototype.setOnLoadCallback = function(callback) { |
|
|
|
this.onLoadCallback = callback; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Add a value to the list with available values for this filter |
|
|
|
* No double entries will be created. |
|
|
|
* @param {Number} index |
|
|
|
*/ |
|
|
|
Filter.prototype.selectValue = function(index) { |
|
|
|
if (index >= this.values.length) |
|
|
|
throw 'Error: index out of range'; |
|
|
|
|
|
|
|
this.index = index; |
|
|
|
this.value = this.values[index]; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Load all filtered rows in the background one by one |
|
|
|
* Start this method without providing an index! |
|
|
|
*/ |
|
|
|
Filter.prototype.loadInBackground = function(index) { |
|
|
|
if (index === undefined) |
|
|
|
index = 0; |
|
|
|
|
|
|
|
var frame = this.graph.frame; |
|
|
|
|
|
|
|
if (index < this.values.length) { |
|
|
|
var dataPointsTemp = this._getDataPoints(index); |
|
|
|
//this.graph.redrawInfo(); // TODO: not neat
|
|
|
|
|
|
|
|
// create a progress box
|
|
|
|
if (frame.progress === undefined) { |
|
|
|
frame.progress = document.createElement('DIV'); |
|
|
|
frame.progress.style.position = 'absolute'; |
|
|
|
frame.progress.style.color = 'gray'; |
|
|
|
frame.appendChild(frame.progress); |
|
|
|
} |
|
|
|
var progress = this.getLoadedProgress(); |
|
|
|
frame.progress.innerHTML = 'Loading animation... ' + progress + '%'; |
|
|
|
// TODO: this is no nice solution...
|
|
|
|
frame.progress.style.bottom = Graph3d.px(60); // TODO: use height of slider
|
|
|
|
frame.progress.style.left = Graph3d.px(10); |
|
|
|
|
|
|
|
var me = this; |
|
|
|
setTimeout(function() {me.loadInBackground(index+1);}, 10); |
|
|
|
this.loaded = false; |
|
|
|
} |
|
|
|
else { |
|
|
|
this.loaded = true; |
|
|
|
|
|
|
|
// remove the progress box
|
|
|
|
if (frame.progress !== undefined) { |
|
|
|
frame.removeChild(frame.progress); |
|
|
|
frame.progress = undefined; |
|
|
|
} |
|
|
|
|
|
|
|
if (this.onLoadCallback) |
|
|
|
this.onLoadCallback(); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* @prototype StepNumber |
|
|
|
* The class StepNumber is an iterator for Numbers. You provide a start and end |
|
|
|
* value, and a best step size. StepNumber itself rounds to fixed values and |
|
|
|
* a finds the step that best fits the provided step. |
|
|
|
* |
|
|
|
* If prettyStep is true, the step size is chosen as close as possible to the |
|
|
|
* provided step, but being a round value like 1, 2, 5, 10, 20, 50, .... |
|
|
|
* |
|
|
|
* Example usage: |
|
|
|
* var step = new StepNumber(0, 10, 2.5, true); |
|
|
|
* step.start(); |
|
|
|
* while (!step.end()) { |
|
|
|
* alert(step.getCurrent()); |
|
|
|
* step.next(); |
|
|
|
* } |
|
|
|
* |
|
|
|
* Version: 1.0 |
|
|
|
* |
|
|
|
* @param {Number} start The start value |
|
|
|
* @param {Number} end The end value |
|
|
|
* @param {Number} step Optional. Step size. Must be a positive value. |
|
|
|
* @param {boolean} prettyStep Optional. If true, the step size is rounded |
|
|
|
* To a pretty step size (like 1, 2, 5, 10, 20, 50, ...) |
|
|
|
*/ |
|
|
|
StepNumber = function (start, end, step, prettyStep) { |
|
|
|
// set default values
|
|
|
|
this._start = 0; |
|
|
|
this._end = 0; |
|
|
|
this._step = 1; |
|
|
|
this.prettyStep = true; |
|
|
|
this.precision = 5; |
|
|
|
|
|
|
|
this._current = 0; |
|
|
|
this.setRange(start, end, step, prettyStep); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Set a new range: start, end and step. |
|
|
|
* |
|
|
|
* @param {Number} start The start value |
|
|
|
* @param {Number} end The end value |
|
|
|
* @param {Number} step Optional. Step size. Must be a positive value. |
|
|
|
* @param {boolean} prettyStep Optional. If true, the step size is rounded |
|
|
|
* To a pretty step size (like 1, 2, 5, 10, 20, 50, ...) |
|
|
|
*/ |
|
|
|
StepNumber.prototype.setRange = function(start, end, step, prettyStep) { |
|
|
|
this._start = start ? start : 0; |
|
|
|
this._end = end ? end : 0; |
|
|
|
|
|
|
|
this.setStep(step, prettyStep); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Set a new step size |
|
|
|
* @param {Number} step New step size. Must be a positive value |
|
|
|
* @param {boolean} prettyStep Optional. If true, the provided step is rounded |
|
|
|
* to a pretty step size (like 1, 2, 5, 10, 20, 50, ...) |
|
|
|
*/ |
|
|
|
StepNumber.prototype.setStep = function(step, prettyStep) { |
|
|
|
if (step === undefined || step <= 0) |
|
|
|
return; |
|
|
|
|
|
|
|
if (prettyStep !== undefined) |
|
|
|
this.prettyStep = prettyStep; |
|
|
|
|
|
|
|
if (this.prettyStep === true) |
|
|
|
this._step = StepNumber.calculatePrettyStep(step); |
|
|
|
else |
|
|
|
this._step = step; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Calculate a nice step size, closest to the desired step size. |
|
|
|
* Returns a value in one of the ranges 1*10^n, 2*10^n, or 5*10^n, where n is an |
|
|
|
* integer Number. For example 1, 2, 5, 10, 20, 50, etc... |
|
|
|
* @param {Number} step Desired step size |
|
|
|
* @return {Number} Nice step size |
|
|
|
*/ |
|
|
|
StepNumber.calculatePrettyStep = function (step) { |
|
|
|
var log10 = function (x) {return Math.log(x) / Math.LN10;}; |
|
|
|
|
|
|
|
// try three steps (multiple of 1, 2, or 5
|
|
|
|
var step1 = Math.pow(10, Math.round(log10(step))), |
|
|
|
step2 = 2 * Math.pow(10, Math.round(log10(step / 2))), |
|
|
|
step5 = 5 * Math.pow(10, Math.round(log10(step / 5))); |
|
|
|
|
|
|
|
// choose the best step (closest to minimum step)
|
|
|
|
var prettyStep = step1; |
|
|
|
if (Math.abs(step2 - step) <= Math.abs(prettyStep - step)) prettyStep = step2; |
|
|
|
if (Math.abs(step5 - step) <= Math.abs(prettyStep - step)) prettyStep = step5; |
|
|
|
|
|
|
|
// for safety
|
|
|
|
if (prettyStep <= 0) { |
|
|
|
prettyStep = 1; |
|
|
|
} |
|
|
|
|
|
|
|
return prettyStep; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* returns the current value of the step |
|
|
|
* @return {Number} current value |
|
|
|
*/ |
|
|
|
StepNumber.prototype.getCurrent = function () { |
|
|
|
return parseFloat(this._current.toPrecision(this.precision)); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* returns the current step size |
|
|
|
* @return {Number} current step size |
|
|
|
*/ |
|
|
|
StepNumber.prototype.getStep = function () { |
|
|
|
return this._step; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Set the current value to the largest value smaller than start, which |
|
|
|
* is a multiple of the step size |
|
|
|
*/ |
|
|
|
StepNumber.prototype.start = function() { |
|
|
|
this._current = this._start - this._start % this._step; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Do a step, add the step size to the current value |
|
|
|
*/ |
|
|
|
StepNumber.prototype.next = function () { |
|
|
|
this._current += this._step; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Returns true whether the end is reached |
|
|
|
* @return {boolean} True if the current value has passed the end value. |
|
|
|
*/ |
|
|
|
StepNumber.prototype.end = function () { |
|
|
|
return (this._current > this._end); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* @constructor Slider |
|
|
|
* |
|
|
@ -2981,7 +2524,7 @@ function Slider(container, options) { |
|
|
|
this.playTimeout = undefined; |
|
|
|
this.playInterval = 1000; // milliseconds
|
|
|
|
this.playLoop = true; |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Select the previous index |
|
|
@ -3308,3 +2851,4 @@ getMouseY = function(event) { |
|
|
|
return event.targetTouches[0] && event.targetTouches[0].clientY || 0; |
|
|
|
}; |
|
|
|
|
|
|
|
module.exports = Graph3d; |