@ -1,4 +1,5 @@
var Emitter = require ( 'emitter-component' ) ; var DataSet = require ( '../DataSet' ) ;
var Emitter = require ( 'emitter-component' ) ;
var DataSet = require ( '../DataSet' ) ;
var DataView = require ( '../DataView' ) ;
var util = require ( '../util' ) ;
var Point3d = require ( './Point3d' ) ;
@ -9,6 +10,7 @@ var Slider = require('./Slider');
var StepNumber = require ( './StepNumber' ) ;
var Range = require ( './Range' ) ;
var Settings = require ( './Settings' ) ;
var DataGroup = require ( './DataGroup' ) ;
/// enumerate the available styles
@ -148,7 +150,7 @@ function Graph3d(container, data, options) {
// create variables and set default values
this . containerElement = container ;
this . dataTable = null ; // The original data table
this . dataGroup = new DataGroup ( ) ;
this . dataPoints = null ; // The table with point objects
// create a frame and canvas
@ -303,11 +305,7 @@ Graph3d.prototype._convertTranslationToScreen = function(translation) {
/ * *
* Calculate the translations and screen positions of all points
* /
Graph3d . prototype . _calcTranslations = function ( points , sort ) {
if ( sort === undefined ) {
sort = true ;
}
Graph3d . prototype . _calcTranslations = function ( points ) {
for ( var i = 0 ; i < points . length ; i ++ ) {
var point = points [ i ] ;
point . trans = this . _convertPointToTranslation ( point . point ) ;
@ -318,10 +316,6 @@ Graph3d.prototype._calcTranslations = function(points, sort) {
point . dist = this . showPerspective ? transBottom . length ( ) : - transBottom . z ;
}
if ( ! sort ) {
return ;
}
// sort the points on depth of their (x,y) position (not on z)
var sortDepth = function ( a , b ) {
return b . dist - a . dist ;
@ -330,78 +324,6 @@ Graph3d.prototype._calcTranslations = function(points, sort) {
} ;
Graph3d . prototype . getNumberOfRows = function ( data ) {
return data . length ;
}
Graph3d . prototype . getNumberOfColumns = function ( data ) {
var counter = 0 ;
for ( var column in data [ 0 ] ) {
if ( data [ 0 ] . hasOwnProperty ( column ) ) {
counter ++ ;
}
}
return counter ;
}
Graph3d . prototype . getDistinctValues = function ( data , column ) {
var distinctValues = [ ] ;
for ( var i = 0 ; i < data . length ; i ++ ) {
if ( distinctValues . indexOf ( data [ i ] [ column ] ) == - 1 ) {
distinctValues . push ( data [ i ] [ column ] ) ;
}
}
return distinctValues . sort ( function ( a , b ) { return a - b ; } ) ;
}
/ * *
* Determine the smallest difference between the values for given
* column in the passed data set .
*
* @ returns { Number | null } Smallest difference value or
* null , if it can ' t be determined .
* /
Graph3d . prototype . getSmallestDifference = function ( data , column ) {
var values = this . getDistinctValues ( data , column ) ;
var diffs = [ ] ;
// Get all the distinct diffs
// Array values is assumed to be sorted here
var smallest_diff = null ;
for ( var i = 1 ; i < values . length ; i ++ ) {
var diff = values [ i ] - values [ i - 1 ] ;
if ( smallest_diff == null || smallest_diff > diff ) {
smallest_diff = diff ;
}
}
return smallest_diff ;
}
/ * *
* Get the absolute min / max values for the passed data column .
*
* @ returns { Range } A Range instance with min / max members properly set .
* /
Graph3d . prototype . getColumnRange = function ( data , column ) {
var range = new Range ( ) ;
// Adjust the range so that it covers all values in the passed data elements.
for ( var i = 0 ; i < data . length ; i ++ ) {
var item = data [ i ] [ column ] ;
range . adjust ( item ) ;
}
return range ;
} ;
/ * *
* Check if the state is consistent for the use of the value field .
*
@ -418,6 +340,7 @@ Graph3d.prototype._checkValueField = function (data) {
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 '
@ -437,150 +360,76 @@ Graph3d.prototype._checkValueField = function (data) {
} ;
/ * *
* Set default values for range
*
* The default values override the range values , if defined .
*
* Because it 's possible that only defaultMin or defaultMax is set, it' s better
* to pass in a range already set with the min / max set from the data . Otherwise ,
* it ' s quite hard to process the min / max properly .
* /
Graph3d . prototype . _setRangeDefaults = function ( range , defaultMin , defaultMax ) {
if ( defaultMin !== undefined ) {
range . min = defaultMin ;
}
if ( defaultMax !== undefined ) {
range . max = defaultMax ;
}
// This is the original way that the default min/max values were adjusted.
// TODO: Perhaps it's better if an error is thrown if the values do not agree.
// But this will change the behaviour.
if ( range . max <= range . min ) range . max = range . min + 1 ;
} ;
/ * *
* Initialize the data from the data table . Calculate minimum and maximum values
* and column index values
* @ param { Array | DataSet | DataView } rawData The data containing the items for
* the Graph .
* @ param { Number } style Style Number
* /
Graph3d . prototype . _dataInitialize = function ( rawData , style ) {
var me = this ;
// unsubscribe from the dataTable
if ( this . dataSet ) {
this . dataSet . off ( '*' , this . _onChange ) ;
}
Graph3d . prototype . _initializeData = function ( rawData , style ) {
this . dataGroup . initializeData ( this , rawData , style ) ;
if ( rawData === undefined )
return ;
// Transfer min/max values to the Graph3d instance.
// 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 ;
if ( Array . isArray ( rawData ) ) {
rawData = new DataSet ( rawData ) ;
}
// 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 ;
var data ;
if ( rawData instanceof DataSet || rawData instanceof DataView ) {
data = rawData . get ( ) ;
}
else {
throw new Error ( 'Array, DataSet, or DataView expected' ) ;
}
// Check if a filter column is provided
var data = this . dataGroup . getDataTable ( ) ;
if ( data . length == 0 )
return ;
this . dataSet = rawData ;
this . dataTable = data ;
// subscribe to changes in the dataset
this . _onChange = function ( ) {
me . setData ( me . dataSet ) ;
} ;
this . dataSet . on ( '*' , this . _onChange ) ;
// determine the location of x,y,z,value,filter columns
this . colX = 'x' ;
this . colY = 'y' ;
this . colZ = 'z' ;
var withBars = this . style == Graph3d . STYLE . BAR ||
this . style == Graph3d . STYLE . BARCOLOR ||
this . style == Graph3d . STYLE . BARSIZE ;
// determine barWidth from data
if ( withBars ) {
if ( this . defaultXBarWidth !== undefined ) {
this . xBarWidth = this . defaultXBarWidth ;
}
else {
this . xBarWidth = this . getSmallestDifference ( data , this . colX ) || 1 ;
}
if ( this . defaultYBarWidth !== undefined ) {
this . yBarWidth = this . defaultYBarWidth ;
}
else {
this . yBarWidth = this . getSmallestDifference ( data , this . colY ) || 1 ;
}
}
// calculate minimums and maximums
var NUMSTEPS = 5 ;
var xRange = this . getColumnRange ( data , this . colX ) ;
if ( withBars ) {
xRange . expand ( this . xBarWidth / 2 ) ;
}
this . _setRangeDefaults ( xRange , this . defaultXMin , this . defaultXMax ) ;
this . xRange = xRange ;
this . xStep = ( this . defaultXStep !== undefined ) ? this . defaultXStep : xRange . range ( ) / NUMSTEPS ;
var yRange = this . getColumnRange ( data , this . colY ) ;
if ( withBars ) {
yRange . expand ( this . yBarWidth / 2 ) ;
}
this . _setRangeDefaults ( yRange , this . defaultYMin , this . defaultYMax ) ;
this . yRange = yRange ;
this . yStep = ( this . defaultYStep !== undefined ) ? this . defaultYStep : yRange . range ( ) / NUMSTEPS ;
var zRange = this . getColumnRange ( data , this . colZ ) ;
this . _setRangeDefaults ( zRange , this . defaultZMin , this . defaultZMax ) ;
this . zRange = zRange ;
this . zStep = ( this . defaultZStep !== undefined ) ? this . defaultZStep : zRange . range ( ) / NUMSTEPS ;
if ( data [ 0 ] . hasOwnProperty ( 'style' ) ) {
this . colValue = 'style' ;
var valueRange = this . getColumnRange ( data , this . colValue ) ;
this . _setRangeDefaults ( valueRange , this . defaultValueMin , this . defaultValueMax ) ;
this . valueRange = valueRange ;
}
// check if a filter column is provided
// Needs to be started after zRange is defined
if ( data [ 0 ] . hasOwnProperty ( 'filter' ) ) {
// Only set this field if it's actually present
this . colFilter = 'filter' ;
var me = this ;
if ( this . dataFilter === undefined ) {
this . dataFilter = new Filter ( rawData , this . colFilter , this ) ;
this . dataFilter = new Filter ( this . dataGroup , this . colFilter , this ) ;
this . dataFilter . setOnLoadCallback ( function ( ) { me . redraw ( ) ; } ) ;
}
}
// set the scale dependent on the ranges.
this . _setScale ( ) ;
} ;
/ * *
* Return all data values as a list of Point3d objects
* /
Graph3d . 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 ;
} ;
/ * *
* Filter the data based on the current filter
@ -598,60 +447,29 @@ Graph3d.prototype._getDataPoints = function (data) {
if ( this . style === Graph3d . STYLE . GRID ||
this . style === Graph3d . STYLE . SURFACE ) {
// copy all values from the google data table to a matrix
// copy all values from the data table to a matrix
// the provided values are supposed to form a grid of (x,y) positions
// create two lists with all present x and y values
var dataX = [ ] ;
var dataY = [ ] ;
for ( i = 0 ; i < this . getNumberOfRows ( data ) ; i ++ ) {
x = data [ i ] [ this . colX ] || 0 ;
y = data [ i ] [ this . colY ] || 0 ;
if ( dataX . indexOf ( x ) === - 1 ) {
dataX . push ( x ) ;
}
if ( dataY . indexOf ( y ) === - 1 ) {
dataY . push ( y ) ;
}
}
var dataX = this . dataGroup . getDistinctValues ( this . colX , data ) ;
var dataY = this . dataGroup . getDistinctValues ( this . colY , data ) ;
var sortNumber = function ( a , b ) {
return a - b ;
} ;
dataX . sort ( sortNumber ) ;
dataY . sort ( sortNumber ) ;
dataPoints = this . getDataPoints ( data ) ;
// create a grid, a 2d matrix, with all values.
var dataMatrix = [ ] ; // temporary data matrix
for ( i = 0 ; i < data . length ; i ++ ) {
x = data [ i ] [ this . colX ] || 0 ;
y = data [ i ] [ this . colY ] || 0 ;
z = data [ i ] [ this . colZ ] || 0 ;
for ( i = 0 ; i < dataPoints . length ; i ++ ) {
obj = dataPoints [ i ] ;
// TODO: implement Array().indexOf() for Internet Explorer
var xIndex = dataX . indexOf ( x ) ;
var yIndex = dataY . indexOf ( y ) ;
var xIndex = dataX . indexOf ( obj . point . x ) ;
var yIndex = dataY . indexOf ( obj . point . y ) ;
if ( dataMatrix [ xIndex ] === undefined ) {
dataMatrix [ xIndex ] = [ ] ;
}
var point3d = new Point3d ( ) ;
point3d . x = x ;
point3d . y = y ;
point3d . z = z ;
point3d . data = data [ i ] ;
obj = { } ;
obj . point = point3d ;
obj . trans = undefined ;
obj . screen = undefined ;
obj . bottom = new Point3d ( x , y , this . zRange . min ) ;
dataMatrix [ xIndex ] [ yIndex ] = obj ;
dataPoints . push ( obj ) ;
}
// fill in the pointers to the neighbors.
@ -670,39 +488,22 @@ Graph3d.prototype._getDataPoints = function (data) {
}
else { // 'dot', 'dot-line', etc.
this . _checkValueField ( data ) ;
dataPoints = this . getDataPoints ( data ) ;
// copy all values from the google data table to a list with Point3d objects
for ( i = 0 ; i < data . length ; i ++ ) {
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 ;
}
obj = { } ;
obj . point = point ;
obj . bottom = new Point3d ( point . x , point . y , this . zRange . min ) ;
obj . trans = undefined ;
obj . screen = undefined ;
if ( this . style === Graph3d . STYLE . LINE ) {
if ( this . style === Graph3d . STYLE . LINE ) {
// Add next member points for line drawing
for ( i = 0 ; i < dataPoints . length ; i ++ ) {
if ( i > 0 ) {
// Add next point for line drawing
dataPoints [ i - 1 ] . pointNext = obj ;
dataPoints [ i - 1 ] . pointNext = dataPoints [ i ] ; ;
}
}
dataPoints . push ( obj ) ;
}
}
return dataPoints ;
} ;
/ * *
* Create the main frame for the Graph3d .
*
@ -854,7 +655,7 @@ Graph3d.prototype.getCameraPosition = function() {
* /
Graph3d . prototype . _readData = function ( data ) {
// read the data
this . _dataInitialize ( data , this . style ) ;
this . _initializeData ( data , this . style ) ;
if ( this . dataFilter ) {
@ -863,7 +664,7 @@ Graph3d.prototype._readData = function(data) {
}
else {
// no filtering. load all data
this . dataPoints = this . _getDataPoints ( this . dataTable ) ;
this . dataPoints = this . _getDataPoints ( this . dataGroup . getData Table ( ) ) ;
}
// draw the filter
@ -901,9 +702,7 @@ Graph3d.prototype.setOptions = function (options) {
this . _setSize ( this . width , this . height ) ;
// re-load the data
if ( this . dataTable ) {
this . setData ( this . dataTable ) ;
}
this . dataGroup . reload ( ) ;
// start animation when option is true
if ( this . animationAutoStart && this . dataFilter ) {
@ -1138,6 +937,7 @@ Graph3d.prototype._redrawLegend = function() {
ctx . fillText ( label , right , bottom + this . margin ) ;
} ;
/ * *
* Redraw the filter
* /
@ -2335,6 +2135,20 @@ Graph3d.prototype._dataPointFromXY = function (x, y) {
return closestDataPoint ;
} ;
/ * *
* Determine if the given style has bars
*
* @ param { number } style the style to check
* @ returns { boolean } true if bar style , false otherwise
* /
Graph3d . prototype . hasBars = function ( style ) {
return style == Graph3d . STYLE . BAR ||
style == Graph3d . STYLE . BARCOLOR ||
style == Graph3d . STYLE . BARSIZE ;
} ;
/ * *
* Display a tooltip for given data point
* @ param { Object } dataPoint