vis-rails 2.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/lib/vis/rails/version.rb +1 -1
  3. data/vendor/assets/javascripts/vis.js +26 -26
  4. metadata +16 -85
  5. data/vendor/assets/vis/DataSet.js +0 -926
  6. data/vendor/assets/vis/DataView.js +0 -283
  7. data/vendor/assets/vis/graph/Edge.js +0 -957
  8. data/vendor/assets/vis/graph/Graph.js +0 -2291
  9. data/vendor/assets/vis/graph/Groups.js +0 -80
  10. data/vendor/assets/vis/graph/Images.js +0 -41
  11. data/vendor/assets/vis/graph/Node.js +0 -966
  12. data/vendor/assets/vis/graph/Popup.js +0 -132
  13. data/vendor/assets/vis/graph/css/graph-manipulation.css +0 -128
  14. data/vendor/assets/vis/graph/css/graph-navigation.css +0 -66
  15. data/vendor/assets/vis/graph/dotparser.js +0 -829
  16. data/vendor/assets/vis/graph/graphMixins/ClusterMixin.js +0 -1143
  17. data/vendor/assets/vis/graph/graphMixins/HierarchicalLayoutMixin.js +0 -311
  18. data/vendor/assets/vis/graph/graphMixins/ManipulationMixin.js +0 -576
  19. data/vendor/assets/vis/graph/graphMixins/MixinLoader.js +0 -199
  20. data/vendor/assets/vis/graph/graphMixins/NavigationMixin.js +0 -205
  21. data/vendor/assets/vis/graph/graphMixins/SectorsMixin.js +0 -552
  22. data/vendor/assets/vis/graph/graphMixins/SelectionMixin.js +0 -648
  23. data/vendor/assets/vis/graph/graphMixins/physics/BarnesHut.js +0 -398
  24. data/vendor/assets/vis/graph/graphMixins/physics/HierarchialRepulsion.js +0 -64
  25. data/vendor/assets/vis/graph/graphMixins/physics/PhysicsMixin.js +0 -697
  26. data/vendor/assets/vis/graph/graphMixins/physics/Repulsion.js +0 -66
  27. data/vendor/assets/vis/graph/img/acceptDeleteIcon.png +0 -0
  28. data/vendor/assets/vis/graph/img/addNodeIcon.png +0 -0
  29. data/vendor/assets/vis/graph/img/backIcon.png +0 -0
  30. data/vendor/assets/vis/graph/img/connectIcon.png +0 -0
  31. data/vendor/assets/vis/graph/img/cross.png +0 -0
  32. data/vendor/assets/vis/graph/img/cross2.png +0 -0
  33. data/vendor/assets/vis/graph/img/deleteIcon.png +0 -0
  34. data/vendor/assets/vis/graph/img/downArrow.png +0 -0
  35. data/vendor/assets/vis/graph/img/editIcon.png +0 -0
  36. data/vendor/assets/vis/graph/img/leftArrow.png +0 -0
  37. data/vendor/assets/vis/graph/img/minus.png +0 -0
  38. data/vendor/assets/vis/graph/img/plus.png +0 -0
  39. data/vendor/assets/vis/graph/img/rightArrow.png +0 -0
  40. data/vendor/assets/vis/graph/img/upArrow.png +0 -0
  41. data/vendor/assets/vis/graph/img/zoomExtends.png +0 -0
  42. data/vendor/assets/vis/graph/shapes.js +0 -225
  43. data/vendor/assets/vis/graph3d/Graph3d.js +0 -3306
  44. data/vendor/assets/vis/module/exports.js +0 -65
  45. data/vendor/assets/vis/module/header.js +0 -24
  46. data/vendor/assets/vis/module/imports.js +0 -31
  47. data/vendor/assets/vis/shim.js +0 -252
  48. data/vendor/assets/vis/timeline/Range.js +0 -532
  49. data/vendor/assets/vis/timeline/TimeStep.js +0 -466
  50. data/vendor/assets/vis/timeline/Timeline.js +0 -851
  51. data/vendor/assets/vis/timeline/component/Component.js +0 -52
  52. data/vendor/assets/vis/timeline/component/CurrentTime.js +0 -128
  53. data/vendor/assets/vis/timeline/component/CustomTime.js +0 -182
  54. data/vendor/assets/vis/timeline/component/Group.js +0 -470
  55. data/vendor/assets/vis/timeline/component/ItemSet.js +0 -1332
  56. data/vendor/assets/vis/timeline/component/TimeAxis.js +0 -389
  57. data/vendor/assets/vis/timeline/component/css/animation.css +0 -33
  58. data/vendor/assets/vis/timeline/component/css/currenttime.css +0 -5
  59. data/vendor/assets/vis/timeline/component/css/customtime.css +0 -6
  60. data/vendor/assets/vis/timeline/component/css/item.css +0 -107
  61. data/vendor/assets/vis/timeline/component/css/itemset.css +0 -33
  62. data/vendor/assets/vis/timeline/component/css/labelset.css +0 -36
  63. data/vendor/assets/vis/timeline/component/css/panel.css +0 -71
  64. data/vendor/assets/vis/timeline/component/css/timeaxis.css +0 -48
  65. data/vendor/assets/vis/timeline/component/css/timeline.css +0 -2
  66. data/vendor/assets/vis/timeline/component/item/Item.js +0 -139
  67. data/vendor/assets/vis/timeline/component/item/ItemBox.js +0 -230
  68. data/vendor/assets/vis/timeline/component/item/ItemPoint.js +0 -190
  69. data/vendor/assets/vis/timeline/component/item/ItemRange.js +0 -262
  70. data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +0 -57
  71. data/vendor/assets/vis/timeline/img/delete.png +0 -0
  72. data/vendor/assets/vis/timeline/stack.js +0 -112
  73. data/vendor/assets/vis/util.js +0 -990
@@ -1,66 +0,0 @@
1
- /**
2
- * Created by Alex on 2/10/14.
3
- */
4
-
5
- var repulsionMixin = {
6
-
7
-
8
- /**
9
- * Calculate the forces the nodes apply on eachother based on a repulsion field.
10
- * This field is linearly approximated.
11
- *
12
- * @private
13
- */
14
- _calculateNodeForces: function () {
15
- var dx, dy, angle, distance, fx, fy, combinedClusterSize,
16
- repulsingForce, node1, node2, i, j;
17
-
18
- var nodes = this.calculationNodes;
19
- var nodeIndices = this.calculationNodeIndices;
20
-
21
- // approximation constants
22
- var a_base = -2 / 3;
23
- var b = 4 / 3;
24
-
25
- // repulsing forces between nodes
26
- var nodeDistance = this.constants.physics.repulsion.nodeDistance;
27
- var minimumDistance = nodeDistance;
28
-
29
- // we loop from i over all but the last entree in the array
30
- // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
31
- for (i = 0; i < nodeIndices.length - 1; i++) {
32
- node1 = nodes[nodeIndices[i]];
33
- for (j = i + 1; j < nodeIndices.length; j++) {
34
- node2 = nodes[nodeIndices[j]];
35
- combinedClusterSize = node1.clusterSize + node2.clusterSize - 2;
36
-
37
- dx = node2.x - node1.x;
38
- dy = node2.y - node1.y;
39
- distance = Math.sqrt(dx * dx + dy * dy);
40
-
41
- minimumDistance = (combinedClusterSize == 0) ? nodeDistance : (nodeDistance * (1 + combinedClusterSize * this.constants.clustering.distanceAmplification));
42
- var a = a_base / minimumDistance;
43
- if (distance < 2 * minimumDistance) {
44
- if (distance < 0.5 * minimumDistance) {
45
- repulsingForce = 1.0;
46
- }
47
- else {
48
- repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness))
49
- }
50
-
51
- // amplify the repulsion for clusters.
52
- repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification;
53
- repulsingForce = repulsingForce / distance;
54
-
55
- fx = dx * repulsingForce;
56
- fy = dy * repulsingForce;
57
-
58
- node1.fx -= fx;
59
- node1.fy -= fy;
60
- node2.fx += fx;
61
- node2.fy += fy;
62
- }
63
- }
64
- }
65
- }
66
- };
@@ -1,225 +0,0 @@
1
- /**
2
- * Canvas shapes used by the Graph
3
- */
4
- if (typeof CanvasRenderingContext2D !== 'undefined') {
5
-
6
- /**
7
- * Draw a circle shape
8
- */
9
- CanvasRenderingContext2D.prototype.circle = function(x, y, r) {
10
- this.beginPath();
11
- this.arc(x, y, r, 0, 2*Math.PI, false);
12
- };
13
-
14
- /**
15
- * Draw a square shape
16
- * @param {Number} x horizontal center
17
- * @param {Number} y vertical center
18
- * @param {Number} r size, width and height of the square
19
- */
20
- CanvasRenderingContext2D.prototype.square = function(x, y, r) {
21
- this.beginPath();
22
- this.rect(x - r, y - r, r * 2, r * 2);
23
- };
24
-
25
- /**
26
- * Draw a triangle shape
27
- * @param {Number} x horizontal center
28
- * @param {Number} y vertical center
29
- * @param {Number} r radius, half the length of the sides of the triangle
30
- */
31
- CanvasRenderingContext2D.prototype.triangle = function(x, y, r) {
32
- // http://en.wikipedia.org/wiki/Equilateral_triangle
33
- this.beginPath();
34
-
35
- var s = r * 2;
36
- var s2 = s / 2;
37
- var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
38
- var h = Math.sqrt(s * s - s2 * s2); // height
39
-
40
- this.moveTo(x, y - (h - ir));
41
- this.lineTo(x + s2, y + ir);
42
- this.lineTo(x - s2, y + ir);
43
- this.lineTo(x, y - (h - ir));
44
- this.closePath();
45
- };
46
-
47
- /**
48
- * Draw a triangle shape in downward orientation
49
- * @param {Number} x horizontal center
50
- * @param {Number} y vertical center
51
- * @param {Number} r radius
52
- */
53
- CanvasRenderingContext2D.prototype.triangleDown = function(x, y, r) {
54
- // http://en.wikipedia.org/wiki/Equilateral_triangle
55
- this.beginPath();
56
-
57
- var s = r * 2;
58
- var s2 = s / 2;
59
- var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
60
- var h = Math.sqrt(s * s - s2 * s2); // height
61
-
62
- this.moveTo(x, y + (h - ir));
63
- this.lineTo(x + s2, y - ir);
64
- this.lineTo(x - s2, y - ir);
65
- this.lineTo(x, y + (h - ir));
66
- this.closePath();
67
- };
68
-
69
- /**
70
- * Draw a star shape, a star with 5 points
71
- * @param {Number} x horizontal center
72
- * @param {Number} y vertical center
73
- * @param {Number} r radius, half the length of the sides of the triangle
74
- */
75
- CanvasRenderingContext2D.prototype.star = function(x, y, r) {
76
- // http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/
77
- this.beginPath();
78
-
79
- for (var n = 0; n < 10; n++) {
80
- var radius = (n % 2 === 0) ? r * 1.3 : r * 0.5;
81
- this.lineTo(
82
- x + radius * Math.sin(n * 2 * Math.PI / 10),
83
- y - radius * Math.cos(n * 2 * Math.PI / 10)
84
- );
85
- }
86
-
87
- this.closePath();
88
- };
89
-
90
- /**
91
- * http://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas
92
- */
93
- CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
94
- var r2d = Math.PI/180;
95
- if( w - ( 2 * r ) < 0 ) { r = ( w / 2 ); } //ensure that the radius isn't too large for x
96
- if( h - ( 2 * r ) < 0 ) { r = ( h / 2 ); } //ensure that the radius isn't too large for y
97
- this.beginPath();
98
- this.moveTo(x+r,y);
99
- this.lineTo(x+w-r,y);
100
- this.arc(x+w-r,y+r,r,r2d*270,r2d*360,false);
101
- this.lineTo(x+w,y+h-r);
102
- this.arc(x+w-r,y+h-r,r,0,r2d*90,false);
103
- this.lineTo(x+r,y+h);
104
- this.arc(x+r,y+h-r,r,r2d*90,r2d*180,false);
105
- this.lineTo(x,y+r);
106
- this.arc(x+r,y+r,r,r2d*180,r2d*270,false);
107
- };
108
-
109
- /**
110
- * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
111
- */
112
- CanvasRenderingContext2D.prototype.ellipse = function(x, y, w, h) {
113
- var kappa = .5522848,
114
- ox = (w / 2) * kappa, // control point offset horizontal
115
- oy = (h / 2) * kappa, // control point offset vertical
116
- xe = x + w, // x-end
117
- ye = y + h, // y-end
118
- xm = x + w / 2, // x-middle
119
- ym = y + h / 2; // y-middle
120
-
121
- this.beginPath();
122
- this.moveTo(x, ym);
123
- this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
124
- this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
125
- this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
126
- this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
127
- };
128
-
129
-
130
-
131
- /**
132
- * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
133
- */
134
- CanvasRenderingContext2D.prototype.database = function(x, y, w, h) {
135
- var f = 1/3;
136
- var wEllipse = w;
137
- var hEllipse = h * f;
138
-
139
- var kappa = .5522848,
140
- ox = (wEllipse / 2) * kappa, // control point offset horizontal
141
- oy = (hEllipse / 2) * kappa, // control point offset vertical
142
- xe = x + wEllipse, // x-end
143
- ye = y + hEllipse, // y-end
144
- xm = x + wEllipse / 2, // x-middle
145
- ym = y + hEllipse / 2, // y-middle
146
- ymb = y + (h - hEllipse/2), // y-midlle, bottom ellipse
147
- yeb = y + h; // y-end, bottom ellipse
148
-
149
- this.beginPath();
150
- this.moveTo(xe, ym);
151
-
152
- this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
153
- this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
154
-
155
- this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
156
- this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
157
-
158
- this.lineTo(xe, ymb);
159
-
160
- this.bezierCurveTo(xe, ymb + oy, xm + ox, yeb, xm, yeb);
161
- this.bezierCurveTo(xm - ox, yeb, x, ymb + oy, x, ymb);
162
-
163
- this.lineTo(x, ym);
164
- };
165
-
166
-
167
- /**
168
- * Draw an arrow point (no line)
169
- */
170
- CanvasRenderingContext2D.prototype.arrow = function(x, y, angle, length) {
171
- // tail
172
- var xt = x - length * Math.cos(angle);
173
- var yt = y - length * Math.sin(angle);
174
-
175
- // inner tail
176
- // TODO: allow to customize different shapes
177
- var xi = x - length * 0.9 * Math.cos(angle);
178
- var yi = y - length * 0.9 * Math.sin(angle);
179
-
180
- // left
181
- var xl = xt + length / 3 * Math.cos(angle + 0.5 * Math.PI);
182
- var yl = yt + length / 3 * Math.sin(angle + 0.5 * Math.PI);
183
-
184
- // right
185
- var xr = xt + length / 3 * Math.cos(angle - 0.5 * Math.PI);
186
- var yr = yt + length / 3 * Math.sin(angle - 0.5 * Math.PI);
187
-
188
- this.beginPath();
189
- this.moveTo(x, y);
190
- this.lineTo(xl, yl);
191
- this.lineTo(xi, yi);
192
- this.lineTo(xr, yr);
193
- this.closePath();
194
- };
195
-
196
- /**
197
- * Sets up the dashedLine functionality for drawing
198
- * Original code came from http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
199
- * @author David Jordan
200
- * @date 2012-08-08
201
- */
202
- CanvasRenderingContext2D.prototype.dashedLine = function(x,y,x2,y2,dashArray){
203
- if (!dashArray) dashArray=[10,5];
204
- if (dashLength==0) dashLength = 0.001; // Hack for Safari
205
- var dashCount = dashArray.length;
206
- this.moveTo(x, y);
207
- var dx = (x2-x), dy = (y2-y);
208
- var slope = dy/dx;
209
- var distRemaining = Math.sqrt( dx*dx + dy*dy );
210
- var dashIndex=0, draw=true;
211
- while (distRemaining>=0.1){
212
- var dashLength = dashArray[dashIndex++%dashCount];
213
- if (dashLength > distRemaining) dashLength = distRemaining;
214
- var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) );
215
- if (dx<0) xStep = -xStep;
216
- x += xStep;
217
- y += slope*xStep;
218
- this[draw ? 'lineTo' : 'moveTo'](x,y);
219
- distRemaining -= dashLength;
220
- draw = !draw;
221
- }
222
- };
223
-
224
- // TODO: add diamond shape
225
- }
@@ -1,3306 +0,0 @@
1
- /**
2
- * @constructor Graph3d
3
- * The Graph is a visualization Graphs on a time line
4
- *
5
- * Graph is developed in javascript as a Google Visualization Chart.
6
- *
7
- * @param {Element} container The DOM element in which the Graph will
8
- * be created. Normally a div element.
9
- * @param {DataSet | DataView | Array} [data]
10
- * @param {Object} [options]
11
- */
12
- function Graph3d(container, data, options) {
13
- // create variables and set default values
14
- this.containerElement = container;
15
- this.width = '400px';
16
- this.height = '400px';
17
- this.margin = 10; // px
18
- this.defaultXCenter = '55%';
19
- this.defaultYCenter = '50%';
20
-
21
- this.xLabel = 'x';
22
- this.yLabel = 'y';
23
- this.zLabel = 'z';
24
- this.filterLabel = 'time';
25
- this.legendLabel = 'value';
26
-
27
- this.style = Graph3d.STYLE.DOT;
28
- this.showPerspective = true;
29
- this.showGrid = true;
30
- this.keepAspectRatio = true;
31
- this.showShadow = false;
32
- this.showGrayBottom = false; // TODO: this does not work correctly
33
- this.showTooltip = false;
34
- this.verticalRatio = 0.5; // 0.1 to 1.0, where 1.0 results in a 'cube'
35
-
36
- this.animationInterval = 1000; // milliseconds
37
- this.animationPreload = false;
38
-
39
- this.camera = new Graph3d.Camera();
40
- this.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window?
41
-
42
- this.dataTable = null; // The original data table
43
- this.dataPoints = null; // The table with point objects
44
-
45
- // the column indexes
46
- this.colX = undefined;
47
- this.colY = undefined;
48
- this.colZ = undefined;
49
- this.colValue = undefined;
50
- this.colFilter = undefined;
51
-
52
- this.xMin = 0;
53
- this.xStep = undefined; // auto by default
54
- this.xMax = 1;
55
- this.yMin = 0;
56
- this.yStep = undefined; // auto by default
57
- this.yMax = 1;
58
- this.zMin = 0;
59
- this.zStep = undefined; // auto by default
60
- this.zMax = 1;
61
- this.valueMin = 0;
62
- this.valueMax = 1;
63
- this.xBarWidth = 1;
64
- this.yBarWidth = 1;
65
- // TODO: customize axis range
66
-
67
- // constants
68
- this.colorAxis = '#4D4D4D';
69
- this.colorGrid = '#D3D3D3';
70
- this.colorDot = '#7DC1FF';
71
- this.colorDotBorder = '#3267D2';
72
-
73
- // create a frame and canvas
74
- this.create();
75
-
76
- // apply options (also when undefined)
77
- this.setOptions(options);
78
-
79
- // apply data
80
- if (data) {
81
- this.setData(data);
82
- }
83
- }
84
-
85
- // Extend Graph with an Emitter mixin
86
- Emitter(Graph3d.prototype);
87
-
88
- /**
89
- * @class Camera
90
- * The camera is mounted on a (virtual) camera arm. The camera arm can rotate
91
- * The camera is always looking in the direction of the origin of the arm.
92
- * This way, the camera always rotates around one fixed point, the location
93
- * of the camera arm.
94
- *
95
- * Documentation:
96
- * http://en.wikipedia.org/wiki/3D_projection
97
- */
98
- Graph3d.Camera = function () {
99
- this.armLocation = new Point3d();
100
- this.armRotation = {};
101
- this.armRotation.horizontal = 0;
102
- this.armRotation.vertical = 0;
103
- this.armLength = 1.7;
104
-
105
- this.cameraLocation = new Point3d();
106
- this.cameraRotation = new Point3d(0.5*Math.PI, 0, 0);
107
-
108
- this.calculateCameraOrientation();
109
- };
110
-
111
- /**
112
- * Set the location (origin) of the arm
113
- * @param {Number} x Normalized value of x
114
- * @param {Number} y Normalized value of y
115
- * @param {Number} z Normalized value of z
116
- */
117
- Graph3d.Camera.prototype.setArmLocation = function(x, y, z) {
118
- this.armLocation.x = x;
119
- this.armLocation.y = y;
120
- this.armLocation.z = z;
121
-
122
- this.calculateCameraOrientation();
123
- };
124
-
125
- /**
126
- * Set the rotation of the camera arm
127
- * @param {Number} horizontal The horizontal rotation, between 0 and 2*PI.
128
- * Optional, can be left undefined.
129
- * @param {Number} vertical The vertical rotation, between 0 and 0.5*PI
130
- * if vertical=0.5*PI, the graph is shown from the
131
- * top. Optional, can be left undefined.
132
- */
133
- Graph3d.Camera.prototype.setArmRotation = function(horizontal, vertical) {
134
- if (horizontal !== undefined) {
135
- this.armRotation.horizontal = horizontal;
136
- }
137
-
138
- if (vertical !== undefined) {
139
- this.armRotation.vertical = vertical;
140
- if (this.armRotation.vertical < 0) this.armRotation.vertical = 0;
141
- if (this.armRotation.vertical > 0.5*Math.PI) this.armRotation.vertical = 0.5*Math.PI;
142
- }
143
-
144
- if (horizontal !== undefined || vertical !== undefined) {
145
- this.calculateCameraOrientation();
146
- }
147
- };
148
-
149
- /**
150
- * Retrieve the current arm rotation
151
- * @return {object} An object with parameters horizontal and vertical
152
- */
153
- Graph3d.Camera.prototype.getArmRotation = function() {
154
- var rot = {};
155
- rot.horizontal = this.armRotation.horizontal;
156
- rot.vertical = this.armRotation.vertical;
157
-
158
- return rot;
159
- };
160
-
161
- /**
162
- * Set the (normalized) length of the camera arm.
163
- * @param {Number} length A length between 0.71 and 5.0
164
- */
165
- Graph3d.Camera.prototype.setArmLength = function(length) {
166
- if (length === undefined)
167
- return;
168
-
169
- this.armLength = length;
170
-
171
- // Radius must be larger than the corner of the graph,
172
- // which has a distance of sqrt(0.5^2+0.5^2) = 0.71 from the center of the
173
- // graph
174
- if (this.armLength < 0.71) this.armLength = 0.71;
175
- if (this.armLength > 5.0) this.armLength = 5.0;
176
-
177
- this.calculateCameraOrientation();
178
- };
179
-
180
- /**
181
- * Retrieve the arm length
182
- * @return {Number} length
183
- */
184
- Graph3d.Camera.prototype.getArmLength = function() {
185
- return this.armLength;
186
- };
187
-
188
- /**
189
- * Retrieve the camera location
190
- * @return {Point3d} cameraLocation
191
- */
192
- Graph3d.Camera.prototype.getCameraLocation = function() {
193
- return this.cameraLocation;
194
- };
195
-
196
- /**
197
- * Retrieve the camera rotation
198
- * @return {Point3d} cameraRotation
199
- */
200
- Graph3d.Camera.prototype.getCameraRotation = function() {
201
- return this.cameraRotation;
202
- };
203
-
204
- /**
205
- * Calculate the location and rotation of the camera based on the
206
- * position and orientation of the camera arm
207
- */
208
- Graph3d.Camera.prototype.calculateCameraOrientation = function() {
209
- // calculate location of the camera
210
- this.cameraLocation.x = this.armLocation.x - this.armLength * Math.sin(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical);
211
- this.cameraLocation.y = this.armLocation.y - this.armLength * Math.cos(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical);
212
- this.cameraLocation.z = this.armLocation.z + this.armLength * Math.sin(this.armRotation.vertical);
213
-
214
- // calculate rotation of the camera
215
- this.cameraRotation.x = Math.PI/2 - this.armRotation.vertical;
216
- this.cameraRotation.y = 0;
217
- this.cameraRotation.z = -this.armRotation.horizontal;
218
- };
219
-
220
- /**
221
- * Calculate the scaling values, dependent on the range in x, y, and z direction
222
- */
223
- Graph3d.prototype._setScale = function() {
224
- this.scale = new Point3d(1 / (this.xMax - this.xMin),
225
- 1 / (this.yMax - this.yMin),
226
- 1 / (this.zMax - this.zMin));
227
-
228
- // keep aspect ration between x and y scale if desired
229
- if (this.keepAspectRatio) {
230
- if (this.scale.x < this.scale.y) {
231
- //noinspection JSSuspiciousNameCombination
232
- this.scale.y = this.scale.x;
233
- }
234
- else {
235
- //noinspection JSSuspiciousNameCombination
236
- this.scale.x = this.scale.y;
237
- }
238
- }
239
-
240
- // scale the vertical axis
241
- this.scale.z *= this.verticalRatio;
242
- // TODO: can this be automated? verticalRatio?
243
-
244
- // determine scale for (optional) value
245
- this.scale.value = 1 / (this.valueMax - this.valueMin);
246
-
247
- // position the camera arm
248
- var xCenter = (this.xMax + this.xMin) / 2 * this.scale.x;
249
- var yCenter = (this.yMax + this.yMin) / 2 * this.scale.y;
250
- var zCenter = (this.zMax + this.zMin) / 2 * this.scale.z;
251
- this.camera.setArmLocation(xCenter, yCenter, zCenter);
252
- };
253
-
254
-
255
- /**
256
- * Convert a 3D location to a 2D location on screen
257
- * http://en.wikipedia.org/wiki/3D_projection
258
- * @param {Point3d} point3d A 3D point with parameters x, y, z
259
- * @return {Point2d} point2d A 2D point with parameters x, y
260
- */
261
- Graph3d.prototype._convert3Dto2D = function(point3d) {
262
- var translation = this._convertPointToTranslation(point3d);
263
- return this._convertTranslationToScreen(translation);
264
- };
265
-
266
- /**
267
- * Convert a 3D location its translation seen from the camera
268
- * http://en.wikipedia.org/wiki/3D_projection
269
- * @param {Point3d} point3d A 3D point with parameters x, y, z
270
- * @return {Point3d} translation A 3D point with parameters x, y, z This is
271
- * the translation of the point, seen from the
272
- * camera
273
- */
274
- Graph3d.prototype._convertPointToTranslation = function(point3d) {
275
- var ax = point3d.x * this.scale.x,
276
- ay = point3d.y * this.scale.y,
277
- az = point3d.z * this.scale.z,
278
-
279
- cx = this.camera.getCameraLocation().x,
280
- cy = this.camera.getCameraLocation().y,
281
- cz = this.camera.getCameraLocation().z,
282
-
283
- // calculate angles
284
- sinTx = Math.sin(this.camera.getCameraRotation().x),
285
- cosTx = Math.cos(this.camera.getCameraRotation().x),
286
- sinTy = Math.sin(this.camera.getCameraRotation().y),
287
- cosTy = Math.cos(this.camera.getCameraRotation().y),
288
- sinTz = Math.sin(this.camera.getCameraRotation().z),
289
- cosTz = Math.cos(this.camera.getCameraRotation().z),
290
-
291
- // calculate translation
292
- dx = cosTy * (sinTz * (ay - cy) + cosTz * (ax - cx)) - sinTy * (az - cz),
293
- dy = sinTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) + cosTx * (cosTz * (ay - cy) - sinTz * (ax-cx)),
294
- dz = cosTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) - sinTx * (cosTz * (ay - cy) - sinTz * (ax-cx));
295
-
296
- return new Point3d(dx, dy, dz);
297
- };
298
-
299
- /**
300
- * Convert a translation point to a point on the screen
301
- * @param {Point3d} translation A 3D point with parameters x, y, z This is
302
- * the translation of the point, seen from the
303
- * camera
304
- * @return {Point2d} point2d A 2D point with parameters x, y
305
- */
306
- Graph3d.prototype._convertTranslationToScreen = function(translation) {
307
- var ex = this.eye.x,
308
- ey = this.eye.y,
309
- ez = this.eye.z,
310
- dx = translation.x,
311
- dy = translation.y,
312
- dz = translation.z;
313
-
314
- // calculate position on screen from translation
315
- var bx;
316
- var by;
317
- if (this.showPerspective) {
318
- bx = (dx - ex) * (ez / dz);
319
- by = (dy - ey) * (ez / dz);
320
- }
321
- else {
322
- bx = dx * -(ez / this.camera.getArmLength());
323
- by = dy * -(ez / this.camera.getArmLength());
324
- }
325
-
326
- // shift and scale the point to the center of the screen
327
- // use the width of the graph to scale both horizontally and vertically.
328
- return new Point2d(
329
- this.xcenter + bx * this.frame.canvas.clientWidth,
330
- this.ycenter - by * this.frame.canvas.clientWidth);
331
- };
332
-
333
- /**
334
- * Set the background styling for the graph
335
- * @param {string | {fill: string, stroke: string, strokeWidth: string}} backgroundColor
336
- */
337
- Graph3d.prototype._setBackgroundColor = function(backgroundColor) {
338
- var fill = 'white';
339
- var stroke = 'gray';
340
- var strokeWidth = 1;
341
-
342
- if (typeof(backgroundColor) === 'string') {
343
- fill = backgroundColor;
344
- stroke = 'none';
345
- strokeWidth = 0;
346
- }
347
- else if (typeof(backgroundColor) === 'object') {
348
- if (backgroundColor.fill !== undefined) fill = backgroundColor.fill;
349
- if (backgroundColor.stroke !== undefined) stroke = backgroundColor.stroke;
350
- if (backgroundColor.strokeWidth !== undefined) strokeWidth = backgroundColor.strokeWidth;
351
- }
352
- else if (backgroundColor === undefined) {
353
- // use use defaults
354
- }
355
- else {
356
- throw 'Unsupported type of backgroundColor';
357
- }
358
-
359
- this.frame.style.backgroundColor = fill;
360
- this.frame.style.borderColor = stroke;
361
- this.frame.style.borderWidth = strokeWidth + 'px';
362
- this.frame.style.borderStyle = 'solid';
363
- };
364
-
365
-
366
- /// enumerate the available styles
367
- Graph3d.STYLE = {
368
- BAR: 0,
369
- BARCOLOR: 1,
370
- BARSIZE: 2,
371
- DOT : 3,
372
- DOTLINE : 4,
373
- DOTCOLOR: 5,
374
- DOTSIZE: 6,
375
- GRID : 7,
376
- LINE: 8,
377
- SURFACE : 9
378
- };
379
-
380
- /**
381
- * Retrieve the style index from given styleName
382
- * @param {string} styleName Style name such as 'dot', 'grid', 'dot-line'
383
- * @return {Number} styleNumber Enumeration value representing the style, or -1
384
- * when not found
385
- */
386
- Graph3d.prototype._getStyleNumber = function(styleName) {
387
- switch (styleName) {
388
- case 'dot': return Graph3d.STYLE.DOT;
389
- case 'dot-line': return Graph3d.STYLE.DOTLINE;
390
- case 'dot-color': return Graph3d.STYLE.DOTCOLOR;
391
- case 'dot-size': return Graph3d.STYLE.DOTSIZE;
392
- case 'line': return Graph3d.STYLE.LINE;
393
- case 'grid': return Graph3d.STYLE.GRID;
394
- case 'surface': return Graph3d.STYLE.SURFACE;
395
- case 'bar': return Graph3d.STYLE.BAR;
396
- case 'bar-color': return Graph3d.STYLE.BARCOLOR;
397
- case 'bar-size': return Graph3d.STYLE.BARSIZE;
398
- }
399
-
400
- return -1;
401
- };
402
-
403
- /**
404
- * Determine the indexes of the data columns, based on the given style and data
405
- * @param {DataSet} data
406
- * @param {Number} style
407
- */
408
- Graph3d.prototype._determineColumnIndexes = function(data, style) {
409
- if (this.style === Graph3d.STYLE.DOT ||
410
- this.style === Graph3d.STYLE.DOTLINE ||
411
- this.style === Graph3d.STYLE.LINE ||
412
- this.style === Graph3d.STYLE.GRID ||
413
- this.style === Graph3d.STYLE.SURFACE ||
414
- this.style === Graph3d.STYLE.BAR) {
415
- // 3 columns expected, and optionally a 4th with filter values
416
- this.colX = 0;
417
- this.colY = 1;
418
- this.colZ = 2;
419
- this.colValue = undefined;
420
-
421
- if (data.getNumberOfColumns() > 3) {
422
- this.colFilter = 3;
423
- }
424
- }
425
- else if (this.style === Graph3d.STYLE.DOTCOLOR ||
426
- this.style === Graph3d.STYLE.DOTSIZE ||
427
- this.style === Graph3d.STYLE.BARCOLOR ||
428
- this.style === Graph3d.STYLE.BARSIZE) {
429
- // 4 columns expected, and optionally a 5th with filter values
430
- this.colX = 0;
431
- this.colY = 1;
432
- this.colZ = 2;
433
- this.colValue = 3;
434
-
435
- if (data.getNumberOfColumns() > 4) {
436
- this.colFilter = 4;
437
- }
438
- }
439
- else {
440
- throw 'Unknown style "' + this.style + '"';
441
- }
442
- };
443
-
444
- Graph3d.prototype.getNumberOfRows = function(data) {
445
- return data.length;
446
- }
447
-
448
-
449
- Graph3d.prototype.getNumberOfColumns = function(data) {
450
- var counter = 0;
451
- for (var column in data[0]) {
452
- if (data[0].hasOwnProperty(column)) {
453
- counter++;
454
- }
455
- }
456
- return counter;
457
- }
458
-
459
-
460
- Graph3d.prototype.getDistinctValues = function(data, column) {
461
- var distinctValues = [];
462
- for (var i = 0; i < data.length; i++) {
463
- if (distinctValues.indexOf(data[i][column]) == -1) {
464
- distinctValues.push(data[i][column]);
465
- }
466
- }
467
- return distinctValues;
468
- }
469
-
470
-
471
- Graph3d.prototype.getColumnRange = function(data,column) {
472
- var minMax = {min:data[0][column],max:data[0][column]};
473
- for (var i = 0; i < data.length; i++) {
474
- if (minMax.min > data[i][column]) { minMax.min = data[i][column]; }
475
- if (minMax.max < data[i][column]) { minMax.max = data[i][column]; }
476
- }
477
- return minMax;
478
- };
479
-
480
- /**
481
- * Initialize the data from the data table. Calculate minimum and maximum values
482
- * and column index values
483
- * @param {Array | DataSet | DataView} rawData The data containing the items for the Graph.
484
- * @param {Number} style Style Number
485
- */
486
- Graph3d.prototype._dataInitialize = function (rawData, style) {
487
- var me = this;
488
-
489
- // unsubscribe from the dataTable
490
- if (this.dataSet) {
491
- this.dataSet.off('*', this._onChange);
492
- }
493
-
494
- if (rawData === undefined)
495
- return;
496
-
497
- if (Array.isArray(rawData)) {
498
- rawData = new DataSet(rawData);
499
- }
500
-
501
- var data;
502
- if (rawData instanceof DataSet || rawData instanceof DataView) {
503
- data = rawData.get();
504
- }
505
- else {
506
- throw new Error('Array, DataSet, or DataView expected');
507
- }
508
-
509
- if (data.length == 0)
510
- return;
511
-
512
- this.dataSet = rawData;
513
- this.dataTable = data;
514
-
515
- // subscribe to changes in the dataset
516
- this._onChange = function () {
517
- me.setData(me.dataSet);
518
- };
519
- this.dataSet.on('*', this._onChange);
520
-
521
- // _determineColumnIndexes
522
- // getNumberOfRows (points)
523
- // getNumberOfColumns (x,y,z,v,t,t1,t2...)
524
- // getDistinctValues (unique values?)
525
- // getColumnRange
526
-
527
- // determine the location of x,y,z,value,filter columns
528
- this.colX = 'x';
529
- this.colY = 'y';
530
- this.colZ = 'z';
531
- this.colValue = 'style';
532
- this.colFilter = 'filter';
533
-
534
-
535
-
536
- // check if a filter column is provided
537
- if (data[0].hasOwnProperty('filter')) {
538
- if (this.dataFilter === undefined) {
539
- this.dataFilter = new Filter(rawData, this.colFilter, this);
540
- this.dataFilter.setOnLoadCallback(function() {me.redraw();});
541
- }
542
- }
543
-
544
-
545
- var withBars = this.style == Graph3d.STYLE.BAR ||
546
- this.style == Graph3d.STYLE.BARCOLOR ||
547
- this.style == Graph3d.STYLE.BARSIZE;
548
-
549
- // determine barWidth from data
550
- if (withBars) {
551
- if (this.defaultXBarWidth !== undefined) {
552
- this.xBarWidth = this.defaultXBarWidth;
553
- }
554
- else {
555
- var dataX = this.getDistinctValues(data,this.colX);
556
- this.xBarWidth = (dataX[1] - dataX[0]) || 1;
557
- }
558
-
559
- if (this.defaultYBarWidth !== undefined) {
560
- this.yBarWidth = this.defaultYBarWidth;
561
- }
562
- else {
563
- var dataY = this.getDistinctValues(data,this.colY);
564
- this.yBarWidth = (dataY[1] - dataY[0]) || 1;
565
- }
566
- }
567
-
568
- // calculate minimums and maximums
569
- var xRange = this.getColumnRange(data,this.colX);
570
- if (withBars) {
571
- xRange.min -= this.xBarWidth / 2;
572
- xRange.max += this.xBarWidth / 2;
573
- }
574
- this.xMin = (this.defaultXMin !== undefined) ? this.defaultXMin : xRange.min;
575
- this.xMax = (this.defaultXMax !== undefined) ? this.defaultXMax : xRange.max;
576
- if (this.xMax <= this.xMin) this.xMax = this.xMin + 1;
577
- this.xStep = (this.defaultXStep !== undefined) ? this.defaultXStep : (this.xMax-this.xMin)/5;
578
-
579
- var yRange = this.getColumnRange(data,this.colY);
580
- if (withBars) {
581
- yRange.min -= this.yBarWidth / 2;
582
- yRange.max += this.yBarWidth / 2;
583
- }
584
- this.yMin = (this.defaultYMin !== undefined) ? this.defaultYMin : yRange.min;
585
- this.yMax = (this.defaultYMax !== undefined) ? this.defaultYMax : yRange.max;
586
- if (this.yMax <= this.yMin) this.yMax = this.yMin + 1;
587
- this.yStep = (this.defaultYStep !== undefined) ? this.defaultYStep : (this.yMax-this.yMin)/5;
588
-
589
- var zRange = this.getColumnRange(data,this.colZ);
590
- this.zMin = (this.defaultZMin !== undefined) ? this.defaultZMin : zRange.min;
591
- this.zMax = (this.defaultZMax !== undefined) ? this.defaultZMax : zRange.max;
592
- if (this.zMax <= this.zMin) this.zMax = this.zMin + 1;
593
- this.zStep = (this.defaultZStep !== undefined) ? this.defaultZStep : (this.zMax-this.zMin)/5;
594
-
595
- if (this.colValue !== undefined) {
596
- var valueRange = this.getColumnRange(data,this.colValue);
597
- this.valueMin = (this.defaultValueMin !== undefined) ? this.defaultValueMin : valueRange.min;
598
- this.valueMax = (this.defaultValueMax !== undefined) ? this.defaultValueMax : valueRange.max;
599
- if (this.valueMax <= this.valueMin) this.valueMax = this.valueMin + 1;
600
- }
601
-
602
- // set the scale dependent on the ranges.
603
- this._setScale();
604
- };
605
-
606
-
607
-
608
- /**
609
- * Filter the data based on the current filter
610
- * @param {Array} data
611
- * @return {Array} dataPoints Array with point objects which can be drawn on screen
612
- */
613
- Graph3d.prototype._getDataPoints = function (data) {
614
- // TODO: store the created matrix dataPoints in the filters instead of reloading each time
615
- var x, y, i, z, obj, point;
616
-
617
- var dataPoints = [];
618
-
619
- if (this.style === Graph3d.STYLE.GRID ||
620
- this.style === Graph3d.STYLE.SURFACE) {
621
- // copy all values from the google data table to a matrix
622
- // the provided values are supposed to form a grid of (x,y) positions
623
-
624
- // create two lists with all present x and y values
625
- var dataX = [];
626
- var dataY = [];
627
- for (i = 0; i < this.getNumberOfRows(data); i++) {
628
- x = data[i][this.colX] || 0;
629
- y = data[i][this.colY] || 0;
630
-
631
- if (dataX.indexOf(x) === -1) {
632
- dataX.push(x);
633
- }
634
- if (dataY.indexOf(y) === -1) {
635
- dataY.push(y);
636
- }
637
- }
638
-
639
- function sortNumber(a, b) {
640
- return a - b;
641
- }
642
- dataX.sort(sortNumber);
643
- dataY.sort(sortNumber);
644
-
645
- // create a grid, a 2d matrix, with all values.
646
- var dataMatrix = []; // temporary data matrix
647
- for (i = 0; i < data.length; i++) {
648
- x = data[i][this.colX] || 0;
649
- y = data[i][this.colY] || 0;
650
- z = data[i][this.colZ] || 0;
651
-
652
- var xIndex = dataX.indexOf(x); // TODO: implement Array().indexOf() for Internet Explorer
653
- var yIndex = dataY.indexOf(y);
654
-
655
- if (dataMatrix[xIndex] === undefined) {
656
- dataMatrix[xIndex] = [];
657
- }
658
-
659
- var point3d = new Point3d();
660
- point3d.x = x;
661
- point3d.y = y;
662
- point3d.z = z;
663
-
664
- obj = {};
665
- obj.point = point3d;
666
- obj.trans = undefined;
667
- obj.screen = undefined;
668
- obj.bottom = new Point3d(x, y, this.zMin);
669
-
670
- dataMatrix[xIndex][yIndex] = obj;
671
-
672
- dataPoints.push(obj);
673
- }
674
-
675
- // fill in the pointers to the neighbors.
676
- for (x = 0; x < dataMatrix.length; x++) {
677
- for (y = 0; y < dataMatrix[x].length; y++) {
678
- if (dataMatrix[x][y]) {
679
- dataMatrix[x][y].pointRight = (x < dataMatrix.length-1) ? dataMatrix[x+1][y] : undefined;
680
- dataMatrix[x][y].pointTop = (y < dataMatrix[x].length-1) ? dataMatrix[x][y+1] : undefined;
681
- dataMatrix[x][y].pointCross =
682
- (x < dataMatrix.length-1 && y < dataMatrix[x].length-1) ?
683
- dataMatrix[x+1][y+1] :
684
- undefined;
685
- }
686
- }
687
- }
688
- }
689
- else { // 'dot', 'dot-line', etc.
690
- // copy all values from the google data table to a list with Point3d objects
691
- for (i = 0; i < data.length; i++) {
692
- point = new Point3d();
693
- point.x = data[i][this.colX] || 0;
694
- point.y = data[i][this.colY] || 0;
695
- point.z = data[i][this.colZ] || 0;
696
-
697
- if (this.colValue !== undefined) {
698
- point.value = data[i][this.colValue] || 0;
699
- }
700
-
701
- obj = {};
702
- obj.point = point;
703
- obj.bottom = new Point3d(point.x, point.y, this.zMin);
704
- obj.trans = undefined;
705
- obj.screen = undefined;
706
-
707
- dataPoints.push(obj);
708
- }
709
- }
710
-
711
- return dataPoints;
712
- };
713
-
714
-
715
-
716
-
717
- /**
718
- * Append suffix 'px' to provided value x
719
- * @param {int} x An integer value
720
- * @return {string} the string value of x, followed by the suffix 'px'
721
- */
722
- Graph3d.px = function(x) {
723
- return x + 'px';
724
- };
725
-
726
-
727
- /**
728
- * Create the main frame for the Graph3d.
729
- * This function is executed once when a Graph3d object is created. The frame
730
- * contains a canvas, and this canvas contains all objects like the axis and
731
- * nodes.
732
- */
733
- Graph3d.prototype.create = function () {
734
- // remove all elements from the container element.
735
- while (this.containerElement.hasChildNodes()) {
736
- this.containerElement.removeChild(this.containerElement.firstChild);
737
- }
738
-
739
- this.frame = document.createElement('div');
740
- this.frame.style.position = 'relative';
741
- this.frame.style.overflow = 'hidden';
742
-
743
- // create the graph canvas (HTML canvas element)
744
- this.frame.canvas = document.createElement( 'canvas' );
745
- this.frame.canvas.style.position = 'relative';
746
- this.frame.appendChild(this.frame.canvas);
747
- //if (!this.frame.canvas.getContext) {
748
- {
749
- var noCanvas = document.createElement( 'DIV' );
750
- noCanvas.style.color = 'red';
751
- noCanvas.style.fontWeight = 'bold' ;
752
- noCanvas.style.padding = '10px';
753
- noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
754
- this.frame.canvas.appendChild(noCanvas);
755
- }
756
-
757
- this.frame.filter = document.createElement( 'div' );
758
- this.frame.filter.style.position = 'absolute';
759
- this.frame.filter.style.bottom = '0px';
760
- this.frame.filter.style.left = '0px';
761
- this.frame.filter.style.width = '100%';
762
- this.frame.appendChild(this.frame.filter);
763
-
764
- // add event listeners to handle moving and zooming the contents
765
- var me = this;
766
- var onmousedown = function (event) {me._onMouseDown(event);};
767
- var ontouchstart = function (event) {me._onTouchStart(event);};
768
- var onmousewheel = function (event) {me._onWheel(event);};
769
- var ontooltip = function (event) {me._onTooltip(event);};
770
- // TODO: these events are never cleaned up... can give a 'memory leakage'
771
-
772
- G3DaddEventListener(this.frame.canvas, 'keydown', onkeydown);
773
- G3DaddEventListener(this.frame.canvas, 'mousedown', onmousedown);
774
- G3DaddEventListener(this.frame.canvas, 'touchstart', ontouchstart);
775
- G3DaddEventListener(this.frame.canvas, 'mousewheel', onmousewheel);
776
- G3DaddEventListener(this.frame.canvas, 'mousemove', ontooltip);
777
-
778
- // add the new graph to the container element
779
- this.containerElement.appendChild(this.frame);
780
- };
781
-
782
-
783
- /**
784
- * Set a new size for the graph
785
- * @param {string} width Width in pixels or percentage (for example '800px'
786
- * or '50%')
787
- * @param {string} height Height in pixels or percentage (for example '400px'
788
- * or '30%')
789
- */
790
- Graph3d.prototype.setSize = function(width, height) {
791
- this.frame.style.width = width;
792
- this.frame.style.height = height;
793
-
794
- this._resizeCanvas();
795
- };
796
-
797
- /**
798
- * Resize the canvas to the current size of the frame
799
- */
800
- Graph3d.prototype._resizeCanvas = function() {
801
- this.frame.canvas.style.width = '100%';
802
- this.frame.canvas.style.height = '100%';
803
-
804
- this.frame.canvas.width = this.frame.canvas.clientWidth;
805
- this.frame.canvas.height = this.frame.canvas.clientHeight;
806
-
807
- // adjust with for margin
808
- this.frame.filter.style.width = (this.frame.canvas.clientWidth - 2 * 10) + 'px';
809
- };
810
-
811
- /**
812
- * Start animation
813
- */
814
- Graph3d.prototype.animationStart = function() {
815
- if (!this.frame.filter || !this.frame.filter.slider)
816
- throw 'No animation available';
817
-
818
- this.frame.filter.slider.play();
819
- };
820
-
821
-
822
- /**
823
- * Stop animation
824
- */
825
- Graph3d.prototype.animationStop = function() {
826
- if (!this.frame.filter || !this.frame.filter.slider) return;
827
-
828
- this.frame.filter.slider.stop();
829
- };
830
-
831
-
832
- /**
833
- * Resize the center position based on the current values in this.defaultXCenter
834
- * and this.defaultYCenter (which are strings with a percentage or a value
835
- * in pixels). The center positions are the variables this.xCenter
836
- * and this.yCenter
837
- */
838
- Graph3d.prototype._resizeCenter = function() {
839
- // calculate the horizontal center position
840
- if (this.defaultXCenter.charAt(this.defaultXCenter.length-1) === '%') {
841
- this.xcenter =
842
- parseFloat(this.defaultXCenter) / 100 *
843
- this.frame.canvas.clientWidth;
844
- }
845
- else {
846
- this.xcenter = parseFloat(this.defaultXCenter); // supposed to be in px
847
- }
848
-
849
- // calculate the vertical center position
850
- if (this.defaultYCenter.charAt(this.defaultYCenter.length-1) === '%') {
851
- this.ycenter =
852
- parseFloat(this.defaultYCenter) / 100 *
853
- (this.frame.canvas.clientHeight - this.frame.filter.clientHeight);
854
- }
855
- else {
856
- this.ycenter = parseFloat(this.defaultYCenter); // supposed to be in px
857
- }
858
- };
859
-
860
- /**
861
- * Set the rotation and distance of the camera
862
- * @param {Object} pos An object with the camera position. The object
863
- * contains three parameters:
864
- * - horizontal {Number}
865
- * The horizontal rotation, between 0 and 2*PI.
866
- * Optional, can be left undefined.
867
- * - vertical {Number}
868
- * The vertical rotation, between 0 and 0.5*PI
869
- * if vertical=0.5*PI, the graph is shown from the
870
- * top. Optional, can be left undefined.
871
- * - distance {Number}
872
- * The (normalized) distance of the camera to the
873
- * center of the graph, a value between 0.71 and 5.0.
874
- * Optional, can be left undefined.
875
- */
876
- Graph3d.prototype.setCameraPosition = function(pos) {
877
- if (pos === undefined) {
878
- return;
879
- }
880
-
881
- if (pos.horizontal !== undefined && pos.vertical !== undefined) {
882
- this.camera.setArmRotation(pos.horizontal, pos.vertical);
883
- }
884
-
885
- if (pos.distance !== undefined) {
886
- this.camera.setArmLength(pos.distance);
887
- }
888
-
889
- this.redraw();
890
- };
891
-
892
-
893
- /**
894
- * Retrieve the current camera rotation
895
- * @return {object} An object with parameters horizontal, vertical, and
896
- * distance
897
- */
898
- Graph3d.prototype.getCameraPosition = function() {
899
- var pos = this.camera.getArmRotation();
900
- pos.distance = this.camera.getArmLength();
901
- return pos;
902
- };
903
-
904
- /**
905
- * Load data into the 3D Graph
906
- */
907
- Graph3d.prototype._readData = function(data) {
908
- // read the data
909
- this._dataInitialize(data, this.style);
910
-
911
-
912
- if (this.dataFilter) {
913
- // apply filtering
914
- this.dataPoints = this.dataFilter._getDataPoints();
915
- }
916
- else {
917
- // no filtering. load all data
918
- this.dataPoints = this._getDataPoints(this.dataTable);
919
- }
920
-
921
- // draw the filter
922
- this._redrawFilter();
923
- };
924
-
925
- /**
926
- * Replace the dataset of the Graph3d
927
- * @param {Array | DataSet | DataView} data
928
- */
929
- Graph3d.prototype.setData = function (data) {
930
- this._readData(data);
931
- this.redraw();
932
-
933
- // start animation when option is true
934
- if (this.animationAutoStart && this.dataFilter) {
935
- this.animationStart();
936
- }
937
- };
938
-
939
- /**
940
- * Update the options. Options will be merged with current options
941
- * @param {Object} options
942
- */
943
- Graph3d.prototype.setOptions = function (options) {
944
- var cameraPosition = undefined;
945
-
946
- this.animationStop();
947
-
948
- if (options !== undefined) {
949
- // retrieve parameter values
950
- if (options.width !== undefined) this.width = options.width;
951
- if (options.height !== undefined) this.height = options.height;
952
-
953
- if (options.xCenter !== undefined) this.defaultXCenter = options.xCenter;
954
- if (options.yCenter !== undefined) this.defaultYCenter = options.yCenter;
955
-
956
- if (options.filterLabel !== undefined) this.filterLabel = options.filterLabel;
957
- if (options.legendLabel !== undefined) this.legendLabel = options.legendLabel;
958
- if (options.xLabel !== undefined) this.xLabel = options.xLabel;
959
- if (options.yLabel !== undefined) this.yLabel = options.yLabel;
960
- if (options.zLabel !== undefined) this.zLabel = options.zLabel;
961
-
962
- if (options.style !== undefined) {
963
- var styleNumber = this._getStyleNumber(options.style);
964
- if (styleNumber !== -1) {
965
- this.style = styleNumber;
966
- }
967
- }
968
- if (options.showGrid !== undefined) this.showGrid = options.showGrid;
969
- if (options.showPerspective !== undefined) this.showPerspective = options.showPerspective;
970
- if (options.showShadow !== undefined) this.showShadow = options.showShadow;
971
- if (options.tooltip !== undefined) this.showTooltip = options.tooltip;
972
- if (options.showAnimationControls !== undefined) this.showAnimationControls = options.showAnimationControls;
973
- if (options.keepAspectRatio !== undefined) this.keepAspectRatio = options.keepAspectRatio;
974
- if (options.verticalRatio !== undefined) this.verticalRatio = options.verticalRatio;
975
-
976
- if (options.animationInterval !== undefined) this.animationInterval = options.animationInterval;
977
- if (options.animationPreload !== undefined) this.animationPreload = options.animationPreload;
978
- if (options.animationAutoStart !== undefined)this.animationAutoStart = options.animationAutoStart;
979
-
980
- if (options.xBarWidth !== undefined) this.defaultXBarWidth = options.xBarWidth;
981
- if (options.yBarWidth !== undefined) this.defaultYBarWidth = options.yBarWidth;
982
-
983
- if (options.xMin !== undefined) this.defaultXMin = options.xMin;
984
- if (options.xStep !== undefined) this.defaultXStep = options.xStep;
985
- if (options.xMax !== undefined) this.defaultXMax = options.xMax;
986
- if (options.yMin !== undefined) this.defaultYMin = options.yMin;
987
- if (options.yStep !== undefined) this.defaultYStep = options.yStep;
988
- if (options.yMax !== undefined) this.defaultYMax = options.yMax;
989
- if (options.zMin !== undefined) this.defaultZMin = options.zMin;
990
- if (options.zStep !== undefined) this.defaultZStep = options.zStep;
991
- if (options.zMax !== undefined) this.defaultZMax = options.zMax;
992
- if (options.valueMin !== undefined) this.defaultValueMin = options.valueMin;
993
- if (options.valueMax !== undefined) this.defaultValueMax = options.valueMax;
994
-
995
- if (options.cameraPosition !== undefined) cameraPosition = options.cameraPosition;
996
-
997
- if (cameraPosition !== undefined) {
998
- this.camera.setArmRotation(cameraPosition.horizontal, cameraPosition.vertical);
999
- this.camera.setArmLength(cameraPosition.distance);
1000
- }
1001
- else {
1002
- this.camera.setArmRotation(1.0, 0.5);
1003
- this.camera.setArmLength(1.7);
1004
- }
1005
- }
1006
-
1007
- this._setBackgroundColor(options && options.backgroundColor);
1008
-
1009
- this.setSize(this.width, this.height);
1010
-
1011
- // re-load the data
1012
- if (this.dataTable) {
1013
- this.setData(this.dataTable);
1014
- }
1015
-
1016
- // start animation when option is true
1017
- if (this.animationAutoStart && this.dataFilter) {
1018
- this.animationStart();
1019
- }
1020
- };
1021
-
1022
- /**
1023
- * Redraw the Graph.
1024
- */
1025
- Graph3d.prototype.redraw = function() {
1026
- if (this.dataPoints === undefined) {
1027
- throw 'Error: graph data not initialized';
1028
- }
1029
-
1030
- this._resizeCanvas();
1031
- this._resizeCenter();
1032
- this._redrawSlider();
1033
- this._redrawClear();
1034
- this._redrawAxis();
1035
-
1036
- if (this.style === Graph3d.STYLE.GRID ||
1037
- this.style === Graph3d.STYLE.SURFACE) {
1038
- this._redrawDataGrid();
1039
- }
1040
- else if (this.style === Graph3d.STYLE.LINE) {
1041
- this._redrawDataLine();
1042
- }
1043
- else if (this.style === Graph3d.STYLE.BAR ||
1044
- this.style === Graph3d.STYLE.BARCOLOR ||
1045
- this.style === Graph3d.STYLE.BARSIZE) {
1046
- this._redrawDataBar();
1047
- }
1048
- else {
1049
- // style is DOT, DOTLINE, DOTCOLOR, DOTSIZE
1050
- this._redrawDataDot();
1051
- }
1052
-
1053
- this._redrawInfo();
1054
- this._redrawLegend();
1055
- };
1056
-
1057
- /**
1058
- * Clear the canvas before redrawing
1059
- */
1060
- Graph3d.prototype._redrawClear = function() {
1061
- var canvas = this.frame.canvas;
1062
- var ctx = canvas.getContext('2d');
1063
-
1064
- ctx.clearRect(0, 0, canvas.width, canvas.height);
1065
- };
1066
-
1067
-
1068
- /**
1069
- * Redraw the legend showing the colors
1070
- */
1071
- Graph3d.prototype._redrawLegend = function() {
1072
- var y;
1073
-
1074
- if (this.style === Graph3d.STYLE.DOTCOLOR ||
1075
- this.style === Graph3d.STYLE.DOTSIZE) {
1076
-
1077
- var dotSize = this.frame.clientWidth * 0.02;
1078
-
1079
- var widthMin, widthMax;
1080
- if (this.style === Graph3d.STYLE.DOTSIZE) {
1081
- widthMin = dotSize / 2; // px
1082
- widthMax = dotSize / 2 + dotSize * 2; // Todo: put this in one function
1083
- }
1084
- else {
1085
- widthMin = 20; // px
1086
- widthMax = 20; // px
1087
- }
1088
-
1089
- var height = Math.max(this.frame.clientHeight * 0.25, 100);
1090
- var top = this.margin;
1091
- var right = this.frame.clientWidth - this.margin;
1092
- var left = right - widthMax;
1093
- var bottom = top + height;
1094
- }
1095
-
1096
- var canvas = this.frame.canvas;
1097
- var ctx = canvas.getContext('2d');
1098
- ctx.lineWidth = 1;
1099
- ctx.font = '14px arial'; // TODO: put in options
1100
-
1101
- if (this.style === Graph3d.STYLE.DOTCOLOR) {
1102
- // draw the color bar
1103
- var ymin = 0;
1104
- var ymax = height; // Todo: make height customizable
1105
- for (y = ymin; y < ymax; y++) {
1106
- var f = (y - ymin) / (ymax - ymin);
1107
-
1108
- //var width = (dotSize / 2 + (1-f) * dotSize * 2); // Todo: put this in one function
1109
- var hue = f * 240;
1110
- var color = this._hsv2rgb(hue, 1, 1);
1111
-
1112
- ctx.strokeStyle = color;
1113
- ctx.beginPath();
1114
- ctx.moveTo(left, top + y);
1115
- ctx.lineTo(right, top + y);
1116
- ctx.stroke();
1117
- }
1118
-
1119
- ctx.strokeStyle = this.colorAxis;
1120
- ctx.strokeRect(left, top, widthMax, height);
1121
- }
1122
-
1123
- if (this.style === Graph3d.STYLE.DOTSIZE) {
1124
- // draw border around color bar
1125
- ctx.strokeStyle = this.colorAxis;
1126
- ctx.fillStyle = this.colorDot;
1127
- ctx.beginPath();
1128
- ctx.moveTo(left, top);
1129
- ctx.lineTo(right, top);
1130
- ctx.lineTo(right - widthMax + widthMin, bottom);
1131
- ctx.lineTo(left, bottom);
1132
- ctx.closePath();
1133
- ctx.fill();
1134
- ctx.stroke();
1135
- }
1136
-
1137
- if (this.style === Graph3d.STYLE.DOTCOLOR ||
1138
- this.style === Graph3d.STYLE.DOTSIZE) {
1139
- // print values along the color bar
1140
- var gridLineLen = 5; // px
1141
- var step = new StepNumber(this.valueMin, this.valueMax, (this.valueMax-this.valueMin)/5, true);
1142
- step.start();
1143
- if (step.getCurrent() < this.valueMin) {
1144
- step.next();
1145
- }
1146
- while (!step.end()) {
1147
- y = bottom - (step.getCurrent() - this.valueMin) / (this.valueMax - this.valueMin) * height;
1148
-
1149
- ctx.beginPath();
1150
- ctx.moveTo(left - gridLineLen, y);
1151
- ctx.lineTo(left, y);
1152
- ctx.stroke();
1153
-
1154
- ctx.textAlign = 'right';
1155
- ctx.textBaseline = 'middle';
1156
- ctx.fillStyle = this.colorAxis;
1157
- ctx.fillText(step.getCurrent(), left - 2 * gridLineLen, y);
1158
-
1159
- step.next();
1160
- }
1161
-
1162
- ctx.textAlign = 'right';
1163
- ctx.textBaseline = 'top';
1164
- var label = this.legendLabel;
1165
- ctx.fillText(label, right, bottom + this.margin);
1166
- }
1167
- };
1168
-
1169
- /**
1170
- * Redraw the filter
1171
- */
1172
- Graph3d.prototype._redrawFilter = function() {
1173
- this.frame.filter.innerHTML = '';
1174
-
1175
- if (this.dataFilter) {
1176
- var options = {
1177
- 'visible': this.showAnimationControls
1178
- };
1179
- var slider = new Slider(this.frame.filter, options);
1180
- this.frame.filter.slider = slider;
1181
-
1182
- // TODO: css here is not nice here...
1183
- this.frame.filter.style.padding = '10px';
1184
- //this.frame.filter.style.backgroundColor = '#EFEFEF';
1185
-
1186
- slider.setValues(this.dataFilter.values);
1187
- slider.setPlayInterval(this.animationInterval);
1188
-
1189
- // create an event handler
1190
- var me = this;
1191
- var onchange = function () {
1192
- var index = slider.getIndex();
1193
-
1194
- me.dataFilter.selectValue(index);
1195
- me.dataPoints = me.dataFilter._getDataPoints();
1196
-
1197
- me.redraw();
1198
- };
1199
- slider.setOnChangeCallback(onchange);
1200
- }
1201
- else {
1202
- this.frame.filter.slider = undefined;
1203
- }
1204
- };
1205
-
1206
- /**
1207
- * Redraw the slider
1208
- */
1209
- Graph3d.prototype._redrawSlider = function() {
1210
- if ( this.frame.filter.slider !== undefined) {
1211
- this.frame.filter.slider.redraw();
1212
- }
1213
- };
1214
-
1215
-
1216
- /**
1217
- * Redraw common information
1218
- */
1219
- Graph3d.prototype._redrawInfo = function() {
1220
- if (this.dataFilter) {
1221
- var canvas = this.frame.canvas;
1222
- var ctx = canvas.getContext('2d');
1223
-
1224
- ctx.font = '14px arial'; // TODO: put in options
1225
- ctx.lineStyle = 'gray';
1226
- ctx.fillStyle = 'gray';
1227
- ctx.textAlign = 'left';
1228
- ctx.textBaseline = 'top';
1229
-
1230
- var x = this.margin;
1231
- var y = this.margin;
1232
- ctx.fillText(this.dataFilter.getLabel() + ': ' + this.dataFilter.getSelectedValue(), x, y);
1233
- }
1234
- };
1235
-
1236
-
1237
- /**
1238
- * Redraw the axis
1239
- */
1240
- Graph3d.prototype._redrawAxis = function() {
1241
- var canvas = this.frame.canvas,
1242
- ctx = canvas.getContext('2d'),
1243
- from, to, step, prettyStep,
1244
- text, xText, yText, zText,
1245
- offset, xOffset, yOffset,
1246
- xMin2d, xMax2d;
1247
-
1248
- // TODO: get the actual rendered style of the containerElement
1249
- //ctx.font = this.containerElement.style.font;
1250
- ctx.font = 24 / this.camera.getArmLength() + 'px arial';
1251
-
1252
- // calculate the length for the short grid lines
1253
- var gridLenX = 0.025 / this.scale.x;
1254
- var gridLenY = 0.025 / this.scale.y;
1255
- var textMargin = 5 / this.camera.getArmLength(); // px
1256
- var armAngle = this.camera.getArmRotation().horizontal;
1257
-
1258
- // draw x-grid lines
1259
- ctx.lineWidth = 1;
1260
- prettyStep = (this.defaultXStep === undefined);
1261
- step = new StepNumber(this.xMin, this.xMax, this.xStep, prettyStep);
1262
- step.start();
1263
- if (step.getCurrent() < this.xMin) {
1264
- step.next();
1265
- }
1266
- while (!step.end()) {
1267
- var x = step.getCurrent();
1268
-
1269
- if (this.showGrid) {
1270
- from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin));
1271
- to = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin));
1272
- ctx.strokeStyle = this.colorGrid;
1273
- ctx.beginPath();
1274
- ctx.moveTo(from.x, from.y);
1275
- ctx.lineTo(to.x, to.y);
1276
- ctx.stroke();
1277
- }
1278
- else {
1279
- from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin));
1280
- to = this._convert3Dto2D(new Point3d(x, this.yMin+gridLenX, this.zMin));
1281
- ctx.strokeStyle = this.colorAxis;
1282
- ctx.beginPath();
1283
- ctx.moveTo(from.x, from.y);
1284
- ctx.lineTo(to.x, to.y);
1285
- ctx.stroke();
1286
-
1287
- from = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin));
1288
- to = this._convert3Dto2D(new Point3d(x, this.yMax-gridLenX, this.zMin));
1289
- ctx.strokeStyle = this.colorAxis;
1290
- ctx.beginPath();
1291
- ctx.moveTo(from.x, from.y);
1292
- ctx.lineTo(to.x, to.y);
1293
- ctx.stroke();
1294
- }
1295
-
1296
- yText = (Math.cos(armAngle) > 0) ? this.yMin : this.yMax;
1297
- text = this._convert3Dto2D(new Point3d(x, yText, this.zMin));
1298
- if (Math.cos(armAngle * 2) > 0) {
1299
- ctx.textAlign = 'center';
1300
- ctx.textBaseline = 'top';
1301
- text.y += textMargin;
1302
- }
1303
- else if (Math.sin(armAngle * 2) < 0){
1304
- ctx.textAlign = 'right';
1305
- ctx.textBaseline = 'middle';
1306
- }
1307
- else {
1308
- ctx.textAlign = 'left';
1309
- ctx.textBaseline = 'middle';
1310
- }
1311
- ctx.fillStyle = this.colorAxis;
1312
- ctx.fillText(' ' + step.getCurrent() + ' ', text.x, text.y);
1313
-
1314
- step.next();
1315
- }
1316
-
1317
- // draw y-grid lines
1318
- ctx.lineWidth = 1;
1319
- prettyStep = (this.defaultYStep === undefined);
1320
- step = new StepNumber(this.yMin, this.yMax, this.yStep, prettyStep);
1321
- step.start();
1322
- if (step.getCurrent() < this.yMin) {
1323
- step.next();
1324
- }
1325
- while (!step.end()) {
1326
- if (this.showGrid) {
1327
- from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin));
1328
- to = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin));
1329
- ctx.strokeStyle = this.colorGrid;
1330
- ctx.beginPath();
1331
- ctx.moveTo(from.x, from.y);
1332
- ctx.lineTo(to.x, to.y);
1333
- ctx.stroke();
1334
- }
1335
- else {
1336
- from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin));
1337
- to = this._convert3Dto2D(new Point3d(this.xMin+gridLenY, step.getCurrent(), this.zMin));
1338
- ctx.strokeStyle = this.colorAxis;
1339
- ctx.beginPath();
1340
- ctx.moveTo(from.x, from.y);
1341
- ctx.lineTo(to.x, to.y);
1342
- ctx.stroke();
1343
-
1344
- from = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin));
1345
- to = this._convert3Dto2D(new Point3d(this.xMax-gridLenY, step.getCurrent(), this.zMin));
1346
- ctx.strokeStyle = this.colorAxis;
1347
- ctx.beginPath();
1348
- ctx.moveTo(from.x, from.y);
1349
- ctx.lineTo(to.x, to.y);
1350
- ctx.stroke();
1351
- }
1352
-
1353
- xText = (Math.sin(armAngle ) > 0) ? this.xMin : this.xMax;
1354
- text = this._convert3Dto2D(new Point3d(xText, step.getCurrent(), this.zMin));
1355
- if (Math.cos(armAngle * 2) < 0) {
1356
- ctx.textAlign = 'center';
1357
- ctx.textBaseline = 'top';
1358
- text.y += textMargin;
1359
- }
1360
- else if (Math.sin(armAngle * 2) > 0){
1361
- ctx.textAlign = 'right';
1362
- ctx.textBaseline = 'middle';
1363
- }
1364
- else {
1365
- ctx.textAlign = 'left';
1366
- ctx.textBaseline = 'middle';
1367
- }
1368
- ctx.fillStyle = this.colorAxis;
1369
- ctx.fillText(' ' + step.getCurrent() + ' ', text.x, text.y);
1370
-
1371
- step.next();
1372
- }
1373
-
1374
- // draw z-grid lines and axis
1375
- ctx.lineWidth = 1;
1376
- prettyStep = (this.defaultZStep === undefined);
1377
- step = new StepNumber(this.zMin, this.zMax, this.zStep, prettyStep);
1378
- step.start();
1379
- if (step.getCurrent() < this.zMin) {
1380
- step.next();
1381
- }
1382
- xText = (Math.cos(armAngle ) > 0) ? this.xMin : this.xMax;
1383
- yText = (Math.sin(armAngle ) < 0) ? this.yMin : this.yMax;
1384
- while (!step.end()) {
1385
- // TODO: make z-grid lines really 3d?
1386
- from = this._convert3Dto2D(new Point3d(xText, yText, step.getCurrent()));
1387
- ctx.strokeStyle = this.colorAxis;
1388
- ctx.beginPath();
1389
- ctx.moveTo(from.x, from.y);
1390
- ctx.lineTo(from.x - textMargin, from.y);
1391
- ctx.stroke();
1392
-
1393
- ctx.textAlign = 'right';
1394
- ctx.textBaseline = 'middle';
1395
- ctx.fillStyle = this.colorAxis;
1396
- ctx.fillText(step.getCurrent() + ' ', from.x - 5, from.y);
1397
-
1398
- step.next();
1399
- }
1400
- ctx.lineWidth = 1;
1401
- from = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
1402
- to = this._convert3Dto2D(new Point3d(xText, yText, this.zMax));
1403
- ctx.strokeStyle = this.colorAxis;
1404
- ctx.beginPath();
1405
- ctx.moveTo(from.x, from.y);
1406
- ctx.lineTo(to.x, to.y);
1407
- ctx.stroke();
1408
-
1409
- // draw x-axis
1410
- ctx.lineWidth = 1;
1411
- // line at yMin
1412
- xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin));
1413
- xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin));
1414
- ctx.strokeStyle = this.colorAxis;
1415
- ctx.beginPath();
1416
- ctx.moveTo(xMin2d.x, xMin2d.y);
1417
- ctx.lineTo(xMax2d.x, xMax2d.y);
1418
- ctx.stroke();
1419
- // line at ymax
1420
- xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin));
1421
- xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin));
1422
- ctx.strokeStyle = this.colorAxis;
1423
- ctx.beginPath();
1424
- ctx.moveTo(xMin2d.x, xMin2d.y);
1425
- ctx.lineTo(xMax2d.x, xMax2d.y);
1426
- ctx.stroke();
1427
-
1428
- // draw y-axis
1429
- ctx.lineWidth = 1;
1430
- // line at xMin
1431
- from = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin));
1432
- to = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin));
1433
- ctx.strokeStyle = this.colorAxis;
1434
- ctx.beginPath();
1435
- ctx.moveTo(from.x, from.y);
1436
- ctx.lineTo(to.x, to.y);
1437
- ctx.stroke();
1438
- // line at xMax
1439
- from = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin));
1440
- to = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin));
1441
- ctx.strokeStyle = this.colorAxis;
1442
- ctx.beginPath();
1443
- ctx.moveTo(from.x, from.y);
1444
- ctx.lineTo(to.x, to.y);
1445
- ctx.stroke();
1446
-
1447
- // draw x-label
1448
- var xLabel = this.xLabel;
1449
- if (xLabel.length > 0) {
1450
- yOffset = 0.1 / this.scale.y;
1451
- xText = (this.xMin + this.xMax) / 2;
1452
- yText = (Math.cos(armAngle) > 0) ? this.yMin - yOffset: this.yMax + yOffset;
1453
- text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
1454
- if (Math.cos(armAngle * 2) > 0) {
1455
- ctx.textAlign = 'center';
1456
- ctx.textBaseline = 'top';
1457
- }
1458
- else if (Math.sin(armAngle * 2) < 0){
1459
- ctx.textAlign = 'right';
1460
- ctx.textBaseline = 'middle';
1461
- }
1462
- else {
1463
- ctx.textAlign = 'left';
1464
- ctx.textBaseline = 'middle';
1465
- }
1466
- ctx.fillStyle = this.colorAxis;
1467
- ctx.fillText(xLabel, text.x, text.y);
1468
- }
1469
-
1470
- // draw y-label
1471
- var yLabel = this.yLabel;
1472
- if (yLabel.length > 0) {
1473
- xOffset = 0.1 / this.scale.x;
1474
- xText = (Math.sin(armAngle ) > 0) ? this.xMin - xOffset : this.xMax + xOffset;
1475
- yText = (this.yMin + this.yMax) / 2;
1476
- text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
1477
- if (Math.cos(armAngle * 2) < 0) {
1478
- ctx.textAlign = 'center';
1479
- ctx.textBaseline = 'top';
1480
- }
1481
- else if (Math.sin(armAngle * 2) > 0){
1482
- ctx.textAlign = 'right';
1483
- ctx.textBaseline = 'middle';
1484
- }
1485
- else {
1486
- ctx.textAlign = 'left';
1487
- ctx.textBaseline = 'middle';
1488
- }
1489
- ctx.fillStyle = this.colorAxis;
1490
- ctx.fillText(yLabel, text.x, text.y);
1491
- }
1492
-
1493
- // draw z-label
1494
- var zLabel = this.zLabel;
1495
- if (zLabel.length > 0) {
1496
- offset = 30; // pixels. // TODO: relate to the max width of the values on the z axis?
1497
- xText = (Math.cos(armAngle ) > 0) ? this.xMin : this.xMax;
1498
- yText = (Math.sin(armAngle ) < 0) ? this.yMin : this.yMax;
1499
- zText = (this.zMin + this.zMax) / 2;
1500
- text = this._convert3Dto2D(new Point3d(xText, yText, zText));
1501
- ctx.textAlign = 'right';
1502
- ctx.textBaseline = 'middle';
1503
- ctx.fillStyle = this.colorAxis;
1504
- ctx.fillText(zLabel, text.x - offset, text.y);
1505
- }
1506
- };
1507
-
1508
- /**
1509
- * Calculate the color based on the given value.
1510
- * @param {Number} H Hue, a value be between 0 and 360
1511
- * @param {Number} S Saturation, a value between 0 and 1
1512
- * @param {Number} V Value, a value between 0 and 1
1513
- */
1514
- Graph3d.prototype._hsv2rgb = function(H, S, V) {
1515
- var R, G, B, C, Hi, X;
1516
-
1517
- C = V * S;
1518
- Hi = Math.floor(H/60); // hi = 0,1,2,3,4,5
1519
- X = C * (1 - Math.abs(((H/60) % 2) - 1));
1520
-
1521
- switch (Hi) {
1522
- case 0: R = C; G = X; B = 0; break;
1523
- case 1: R = X; G = C; B = 0; break;
1524
- case 2: R = 0; G = C; B = X; break;
1525
- case 3: R = 0; G = X; B = C; break;
1526
- case 4: R = X; G = 0; B = C; break;
1527
- case 5: R = C; G = 0; B = X; break;
1528
-
1529
- default: R = 0; G = 0; B = 0; break;
1530
- }
1531
-
1532
- return 'RGB(' + parseInt(R*255) + ',' + parseInt(G*255) + ',' + parseInt(B*255) + ')';
1533
- };
1534
-
1535
-
1536
- /**
1537
- * Draw all datapoints as a grid
1538
- * This function can be used when the style is 'grid'
1539
- */
1540
- Graph3d.prototype._redrawDataGrid = function() {
1541
- var canvas = this.frame.canvas,
1542
- ctx = canvas.getContext('2d'),
1543
- point, right, top, cross,
1544
- i,
1545
- topSideVisible, fillStyle, strokeStyle, lineWidth,
1546
- h, s, v, zAvg;
1547
-
1548
-
1549
- if (this.dataPoints === undefined || this.dataPoints.length <= 0)
1550
- return; // TODO: throw exception?
1551
-
1552
- // calculate the translations and screen position of all points
1553
- for (i = 0; i < this.dataPoints.length; i++) {
1554
- var trans = this._convertPointToTranslation(this.dataPoints[i].point);
1555
- var screen = this._convertTranslationToScreen(trans);
1556
-
1557
- this.dataPoints[i].trans = trans;
1558
- this.dataPoints[i].screen = screen;
1559
-
1560
- // calculate the translation of the point at the bottom (needed for sorting)
1561
- var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
1562
- this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
1563
- }
1564
-
1565
- // sort the points on depth of their (x,y) position (not on z)
1566
- var sortDepth = function (a, b) {
1567
- return b.dist - a.dist;
1568
- };
1569
- this.dataPoints.sort(sortDepth);
1570
-
1571
- if (this.style === Graph3d.STYLE.SURFACE) {
1572
- for (i = 0; i < this.dataPoints.length; i++) {
1573
- point = this.dataPoints[i];
1574
- right = this.dataPoints[i].pointRight;
1575
- top = this.dataPoints[i].pointTop;
1576
- cross = this.dataPoints[i].pointCross;
1577
-
1578
- if (point !== undefined && right !== undefined && top !== undefined && cross !== undefined) {
1579
-
1580
- if (this.showGrayBottom || this.showShadow) {
1581
- // calculate the cross product of the two vectors from center
1582
- // to left and right, in order to know whether we are looking at the
1583
- // bottom or at the top side. We can also use the cross product
1584
- // for calculating light intensity
1585
- var aDiff = Point3d.subtract(cross.trans, point.trans);
1586
- var bDiff = Point3d.subtract(top.trans, right.trans);
1587
- var crossproduct = Point3d.crossProduct(aDiff, bDiff);
1588
- var len = crossproduct.length();
1589
- // FIXME: there is a bug with determining the surface side (shadow or colored)
1590
-
1591
- topSideVisible = (crossproduct.z > 0);
1592
- }
1593
- else {
1594
- topSideVisible = true;
1595
- }
1596
-
1597
- if (topSideVisible) {
1598
- // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
1599
- zAvg = (point.point.z + right.point.z + top.point.z + cross.point.z) / 4;
1600
- h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
1601
- s = 1; // saturation
1602
-
1603
- if (this.showShadow) {
1604
- v = Math.min(1 + (crossproduct.x / len) / 2, 1); // value. TODO: scale
1605
- fillStyle = this._hsv2rgb(h, s, v);
1606
- strokeStyle = fillStyle;
1607
- }
1608
- else {
1609
- v = 1;
1610
- fillStyle = this._hsv2rgb(h, s, v);
1611
- strokeStyle = this.colorAxis;
1612
- }
1613
- }
1614
- else {
1615
- fillStyle = 'gray';
1616
- strokeStyle = this.colorAxis;
1617
- }
1618
- lineWidth = 0.5;
1619
-
1620
- ctx.lineWidth = lineWidth;
1621
- ctx.fillStyle = fillStyle;
1622
- ctx.strokeStyle = strokeStyle;
1623
- ctx.beginPath();
1624
- ctx.moveTo(point.screen.x, point.screen.y);
1625
- ctx.lineTo(right.screen.x, right.screen.y);
1626
- ctx.lineTo(cross.screen.x, cross.screen.y);
1627
- ctx.lineTo(top.screen.x, top.screen.y);
1628
- ctx.closePath();
1629
- ctx.fill();
1630
- ctx.stroke();
1631
- }
1632
- }
1633
- }
1634
- else { // grid style
1635
- for (i = 0; i < this.dataPoints.length; i++) {
1636
- point = this.dataPoints[i];
1637
- right = this.dataPoints[i].pointRight;
1638
- top = this.dataPoints[i].pointTop;
1639
-
1640
- if (point !== undefined) {
1641
- if (this.showPerspective) {
1642
- lineWidth = 2 / -point.trans.z;
1643
- }
1644
- else {
1645
- lineWidth = 2 * -(this.eye.z / this.camera.getArmLength());
1646
- }
1647
- }
1648
-
1649
- if (point !== undefined && right !== undefined) {
1650
- // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
1651
- zAvg = (point.point.z + right.point.z) / 2;
1652
- h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
1653
-
1654
- ctx.lineWidth = lineWidth;
1655
- ctx.strokeStyle = this._hsv2rgb(h, 1, 1);
1656
- ctx.beginPath();
1657
- ctx.moveTo(point.screen.x, point.screen.y);
1658
- ctx.lineTo(right.screen.x, right.screen.y);
1659
- ctx.stroke();
1660
- }
1661
-
1662
- if (point !== undefined && top !== undefined) {
1663
- // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
1664
- zAvg = (point.point.z + top.point.z) / 2;
1665
- h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
1666
-
1667
- ctx.lineWidth = lineWidth;
1668
- ctx.strokeStyle = this._hsv2rgb(h, 1, 1);
1669
- ctx.beginPath();
1670
- ctx.moveTo(point.screen.x, point.screen.y);
1671
- ctx.lineTo(top.screen.x, top.screen.y);
1672
- ctx.stroke();
1673
- }
1674
- }
1675
- }
1676
- };
1677
-
1678
-
1679
- /**
1680
- * Draw all datapoints as dots.
1681
- * This function can be used when the style is 'dot' or 'dot-line'
1682
- */
1683
- Graph3d.prototype._redrawDataDot = function() {
1684
- var canvas = this.frame.canvas;
1685
- var ctx = canvas.getContext('2d');
1686
- var i;
1687
-
1688
- if (this.dataPoints === undefined || this.dataPoints.length <= 0)
1689
- return; // TODO: throw exception?
1690
-
1691
- // calculate the translations of all points
1692
- for (i = 0; i < this.dataPoints.length; i++) {
1693
- var trans = this._convertPointToTranslation(this.dataPoints[i].point);
1694
- var screen = this._convertTranslationToScreen(trans);
1695
- this.dataPoints[i].trans = trans;
1696
- this.dataPoints[i].screen = screen;
1697
-
1698
- // calculate the distance from the point at the bottom to the camera
1699
- var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
1700
- this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
1701
- }
1702
-
1703
- // order the translated points by depth
1704
- var sortDepth = function (a, b) {
1705
- return b.dist - a.dist;
1706
- };
1707
- this.dataPoints.sort(sortDepth);
1708
-
1709
- // draw the datapoints as colored circles
1710
- var dotSize = this.frame.clientWidth * 0.02; // px
1711
- for (i = 0; i < this.dataPoints.length; i++) {
1712
- var point = this.dataPoints[i];
1713
-
1714
- if (this.style === Graph3d.STYLE.DOTLINE) {
1715
- // draw a vertical line from the bottom to the graph value
1716
- //var from = this._convert3Dto2D(new Point3d(point.point.x, point.point.y, this.zMin));
1717
- var from = this._convert3Dto2D(point.bottom);
1718
- ctx.lineWidth = 1;
1719
- ctx.strokeStyle = this.colorGrid;
1720
- ctx.beginPath();
1721
- ctx.moveTo(from.x, from.y);
1722
- ctx.lineTo(point.screen.x, point.screen.y);
1723
- ctx.stroke();
1724
- }
1725
-
1726
- // calculate radius for the circle
1727
- var size;
1728
- if (this.style === Graph3d.STYLE.DOTSIZE) {
1729
- size = dotSize/2 + 2*dotSize * (point.point.value - this.valueMin) / (this.valueMax - this.valueMin);
1730
- }
1731
- else {
1732
- size = dotSize;
1733
- }
1734
-
1735
- var radius;
1736
- if (this.showPerspective) {
1737
- radius = size / -point.trans.z;
1738
- }
1739
- else {
1740
- radius = size * -(this.eye.z / this.camera.getArmLength());
1741
- }
1742
- if (radius < 0) {
1743
- radius = 0;
1744
- }
1745
-
1746
- var hue, color, borderColor;
1747
- if (this.style === Graph3d.STYLE.DOTCOLOR ) {
1748
- // calculate the color based on the value
1749
- hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240;
1750
- color = this._hsv2rgb(hue, 1, 1);
1751
- borderColor = this._hsv2rgb(hue, 1, 0.8);
1752
- }
1753
- else if (this.style === Graph3d.STYLE.DOTSIZE) {
1754
- color = this.colorDot;
1755
- borderColor = this.colorDotBorder;
1756
- }
1757
- else {
1758
- // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
1759
- hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240;
1760
- color = this._hsv2rgb(hue, 1, 1);
1761
- borderColor = this._hsv2rgb(hue, 1, 0.8);
1762
- }
1763
-
1764
- // draw the circle
1765
- ctx.lineWidth = 1.0;
1766
- ctx.strokeStyle = borderColor;
1767
- ctx.fillStyle = color;
1768
- ctx.beginPath();
1769
- ctx.arc(point.screen.x, point.screen.y, radius, 0, Math.PI*2, true);
1770
- ctx.fill();
1771
- ctx.stroke();
1772
- }
1773
- };
1774
-
1775
- /**
1776
- * Draw all datapoints as bars.
1777
- * This function can be used when the style is 'bar', 'bar-color', or 'bar-size'
1778
- */
1779
- Graph3d.prototype._redrawDataBar = function() {
1780
- var canvas = this.frame.canvas;
1781
- var ctx = canvas.getContext('2d');
1782
- var i, j, surface, corners;
1783
-
1784
- if (this.dataPoints === undefined || this.dataPoints.length <= 0)
1785
- return; // TODO: throw exception?
1786
-
1787
- // calculate the translations of all points
1788
- for (i = 0; i < this.dataPoints.length; i++) {
1789
- var trans = this._convertPointToTranslation(this.dataPoints[i].point);
1790
- var screen = this._convertTranslationToScreen(trans);
1791
- this.dataPoints[i].trans = trans;
1792
- this.dataPoints[i].screen = screen;
1793
-
1794
- // calculate the distance from the point at the bottom to the camera
1795
- var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
1796
- this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
1797
- }
1798
-
1799
- // order the translated points by depth
1800
- var sortDepth = function (a, b) {
1801
- return b.dist - a.dist;
1802
- };
1803
- this.dataPoints.sort(sortDepth);
1804
-
1805
- // draw the datapoints as bars
1806
- var xWidth = this.xBarWidth / 2;
1807
- var yWidth = this.yBarWidth / 2;
1808
- for (i = 0; i < this.dataPoints.length; i++) {
1809
- var point = this.dataPoints[i];
1810
-
1811
- // determine color
1812
- var hue, color, borderColor;
1813
- if (this.style === Graph3d.STYLE.BARCOLOR ) {
1814
- // calculate the color based on the value
1815
- hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240;
1816
- color = this._hsv2rgb(hue, 1, 1);
1817
- borderColor = this._hsv2rgb(hue, 1, 0.8);
1818
- }
1819
- else if (this.style === Graph3d.STYLE.BARSIZE) {
1820
- color = this.colorDot;
1821
- borderColor = this.colorDotBorder;
1822
- }
1823
- else {
1824
- // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
1825
- hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240;
1826
- color = this._hsv2rgb(hue, 1, 1);
1827
- borderColor = this._hsv2rgb(hue, 1, 0.8);
1828
- }
1829
-
1830
- // calculate size for the bar
1831
- if (this.style === Graph3d.STYLE.BARSIZE) {
1832
- xWidth = (this.xBarWidth / 2) * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2);
1833
- yWidth = (this.yBarWidth / 2) * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2);
1834
- }
1835
-
1836
- // calculate all corner points
1837
- var me = this;
1838
- var point3d = point.point;
1839
- var top = [
1840
- {point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, point3d.z)},
1841
- {point: new Point3d(point3d.x + xWidth, point3d.y - yWidth, point3d.z)},
1842
- {point: new Point3d(point3d.x + xWidth, point3d.y + yWidth, point3d.z)},
1843
- {point: new Point3d(point3d.x - xWidth, point3d.y + yWidth, point3d.z)}
1844
- ];
1845
- var bottom = [
1846
- {point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, this.zMin)},
1847
- {point: new Point3d(point3d.x + xWidth, point3d.y - yWidth, this.zMin)},
1848
- {point: new Point3d(point3d.x + xWidth, point3d.y + yWidth, this.zMin)},
1849
- {point: new Point3d(point3d.x - xWidth, point3d.y + yWidth, this.zMin)}
1850
- ];
1851
-
1852
- // calculate screen location of the points
1853
- top.forEach(function (obj) {
1854
- obj.screen = me._convert3Dto2D(obj.point);
1855
- });
1856
- bottom.forEach(function (obj) {
1857
- obj.screen = me._convert3Dto2D(obj.point);
1858
- });
1859
-
1860
- // create five sides, calculate both corner points and center points
1861
- var surfaces = [
1862
- {corners: top, center: Point3d.avg(bottom[0].point, bottom[2].point)},
1863
- {corners: [top[0], top[1], bottom[1], bottom[0]], center: Point3d.avg(bottom[1].point, bottom[0].point)},
1864
- {corners: [top[1], top[2], bottom[2], bottom[1]], center: Point3d.avg(bottom[2].point, bottom[1].point)},
1865
- {corners: [top[2], top[3], bottom[3], bottom[2]], center: Point3d.avg(bottom[3].point, bottom[2].point)},
1866
- {corners: [top[3], top[0], bottom[0], bottom[3]], center: Point3d.avg(bottom[0].point, bottom[3].point)}
1867
- ];
1868
- point.surfaces = surfaces;
1869
-
1870
- // calculate the distance of each of the surface centers to the camera
1871
- for (j = 0; j < surfaces.length; j++) {
1872
- surface = surfaces[j];
1873
- var transCenter = this._convertPointToTranslation(surface.center);
1874
- surface.dist = this.showPerspective ? transCenter.length() : -transCenter.z;
1875
- // TODO: this dept calculation doesn't work 100% of the cases due to perspective,
1876
- // but the current solution is fast/simple and works in 99.9% of all cases
1877
- // the issue is visible in example 14, with graph.setCameraPosition({horizontal: 2.97, vertical: 0.5, distance: 0.9})
1878
- }
1879
-
1880
- // order the surfaces by their (translated) depth
1881
- surfaces.sort(function (a, b) {
1882
- var diff = b.dist - a.dist;
1883
- if (diff) return diff;
1884
-
1885
- // if equal depth, sort the top surface last
1886
- if (a.corners === top) return 1;
1887
- if (b.corners === top) return -1;
1888
-
1889
- // both are equal
1890
- return 0;
1891
- });
1892
-
1893
- // draw the ordered surfaces
1894
- ctx.lineWidth = 1;
1895
- ctx.strokeStyle = borderColor;
1896
- ctx.fillStyle = color;
1897
- // NOTE: we start at j=2 instead of j=0 as we don't need to draw the two surfaces at the backside
1898
- for (j = 2; j < surfaces.length; j++) {
1899
- surface = surfaces[j];
1900
- corners = surface.corners;
1901
- ctx.beginPath();
1902
- ctx.moveTo(corners[3].screen.x, corners[3].screen.y);
1903
- ctx.lineTo(corners[0].screen.x, corners[0].screen.y);
1904
- ctx.lineTo(corners[1].screen.x, corners[1].screen.y);
1905
- ctx.lineTo(corners[2].screen.x, corners[2].screen.y);
1906
- ctx.lineTo(corners[3].screen.x, corners[3].screen.y);
1907
- ctx.fill();
1908
- ctx.stroke();
1909
- }
1910
- }
1911
- };
1912
-
1913
-
1914
- /**
1915
- * Draw a line through all datapoints.
1916
- * This function can be used when the style is 'line'
1917
- */
1918
- Graph3d.prototype._redrawDataLine = function() {
1919
- var canvas = this.frame.canvas,
1920
- ctx = canvas.getContext('2d'),
1921
- point, i;
1922
-
1923
- if (this.dataPoints === undefined || this.dataPoints.length <= 0)
1924
- return; // TODO: throw exception?
1925
-
1926
- // calculate the translations of all points
1927
- for (i = 0; i < this.dataPoints.length; i++) {
1928
- var trans = this._convertPointToTranslation(this.dataPoints[i].point);
1929
- var screen = this._convertTranslationToScreen(trans);
1930
-
1931
- this.dataPoints[i].trans = trans;
1932
- this.dataPoints[i].screen = screen;
1933
- }
1934
-
1935
- // start the line
1936
- if (this.dataPoints.length > 0) {
1937
- point = this.dataPoints[0];
1938
-
1939
- ctx.lineWidth = 1; // TODO: make customizable
1940
- ctx.strokeStyle = 'blue'; // TODO: make customizable
1941
- ctx.beginPath();
1942
- ctx.moveTo(point.screen.x, point.screen.y);
1943
- }
1944
-
1945
- // draw the datapoints as colored circles
1946
- for (i = 1; i < this.dataPoints.length; i++) {
1947
- point = this.dataPoints[i];
1948
- ctx.lineTo(point.screen.x, point.screen.y);
1949
- }
1950
-
1951
- // finish the line
1952
- if (this.dataPoints.length > 0) {
1953
- ctx.stroke();
1954
- }
1955
- };
1956
-
1957
- /**
1958
- * Start a moving operation inside the provided parent element
1959
- * @param {Event} event The event that occurred (required for
1960
- * retrieving the mouse position)
1961
- */
1962
- Graph3d.prototype._onMouseDown = function(event) {
1963
- event = event || window.event;
1964
-
1965
- // check if mouse is still down (may be up when focus is lost for example
1966
- // in an iframe)
1967
- if (this.leftButtonDown) {
1968
- this._onMouseUp(event);
1969
- }
1970
-
1971
- // only react on left mouse button down
1972
- this.leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
1973
- if (!this.leftButtonDown && !this.touchDown) return;
1974
-
1975
- // get mouse position (different code for IE and all other browsers)
1976
- this.startMouseX = getMouseX(event);
1977
- this.startMouseY = getMouseY(event);
1978
-
1979
- this.startStart = new Date(this.start);
1980
- this.startEnd = new Date(this.end);
1981
- this.startArmRotation = this.camera.getArmRotation();
1982
-
1983
- this.frame.style.cursor = 'move';
1984
-
1985
- // add event listeners to handle moving the contents
1986
- // we store the function onmousemove and onmouseup in the graph, so we can
1987
- // remove the eventlisteners lateron in the function mouseUp()
1988
- var me = this;
1989
- this.onmousemove = function (event) {me._onMouseMove(event);};
1990
- this.onmouseup = function (event) {me._onMouseUp(event);};
1991
- G3DaddEventListener(document, 'mousemove', me.onmousemove);
1992
- G3DaddEventListener(document, 'mouseup', me.onmouseup);
1993
- G3DpreventDefault(event);
1994
- };
1995
-
1996
-
1997
- /**
1998
- * Perform moving operating.
1999
- * This function activated from within the funcion Graph.mouseDown().
2000
- * @param {Event} event Well, eehh, the event
2001
- */
2002
- Graph3d.prototype._onMouseMove = function (event) {
2003
- event = event || window.event;
2004
-
2005
- // calculate change in mouse position
2006
- var diffX = parseFloat(getMouseX(event)) - this.startMouseX;
2007
- var diffY = parseFloat(getMouseY(event)) - this.startMouseY;
2008
-
2009
- var horizontalNew = this.startArmRotation.horizontal + diffX / 200;
2010
- var verticalNew = this.startArmRotation.vertical + diffY / 200;
2011
-
2012
- var snapAngle = 4; // degrees
2013
- var snapValue = Math.sin(snapAngle / 360 * 2 * Math.PI);
2014
-
2015
- // snap horizontally to nice angles at 0pi, 0.5pi, 1pi, 1.5pi, etc...
2016
- // the -0.001 is to take care that the vertical axis is always drawn at the left front corner
2017
- if (Math.abs(Math.sin(horizontalNew)) < snapValue) {
2018
- horizontalNew = Math.round((horizontalNew / Math.PI)) * Math.PI - 0.001;
2019
- }
2020
- if (Math.abs(Math.cos(horizontalNew)) < snapValue) {
2021
- horizontalNew = (Math.round((horizontalNew/ Math.PI - 0.5)) + 0.5) * Math.PI - 0.001;
2022
- }
2023
-
2024
- // snap vertically to nice angles
2025
- if (Math.abs(Math.sin(verticalNew)) < snapValue) {
2026
- verticalNew = Math.round((verticalNew / Math.PI)) * Math.PI;
2027
- }
2028
- if (Math.abs(Math.cos(verticalNew)) < snapValue) {
2029
- verticalNew = (Math.round((verticalNew/ Math.PI - 0.5)) + 0.5) * Math.PI;
2030
- }
2031
-
2032
- this.camera.setArmRotation(horizontalNew, verticalNew);
2033
- this.redraw();
2034
-
2035
- // fire a cameraPositionChange event
2036
- var parameters = this.getCameraPosition();
2037
- this.emit('cameraPositionChange', parameters);
2038
-
2039
- G3DpreventDefault(event);
2040
- };
2041
-
2042
-
2043
- /**
2044
- * Stop moving operating.
2045
- * This function activated from within the funcion Graph.mouseDown().
2046
- * @param {event} event The event
2047
- */
2048
- Graph3d.prototype._onMouseUp = function (event) {
2049
- this.frame.style.cursor = 'auto';
2050
- this.leftButtonDown = false;
2051
-
2052
- // remove event listeners here
2053
- G3DremoveEventListener(document, 'mousemove', this.onmousemove);
2054
- G3DremoveEventListener(document, 'mouseup', this.onmouseup);
2055
- G3DpreventDefault(event);
2056
- };
2057
-
2058
- /**
2059
- * After having moved the mouse, a tooltip should pop up when the mouse is resting on a data point
2060
- * @param {Event} event A mouse move event
2061
- */
2062
- Graph3d.prototype._onTooltip = function (event) {
2063
- var delay = 300; // ms
2064
- var mouseX = getMouseX(event) - getAbsoluteLeft(this.frame);
2065
- var mouseY = getMouseY(event) - getAbsoluteTop(this.frame);
2066
-
2067
- if (!this.showTooltip) {
2068
- return;
2069
- }
2070
-
2071
- if (this.tooltipTimeout) {
2072
- clearTimeout(this.tooltipTimeout);
2073
- }
2074
-
2075
- // (delayed) display of a tooltip only if no mouse button is down
2076
- if (this.leftButtonDown) {
2077
- this._hideTooltip();
2078
- return;
2079
- }
2080
-
2081
- if (this.tooltip && this.tooltip.dataPoint) {
2082
- // tooltip is currently visible
2083
- var dataPoint = this._dataPointFromXY(mouseX, mouseY);
2084
- if (dataPoint !== this.tooltip.dataPoint) {
2085
- // datapoint changed
2086
- if (dataPoint) {
2087
- this._showTooltip(dataPoint);
2088
- }
2089
- else {
2090
- this._hideTooltip();
2091
- }
2092
- }
2093
- }
2094
- else {
2095
- // tooltip is currently not visible
2096
- var me = this;
2097
- this.tooltipTimeout = setTimeout(function () {
2098
- me.tooltipTimeout = null;
2099
-
2100
- // show a tooltip if we have a data point
2101
- var dataPoint = me._dataPointFromXY(mouseX, mouseY);
2102
- if (dataPoint) {
2103
- me._showTooltip(dataPoint);
2104
- }
2105
- }, delay);
2106
- }
2107
- };
2108
-
2109
- /**
2110
- * Event handler for touchstart event on mobile devices
2111
- */
2112
- Graph3d.prototype._onTouchStart = function(event) {
2113
- this.touchDown = true;
2114
-
2115
- var me = this;
2116
- this.ontouchmove = function (event) {me._onTouchMove(event);};
2117
- this.ontouchend = function (event) {me._onTouchEnd(event);};
2118
- G3DaddEventListener(document, 'touchmove', me.ontouchmove);
2119
- G3DaddEventListener(document, 'touchend', me.ontouchend);
2120
-
2121
- this._onMouseDown(event);
2122
- };
2123
-
2124
- /**
2125
- * Event handler for touchmove event on mobile devices
2126
- */
2127
- Graph3d.prototype._onTouchMove = function(event) {
2128
- this._onMouseMove(event);
2129
- };
2130
-
2131
- /**
2132
- * Event handler for touchend event on mobile devices
2133
- */
2134
- Graph3d.prototype._onTouchEnd = function(event) {
2135
- this.touchDown = false;
2136
-
2137
- G3DremoveEventListener(document, 'touchmove', this.ontouchmove);
2138
- G3DremoveEventListener(document, 'touchend', this.ontouchend);
2139
-
2140
- this._onMouseUp(event);
2141
- };
2142
-
2143
-
2144
- /**
2145
- * Event handler for mouse wheel event, used to zoom the graph
2146
- * Code from http://adomas.org/javascript-mouse-wheel/
2147
- * @param {event} event The event
2148
- */
2149
- Graph3d.prototype._onWheel = function(event) {
2150
- if (!event) /* For IE. */
2151
- event = window.event;
2152
-
2153
- // retrieve delta
2154
- var delta = 0;
2155
- if (event.wheelDelta) { /* IE/Opera. */
2156
- delta = event.wheelDelta/120;
2157
- } else if (event.detail) { /* Mozilla case. */
2158
- // In Mozilla, sign of delta is different than in IE.
2159
- // Also, delta is multiple of 3.
2160
- delta = -event.detail/3;
2161
- }
2162
-
2163
- // If delta is nonzero, handle it.
2164
- // Basically, delta is now positive if wheel was scrolled up,
2165
- // and negative, if wheel was scrolled down.
2166
- if (delta) {
2167
- var oldLength = this.camera.getArmLength();
2168
- var newLength = oldLength * (1 - delta / 10);
2169
-
2170
- this.camera.setArmLength(newLength);
2171
- this.redraw();
2172
-
2173
- this._hideTooltip();
2174
- }
2175
-
2176
- // fire a cameraPositionChange event
2177
- var parameters = this.getCameraPosition();
2178
- this.emit('cameraPositionChange', parameters);
2179
-
2180
- // Prevent default actions caused by mouse wheel.
2181
- // That might be ugly, but we handle scrolls somehow
2182
- // anyway, so don't bother here..
2183
- G3DpreventDefault(event);
2184
- };
2185
-
2186
- /**
2187
- * Test whether a point lies inside given 2D triangle
2188
- * @param {Point2d} point
2189
- * @param {Point2d[]} triangle
2190
- * @return {boolean} Returns true if given point lies inside or on the edge of the triangle
2191
- * @private
2192
- */
2193
- Graph3d.prototype._insideTriangle = function (point, triangle) {
2194
- var a = triangle[0],
2195
- b = triangle[1],
2196
- c = triangle[2];
2197
-
2198
- function sign (x) {
2199
- return x > 0 ? 1 : x < 0 ? -1 : 0;
2200
- }
2201
-
2202
- var as = sign((b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x));
2203
- var bs = sign((c.x - b.x) * (point.y - b.y) - (c.y - b.y) * (point.x - b.x));
2204
- var cs = sign((a.x - c.x) * (point.y - c.y) - (a.y - c.y) * (point.x - c.x));
2205
-
2206
- // each of the three signs must be either equal to each other or zero
2207
- return (as == 0 || bs == 0 || as == bs) &&
2208
- (bs == 0 || cs == 0 || bs == cs) &&
2209
- (as == 0 || cs == 0 || as == cs);
2210
- };
2211
-
2212
- /**
2213
- * Find a data point close to given screen position (x, y)
2214
- * @param {Number} x
2215
- * @param {Number} y
2216
- * @return {Object | null} The closest data point or null if not close to any data point
2217
- * @private
2218
- */
2219
- Graph3d.prototype._dataPointFromXY = function (x, y) {
2220
- var i,
2221
- distMax = 100, // px
2222
- dataPoint = null,
2223
- closestDataPoint = null,
2224
- closestDist = null,
2225
- center = new Point2d(x, y);
2226
-
2227
- if (this.style === Graph3d.STYLE.BAR ||
2228
- this.style === Graph3d.STYLE.BARCOLOR ||
2229
- this.style === Graph3d.STYLE.BARSIZE) {
2230
- // the data points are ordered from far away to closest
2231
- for (i = this.dataPoints.length - 1; i >= 0; i--) {
2232
- dataPoint = this.dataPoints[i];
2233
- var surfaces = dataPoint.surfaces;
2234
- if (surfaces) {
2235
- for (var s = surfaces.length - 1; s >= 0; s--) {
2236
- // split each surface in two triangles, and see if the center point is inside one of these
2237
- var surface = surfaces[s];
2238
- var corners = surface.corners;
2239
- var triangle1 = [corners[0].screen, corners[1].screen, corners[2].screen];
2240
- var triangle2 = [corners[2].screen, corners[3].screen, corners[0].screen];
2241
- if (this._insideTriangle(center, triangle1) ||
2242
- this._insideTriangle(center, triangle2)) {
2243
- // return immediately at the first hit
2244
- return dataPoint;
2245
- }
2246
- }
2247
- }
2248
- }
2249
- }
2250
- else {
2251
- // find the closest data point, using distance to the center of the point on 2d screen
2252
- for (i = 0; i < this.dataPoints.length; i++) {
2253
- dataPoint = this.dataPoints[i];
2254
- var point = dataPoint.screen;
2255
- if (point) {
2256
- var distX = Math.abs(x - point.x);
2257
- var distY = Math.abs(y - point.y);
2258
- var dist = Math.sqrt(distX * distX + distY * distY);
2259
-
2260
- if ((closestDist === null || dist < closestDist) && dist < distMax) {
2261
- closestDist = dist;
2262
- closestDataPoint = dataPoint;
2263
- }
2264
- }
2265
- }
2266
- }
2267
-
2268
-
2269
- return closestDataPoint;
2270
- };
2271
-
2272
- /**
2273
- * Display a tooltip for given data point
2274
- * @param {Object} dataPoint
2275
- * @private
2276
- */
2277
- Graph3d.prototype._showTooltip = function (dataPoint) {
2278
- var content, line, dot;
2279
-
2280
- if (!this.tooltip) {
2281
- content = document.createElement('div');
2282
- content.style.position = 'absolute';
2283
- content.style.padding = '10px';
2284
- content.style.border = '1px solid #4d4d4d';
2285
- content.style.color = '#1a1a1a';
2286
- content.style.background = 'rgba(255,255,255,0.7)';
2287
- content.style.borderRadius = '2px';
2288
- content.style.boxShadow = '5px 5px 10px rgba(128,128,128,0.5)';
2289
-
2290
- line = document.createElement('div');
2291
- line.style.position = 'absolute';
2292
- line.style.height = '40px';
2293
- line.style.width = '0';
2294
- line.style.borderLeft = '1px solid #4d4d4d';
2295
-
2296
- dot = document.createElement('div');
2297
- dot.style.position = 'absolute';
2298
- dot.style.height = '0';
2299
- dot.style.width = '0';
2300
- dot.style.border = '5px solid #4d4d4d';
2301
- dot.style.borderRadius = '5px';
2302
-
2303
- this.tooltip = {
2304
- dataPoint: null,
2305
- dom: {
2306
- content: content,
2307
- line: line,
2308
- dot: dot
2309
- }
2310
- };
2311
- }
2312
- else {
2313
- content = this.tooltip.dom.content;
2314
- line = this.tooltip.dom.line;
2315
- dot = this.tooltip.dom.dot;
2316
- }
2317
-
2318
- this._hideTooltip();
2319
-
2320
- this.tooltip.dataPoint = dataPoint;
2321
- if (typeof this.showTooltip === 'function') {
2322
- content.innerHTML = this.showTooltip(dataPoint.point);
2323
- }
2324
- else {
2325
- content.innerHTML = '<table>' +
2326
- '<tr><td>x:</td><td>' + dataPoint.point.x + '</td></tr>' +
2327
- '<tr><td>y:</td><td>' + dataPoint.point.y + '</td></tr>' +
2328
- '<tr><td>z:</td><td>' + dataPoint.point.z + '</td></tr>' +
2329
- '</table>';
2330
- }
2331
-
2332
- content.style.left = '0';
2333
- content.style.top = '0';
2334
- this.frame.appendChild(content);
2335
- this.frame.appendChild(line);
2336
- this.frame.appendChild(dot);
2337
-
2338
- // calculate sizes
2339
- var contentWidth = content.offsetWidth;
2340
- var contentHeight = content.offsetHeight;
2341
- var lineHeight = line.offsetHeight;
2342
- var dotWidth = dot.offsetWidth;
2343
- var dotHeight = dot.offsetHeight;
2344
-
2345
- var left = dataPoint.screen.x - contentWidth / 2;
2346
- left = Math.min(Math.max(left, 10), this.frame.clientWidth - 10 - contentWidth);
2347
-
2348
- line.style.left = dataPoint.screen.x + 'px';
2349
- line.style.top = (dataPoint.screen.y - lineHeight) + 'px';
2350
- content.style.left = left + 'px';
2351
- content.style.top = (dataPoint.screen.y - lineHeight - contentHeight) + 'px';
2352
- dot.style.left = (dataPoint.screen.x - dotWidth / 2) + 'px';
2353
- dot.style.top = (dataPoint.screen.y - dotHeight / 2) + 'px';
2354
- };
2355
-
2356
- /**
2357
- * Hide the tooltip when displayed
2358
- * @private
2359
- */
2360
- Graph3d.prototype._hideTooltip = function () {
2361
- if (this.tooltip) {
2362
- this.tooltip.dataPoint = null;
2363
-
2364
- for (var prop in this.tooltip.dom) {
2365
- if (this.tooltip.dom.hasOwnProperty(prop)) {
2366
- var elem = this.tooltip.dom[prop];
2367
- if (elem && elem.parentNode) {
2368
- elem.parentNode.removeChild(elem);
2369
- }
2370
- }
2371
- }
2372
- }
2373
- };
2374
-
2375
-
2376
- /**
2377
- * Add and event listener. Works for all browsers
2378
- * @param {Element} element An html element
2379
- * @param {string} action The action, for example 'click',
2380
- * without the prefix 'on'
2381
- * @param {function} listener The callback function to be executed
2382
- * @param {boolean} useCapture
2383
- */
2384
- G3DaddEventListener = function(element, action, listener, useCapture) {
2385
- if (element.addEventListener) {
2386
- if (useCapture === undefined)
2387
- useCapture = false;
2388
-
2389
- if (action === 'mousewheel' && navigator.userAgent.indexOf('Firefox') >= 0) {
2390
- action = 'DOMMouseScroll'; // For Firefox
2391
- }
2392
-
2393
- element.addEventListener(action, listener, useCapture);
2394
- } else {
2395
- element.attachEvent('on' + action, listener); // IE browsers
2396
- }
2397
- };
2398
-
2399
- /**
2400
- * Remove an event listener from an element
2401
- * @param {Element} element An html dom element
2402
- * @param {string} action The name of the event, for example 'mousedown'
2403
- * @param {function} listener The listener function
2404
- * @param {boolean} useCapture
2405
- */
2406
- G3DremoveEventListener = function(element, action, listener, useCapture) {
2407
- if (element.removeEventListener) {
2408
- // non-IE browsers
2409
- if (useCapture === undefined)
2410
- useCapture = false;
2411
-
2412
- if (action === 'mousewheel' && navigator.userAgent.indexOf('Firefox') >= 0) {
2413
- action = 'DOMMouseScroll'; // For Firefox
2414
- }
2415
-
2416
- element.removeEventListener(action, listener, useCapture);
2417
- } else {
2418
- // IE browsers
2419
- element.detachEvent('on' + action, listener);
2420
- }
2421
- };
2422
-
2423
- /**
2424
- * Stop event propagation
2425
- */
2426
- G3DstopPropagation = function(event) {
2427
- if (!event)
2428
- event = window.event;
2429
-
2430
- if (event.stopPropagation) {
2431
- event.stopPropagation(); // non-IE browsers
2432
- }
2433
- else {
2434
- event.cancelBubble = true; // IE browsers
2435
- }
2436
- };
2437
-
2438
-
2439
- /**
2440
- * Cancels the event if it is cancelable, without stopping further propagation of the event.
2441
- */
2442
- G3DpreventDefault = function (event) {
2443
- if (!event)
2444
- event = window.event;
2445
-
2446
- if (event.preventDefault) {
2447
- event.preventDefault(); // non-IE browsers
2448
- }
2449
- else {
2450
- event.returnValue = false; // IE browsers
2451
- }
2452
- };
2453
-
2454
-
2455
-
2456
- /**
2457
- * @prototype Point3d
2458
- * @param {Number} x
2459
- * @param {Number} y
2460
- * @param {Number} z
2461
- */
2462
- function Point3d(x, y, z) {
2463
- this.x = x !== undefined ? x : 0;
2464
- this.y = y !== undefined ? y : 0;
2465
- this.z = z !== undefined ? z : 0;
2466
- };
2467
-
2468
- /**
2469
- * Subtract the two provided points, returns a-b
2470
- * @param {Point3d} a
2471
- * @param {Point3d} b
2472
- * @return {Point3d} a-b
2473
- */
2474
- Point3d.subtract = function(a, b) {
2475
- var sub = new Point3d();
2476
- sub.x = a.x - b.x;
2477
- sub.y = a.y - b.y;
2478
- sub.z = a.z - b.z;
2479
- return sub;
2480
- };
2481
-
2482
- /**
2483
- * Add the two provided points, returns a+b
2484
- * @param {Point3d} a
2485
- * @param {Point3d} b
2486
- * @return {Point3d} a+b
2487
- */
2488
- Point3d.add = function(a, b) {
2489
- var sum = new Point3d();
2490
- sum.x = a.x + b.x;
2491
- sum.y = a.y + b.y;
2492
- sum.z = a.z + b.z;
2493
- return sum;
2494
- };
2495
-
2496
- /**
2497
- * Calculate the average of two 3d points
2498
- * @param {Point3d} a
2499
- * @param {Point3d} b
2500
- * @return {Point3d} The average, (a+b)/2
2501
- */
2502
- Point3d.avg = function(a, b) {
2503
- return new Point3d(
2504
- (a.x + b.x) / 2,
2505
- (a.y + b.y) / 2,
2506
- (a.z + b.z) / 2
2507
- );
2508
- };
2509
-
2510
- /**
2511
- * Calculate the cross product of the two provided points, returns axb
2512
- * Documentation: http://en.wikipedia.org/wiki/Cross_product
2513
- * @param {Point3d} a
2514
- * @param {Point3d} b
2515
- * @return {Point3d} cross product axb
2516
- */
2517
- Point3d.crossProduct = function(a, b) {
2518
- var crossproduct = new Point3d();
2519
-
2520
- crossproduct.x = a.y * b.z - a.z * b.y;
2521
- crossproduct.y = a.z * b.x - a.x * b.z;
2522
- crossproduct.z = a.x * b.y - a.y * b.x;
2523
-
2524
- return crossproduct;
2525
- };
2526
-
2527
-
2528
- /**
2529
- * Rtrieve the length of the vector (or the distance from this point to the origin
2530
- * @return {Number} length
2531
- */
2532
- Point3d.prototype.length = function() {
2533
- return Math.sqrt(
2534
- this.x * this.x +
2535
- this.y * this.y +
2536
- this.z * this.z
2537
- );
2538
- };
2539
-
2540
- /**
2541
- * @prototype Point2d
2542
- */
2543
- Point2d = function (x, y) {
2544
- this.x = x !== undefined ? x : 0;
2545
- this.y = y !== undefined ? y : 0;
2546
- };
2547
-
2548
-
2549
- /**
2550
- * @class Filter
2551
- *
2552
- * @param {DataSet} data The google data table
2553
- * @param {Number} column The index of the column to be filtered
2554
- * @param {Graph} graph The graph
2555
- */
2556
- function Filter (data, column, graph) {
2557
- this.data = data;
2558
- this.column = column;
2559
- this.graph = graph; // the parent graph
2560
-
2561
- this.index = undefined;
2562
- this.value = undefined;
2563
-
2564
- // read all distinct values and select the first one
2565
- this.values = graph.getDistinctValues(data.get(), this.column);
2566
-
2567
- // sort both numeric and string values correctly
2568
- this.values.sort(function (a, b) {
2569
- return a > b ? 1 : a < b ? -1 : 0;
2570
- });
2571
-
2572
- if (this.values.length > 0) {
2573
- this.selectValue(0);
2574
- }
2575
-
2576
- // create an array with the filtered datapoints. this will be loaded afterwards
2577
- this.dataPoints = [];
2578
-
2579
- this.loaded = false;
2580
- this.onLoadCallback = undefined;
2581
-
2582
- if (graph.animationPreload) {
2583
- this.loaded = false;
2584
- this.loadInBackground();
2585
- }
2586
- else {
2587
- this.loaded = true;
2588
- }
2589
- };
2590
-
2591
-
2592
- /**
2593
- * Return the label
2594
- * @return {string} label
2595
- */
2596
- Filter.prototype.isLoaded = function() {
2597
- return this.loaded;
2598
- };
2599
-
2600
-
2601
- /**
2602
- * Return the loaded progress
2603
- * @return {Number} percentage between 0 and 100
2604
- */
2605
- Filter.prototype.getLoadedProgress = function() {
2606
- var len = this.values.length;
2607
-
2608
- var i = 0;
2609
- while (this.dataPoints[i]) {
2610
- i++;
2611
- }
2612
-
2613
- return Math.round(i / len * 100);
2614
- };
2615
-
2616
-
2617
- /**
2618
- * Return the label
2619
- * @return {string} label
2620
- */
2621
- Filter.prototype.getLabel = function() {
2622
- return this.graph.filterLabel;
2623
- };
2624
-
2625
-
2626
- /**
2627
- * Return the columnIndex of the filter
2628
- * @return {Number} columnIndex
2629
- */
2630
- Filter.prototype.getColumn = function() {
2631
- return this.column;
2632
- };
2633
-
2634
- /**
2635
- * Return the currently selected value. Returns undefined if there is no selection
2636
- * @return {*} value
2637
- */
2638
- Filter.prototype.getSelectedValue = function() {
2639
- if (this.index === undefined)
2640
- return undefined;
2641
-
2642
- return this.values[this.index];
2643
- };
2644
-
2645
- /**
2646
- * Retrieve all values of the filter
2647
- * @return {Array} values
2648
- */
2649
- Filter.prototype.getValues = function() {
2650
- return this.values;
2651
- };
2652
-
2653
- /**
2654
- * Retrieve one value of the filter
2655
- * @param {Number} index
2656
- * @return {*} value
2657
- */
2658
- Filter.prototype.getValue = function(index) {
2659
- if (index >= this.values.length)
2660
- throw 'Error: index out of range';
2661
-
2662
- return this.values[index];
2663
- };
2664
-
2665
-
2666
- /**
2667
- * Retrieve the (filtered) dataPoints for the currently selected filter index
2668
- * @param {Number} [index] (optional)
2669
- * @return {Array} dataPoints
2670
- */
2671
- Filter.prototype._getDataPoints = function(index) {
2672
- if (index === undefined)
2673
- index = this.index;
2674
-
2675
- if (index === undefined)
2676
- return [];
2677
-
2678
- var dataPoints;
2679
- if (this.dataPoints[index]) {
2680
- dataPoints = this.dataPoints[index];
2681
- }
2682
- else {
2683
- var f = {};
2684
- f.column = this.column;
2685
- f.value = this.values[index];
2686
-
2687
- var dataView = new DataView(this.data,{filter: function (item) {return (item[f.column] == f.value);}}).get();
2688
- dataPoints = this.graph._getDataPoints(dataView);
2689
-
2690
- this.dataPoints[index] = dataPoints;
2691
- }
2692
-
2693
- return dataPoints;
2694
- };
2695
-
2696
-
2697
-
2698
- /**
2699
- * Set a callback function when the filter is fully loaded.
2700
- */
2701
- Filter.prototype.setOnLoadCallback = function(callback) {
2702
- this.onLoadCallback = callback;
2703
- };
2704
-
2705
-
2706
- /**
2707
- * Add a value to the list with available values for this filter
2708
- * No double entries will be created.
2709
- * @param {Number} index
2710
- */
2711
- Filter.prototype.selectValue = function(index) {
2712
- if (index >= this.values.length)
2713
- throw 'Error: index out of range';
2714
-
2715
- this.index = index;
2716
- this.value = this.values[index];
2717
- };
2718
-
2719
- /**
2720
- * Load all filtered rows in the background one by one
2721
- * Start this method without providing an index!
2722
- */
2723
- Filter.prototype.loadInBackground = function(index) {
2724
- if (index === undefined)
2725
- index = 0;
2726
-
2727
- var frame = this.graph.frame;
2728
-
2729
- if (index < this.values.length) {
2730
- var dataPointsTemp = this._getDataPoints(index);
2731
- //this.graph.redrawInfo(); // TODO: not neat
2732
-
2733
- // create a progress box
2734
- if (frame.progress === undefined) {
2735
- frame.progress = document.createElement('DIV');
2736
- frame.progress.style.position = 'absolute';
2737
- frame.progress.style.color = 'gray';
2738
- frame.appendChild(frame.progress);
2739
- }
2740
- var progress = this.getLoadedProgress();
2741
- frame.progress.innerHTML = 'Loading animation... ' + progress + '%';
2742
- // TODO: this is no nice solution...
2743
- frame.progress.style.bottom = Graph3d.px(60); // TODO: use height of slider
2744
- frame.progress.style.left = Graph3d.px(10);
2745
-
2746
- var me = this;
2747
- setTimeout(function() {me.loadInBackground(index+1);}, 10);
2748
- this.loaded = false;
2749
- }
2750
- else {
2751
- this.loaded = true;
2752
-
2753
- // remove the progress box
2754
- if (frame.progress !== undefined) {
2755
- frame.removeChild(frame.progress);
2756
- frame.progress = undefined;
2757
- }
2758
-
2759
- if (this.onLoadCallback)
2760
- this.onLoadCallback();
2761
- }
2762
- };
2763
-
2764
-
2765
-
2766
- /**
2767
- * @prototype StepNumber
2768
- * The class StepNumber is an iterator for Numbers. You provide a start and end
2769
- * value, and a best step size. StepNumber itself rounds to fixed values and
2770
- * a finds the step that best fits the provided step.
2771
- *
2772
- * If prettyStep is true, the step size is chosen as close as possible to the
2773
- * provided step, but being a round value like 1, 2, 5, 10, 20, 50, ....
2774
- *
2775
- * Example usage:
2776
- * var step = new StepNumber(0, 10, 2.5, true);
2777
- * step.start();
2778
- * while (!step.end()) {
2779
- * alert(step.getCurrent());
2780
- * step.next();
2781
- * }
2782
- *
2783
- * Version: 1.0
2784
- *
2785
- * @param {Number} start The start value
2786
- * @param {Number} end The end value
2787
- * @param {Number} step Optional. Step size. Must be a positive value.
2788
- * @param {boolean} prettyStep Optional. If true, the step size is rounded
2789
- * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
2790
- */
2791
- StepNumber = function (start, end, step, prettyStep) {
2792
- // set default values
2793
- this._start = 0;
2794
- this._end = 0;
2795
- this._step = 1;
2796
- this.prettyStep = true;
2797
- this.precision = 5;
2798
-
2799
- this._current = 0;
2800
- this.setRange(start, end, step, prettyStep);
2801
- };
2802
-
2803
- /**
2804
- * Set a new range: start, end and step.
2805
- *
2806
- * @param {Number} start The start value
2807
- * @param {Number} end The end value
2808
- * @param {Number} step Optional. Step size. Must be a positive value.
2809
- * @param {boolean} prettyStep Optional. If true, the step size is rounded
2810
- * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
2811
- */
2812
- StepNumber.prototype.setRange = function(start, end, step, prettyStep) {
2813
- this._start = start ? start : 0;
2814
- this._end = end ? end : 0;
2815
-
2816
- this.setStep(step, prettyStep);
2817
- };
2818
-
2819
- /**
2820
- * Set a new step size
2821
- * @param {Number} step New step size. Must be a positive value
2822
- * @param {boolean} prettyStep Optional. If true, the provided step is rounded
2823
- * to a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
2824
- */
2825
- StepNumber.prototype.setStep = function(step, prettyStep) {
2826
- if (step === undefined || step <= 0)
2827
- return;
2828
-
2829
- if (prettyStep !== undefined)
2830
- this.prettyStep = prettyStep;
2831
-
2832
- if (this.prettyStep === true)
2833
- this._step = StepNumber.calculatePrettyStep(step);
2834
- else
2835
- this._step = step;
2836
- };
2837
-
2838
- /**
2839
- * Calculate a nice step size, closest to the desired step size.
2840
- * Returns a value in one of the ranges 1*10^n, 2*10^n, or 5*10^n, where n is an
2841
- * integer Number. For example 1, 2, 5, 10, 20, 50, etc...
2842
- * @param {Number} step Desired step size
2843
- * @return {Number} Nice step size
2844
- */
2845
- StepNumber.calculatePrettyStep = function (step) {
2846
- var log10 = function (x) {return Math.log(x) / Math.LN10;};
2847
-
2848
- // try three steps (multiple of 1, 2, or 5
2849
- var step1 = Math.pow(10, Math.round(log10(step))),
2850
- step2 = 2 * Math.pow(10, Math.round(log10(step / 2))),
2851
- step5 = 5 * Math.pow(10, Math.round(log10(step / 5)));
2852
-
2853
- // choose the best step (closest to minimum step)
2854
- var prettyStep = step1;
2855
- if (Math.abs(step2 - step) <= Math.abs(prettyStep - step)) prettyStep = step2;
2856
- if (Math.abs(step5 - step) <= Math.abs(prettyStep - step)) prettyStep = step5;
2857
-
2858
- // for safety
2859
- if (prettyStep <= 0) {
2860
- prettyStep = 1;
2861
- }
2862
-
2863
- return prettyStep;
2864
- };
2865
-
2866
- /**
2867
- * returns the current value of the step
2868
- * @return {Number} current value
2869
- */
2870
- StepNumber.prototype.getCurrent = function () {
2871
- return parseFloat(this._current.toPrecision(this.precision));
2872
- };
2873
-
2874
- /**
2875
- * returns the current step size
2876
- * @return {Number} current step size
2877
- */
2878
- StepNumber.prototype.getStep = function () {
2879
- return this._step;
2880
- };
2881
-
2882
- /**
2883
- * Set the current value to the largest value smaller than start, which
2884
- * is a multiple of the step size
2885
- */
2886
- StepNumber.prototype.start = function() {
2887
- this._current = this._start - this._start % this._step;
2888
- };
2889
-
2890
- /**
2891
- * Do a step, add the step size to the current value
2892
- */
2893
- StepNumber.prototype.next = function () {
2894
- this._current += this._step;
2895
- };
2896
-
2897
- /**
2898
- * Returns true whether the end is reached
2899
- * @return {boolean} True if the current value has passed the end value.
2900
- */
2901
- StepNumber.prototype.end = function () {
2902
- return (this._current > this._end);
2903
- };
2904
-
2905
-
2906
- /**
2907
- * @constructor Slider
2908
- *
2909
- * An html slider control with start/stop/prev/next buttons
2910
- * @param {Element} container The element where the slider will be created
2911
- * @param {Object} options Available options:
2912
- * {boolean} visible If true (default) the
2913
- * slider is visible.
2914
- */
2915
- function Slider(container, options) {
2916
- if (container === undefined) {
2917
- throw 'Error: No container element defined';
2918
- }
2919
- this.container = container;
2920
- this.visible = (options && options.visible != undefined) ? options.visible : true;
2921
-
2922
- if (this.visible) {
2923
- this.frame = document.createElement('DIV');
2924
- //this.frame.style.backgroundColor = '#E5E5E5';
2925
- this.frame.style.width = '100%';
2926
- this.frame.style.position = 'relative';
2927
- this.container.appendChild(this.frame);
2928
-
2929
- this.frame.prev = document.createElement('INPUT');
2930
- this.frame.prev.type = 'BUTTON';
2931
- this.frame.prev.value = 'Prev';
2932
- this.frame.appendChild(this.frame.prev);
2933
-
2934
- this.frame.play = document.createElement('INPUT');
2935
- this.frame.play.type = 'BUTTON';
2936
- this.frame.play.value = 'Play';
2937
- this.frame.appendChild(this.frame.play);
2938
-
2939
- this.frame.next = document.createElement('INPUT');
2940
- this.frame.next.type = 'BUTTON';
2941
- this.frame.next.value = 'Next';
2942
- this.frame.appendChild(this.frame.next);
2943
-
2944
- this.frame.bar = document.createElement('INPUT');
2945
- this.frame.bar.type = 'BUTTON';
2946
- this.frame.bar.style.position = 'absolute';
2947
- this.frame.bar.style.border = '1px solid red';
2948
- this.frame.bar.style.width = '100px';
2949
- this.frame.bar.style.height = '6px';
2950
- this.frame.bar.style.borderRadius = '2px';
2951
- this.frame.bar.style.MozBorderRadius = '2px';
2952
- this.frame.bar.style.border = '1px solid #7F7F7F';
2953
- this.frame.bar.style.backgroundColor = '#E5E5E5';
2954
- this.frame.appendChild(this.frame.bar);
2955
-
2956
- this.frame.slide = document.createElement('INPUT');
2957
- this.frame.slide.type = 'BUTTON';
2958
- this.frame.slide.style.margin = '0px';
2959
- this.frame.slide.value = ' ';
2960
- this.frame.slide.style.position = 'relative';
2961
- this.frame.slide.style.left = '-100px';
2962
- this.frame.appendChild(this.frame.slide);
2963
-
2964
- // create events
2965
- var me = this;
2966
- this.frame.slide.onmousedown = function (event) {me._onMouseDown(event);};
2967
- this.frame.prev.onclick = function (event) {me.prev(event);};
2968
- this.frame.play.onclick = function (event) {me.togglePlay(event);};
2969
- this.frame.next.onclick = function (event) {me.next(event);};
2970
- }
2971
-
2972
- this.onChangeCallback = undefined;
2973
-
2974
- this.values = [];
2975
- this.index = undefined;
2976
-
2977
- this.playTimeout = undefined;
2978
- this.playInterval = 1000; // milliseconds
2979
- this.playLoop = true;
2980
- };
2981
-
2982
- /**
2983
- * Select the previous index
2984
- */
2985
- Slider.prototype.prev = function() {
2986
- var index = this.getIndex();
2987
- if (index > 0) {
2988
- index--;
2989
- this.setIndex(index);
2990
- }
2991
- };
2992
-
2993
- /**
2994
- * Select the next index
2995
- */
2996
- Slider.prototype.next = function() {
2997
- var index = this.getIndex();
2998
- if (index < this.values.length - 1) {
2999
- index++;
3000
- this.setIndex(index);
3001
- }
3002
- };
3003
-
3004
- /**
3005
- * Select the next index
3006
- */
3007
- Slider.prototype.playNext = function() {
3008
- var start = new Date();
3009
-
3010
- var index = this.getIndex();
3011
- if (index < this.values.length - 1) {
3012
- index++;
3013
- this.setIndex(index);
3014
- }
3015
- else if (this.playLoop) {
3016
- // jump to the start
3017
- index = 0;
3018
- this.setIndex(index);
3019
- }
3020
-
3021
- var end = new Date();
3022
- var diff = (end - start);
3023
-
3024
- // calculate how much time it to to set the index and to execute the callback
3025
- // function.
3026
- var interval = Math.max(this.playInterval - diff, 0);
3027
- // document.title = diff // TODO: cleanup
3028
-
3029
- var me = this;
3030
- this.playTimeout = setTimeout(function() {me.playNext();}, interval);
3031
- };
3032
-
3033
- /**
3034
- * Toggle start or stop playing
3035
- */
3036
- Slider.prototype.togglePlay = function() {
3037
- if (this.playTimeout === undefined) {
3038
- this.play();
3039
- } else {
3040
- this.stop();
3041
- }
3042
- };
3043
-
3044
- /**
3045
- * Start playing
3046
- */
3047
- Slider.prototype.play = function() {
3048
- // Test whether already playing
3049
- if (this.playTimeout) return;
3050
-
3051
- this.playNext();
3052
-
3053
- if (this.frame) {
3054
- this.frame.play.value = 'Stop';
3055
- }
3056
- };
3057
-
3058
- /**
3059
- * Stop playing
3060
- */
3061
- Slider.prototype.stop = function() {
3062
- clearInterval(this.playTimeout);
3063
- this.playTimeout = undefined;
3064
-
3065
- if (this.frame) {
3066
- this.frame.play.value = 'Play';
3067
- }
3068
- };
3069
-
3070
- /**
3071
- * Set a callback function which will be triggered when the value of the
3072
- * slider bar has changed.
3073
- */
3074
- Slider.prototype.setOnChangeCallback = function(callback) {
3075
- this.onChangeCallback = callback;
3076
- };
3077
-
3078
- /**
3079
- * Set the interval for playing the list
3080
- * @param {Number} interval The interval in milliseconds
3081
- */
3082
- Slider.prototype.setPlayInterval = function(interval) {
3083
- this.playInterval = interval;
3084
- };
3085
-
3086
- /**
3087
- * Retrieve the current play interval
3088
- * @return {Number} interval The interval in milliseconds
3089
- */
3090
- Slider.prototype.getPlayInterval = function(interval) {
3091
- return this.playInterval;
3092
- };
3093
-
3094
- /**
3095
- * Set looping on or off
3096
- * @pararm {boolean} doLoop If true, the slider will jump to the start when
3097
- * the end is passed, and will jump to the end
3098
- * when the start is passed.
3099
- */
3100
- Slider.prototype.setPlayLoop = function(doLoop) {
3101
- this.playLoop = doLoop;
3102
- };
3103
-
3104
-
3105
- /**
3106
- * Execute the onchange callback function
3107
- */
3108
- Slider.prototype.onChange = function() {
3109
- if (this.onChangeCallback !== undefined) {
3110
- this.onChangeCallback();
3111
- }
3112
- };
3113
-
3114
- /**
3115
- * redraw the slider on the correct place
3116
- */
3117
- Slider.prototype.redraw = function() {
3118
- if (this.frame) {
3119
- // resize the bar
3120
- this.frame.bar.style.top = (this.frame.clientHeight/2 -
3121
- this.frame.bar.offsetHeight/2) + 'px';
3122
- this.frame.bar.style.width = (this.frame.clientWidth -
3123
- this.frame.prev.clientWidth -
3124
- this.frame.play.clientWidth -
3125
- this.frame.next.clientWidth - 30) + 'px';
3126
-
3127
- // position the slider button
3128
- var left = this.indexToLeft(this.index);
3129
- this.frame.slide.style.left = (left) + 'px';
3130
- }
3131
- };
3132
-
3133
-
3134
- /**
3135
- * Set the list with values for the slider
3136
- * @param {Array} values A javascript array with values (any type)
3137
- */
3138
- Slider.prototype.setValues = function(values) {
3139
- this.values = values;
3140
-
3141
- if (this.values.length > 0)
3142
- this.setIndex(0);
3143
- else
3144
- this.index = undefined;
3145
- };
3146
-
3147
- /**
3148
- * Select a value by its index
3149
- * @param {Number} index
3150
- */
3151
- Slider.prototype.setIndex = function(index) {
3152
- if (index < this.values.length) {
3153
- this.index = index;
3154
-
3155
- this.redraw();
3156
- this.onChange();
3157
- }
3158
- else {
3159
- throw 'Error: index out of range';
3160
- }
3161
- };
3162
-
3163
- /**
3164
- * retrieve the index of the currently selected vaue
3165
- * @return {Number} index
3166
- */
3167
- Slider.prototype.getIndex = function() {
3168
- return this.index;
3169
- };
3170
-
3171
-
3172
- /**
3173
- * retrieve the currently selected value
3174
- * @return {*} value
3175
- */
3176
- Slider.prototype.get = function() {
3177
- return this.values[this.index];
3178
- };
3179
-
3180
-
3181
- Slider.prototype._onMouseDown = function(event) {
3182
- // only react on left mouse button down
3183
- var leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
3184
- if (!leftButtonDown) return;
3185
-
3186
- this.startClientX = event.clientX;
3187
- this.startSlideX = parseFloat(this.frame.slide.style.left);
3188
-
3189
- this.frame.style.cursor = 'move';
3190
-
3191
- // add event listeners to handle moving the contents
3192
- // we store the function onmousemove and onmouseup in the graph, so we can
3193
- // remove the eventlisteners lateron in the function mouseUp()
3194
- var me = this;
3195
- this.onmousemove = function (event) {me._onMouseMove(event);};
3196
- this.onmouseup = function (event) {me._onMouseUp(event);};
3197
- G3DaddEventListener(document, 'mousemove', this.onmousemove);
3198
- G3DaddEventListener(document, 'mouseup', this.onmouseup);
3199
- G3DpreventDefault(event);
3200
- };
3201
-
3202
-
3203
- Slider.prototype.leftToIndex = function (left) {
3204
- var width = parseFloat(this.frame.bar.style.width) -
3205
- this.frame.slide.clientWidth - 10;
3206
- var x = left - 3;
3207
-
3208
- var index = Math.round(x / width * (this.values.length-1));
3209
- if (index < 0) index = 0;
3210
- if (index > this.values.length-1) index = this.values.length-1;
3211
-
3212
- return index;
3213
- };
3214
-
3215
- Slider.prototype.indexToLeft = function (index) {
3216
- var width = parseFloat(this.frame.bar.style.width) -
3217
- this.frame.slide.clientWidth - 10;
3218
-
3219
- var x = index / (this.values.length-1) * width;
3220
- var left = x + 3;
3221
-
3222
- return left;
3223
- };
3224
-
3225
-
3226
-
3227
- Slider.prototype._onMouseMove = function (event) {
3228
- var diff = event.clientX - this.startClientX;
3229
- var x = this.startSlideX + diff;
3230
-
3231
- var index = this.leftToIndex(x);
3232
-
3233
- this.setIndex(index);
3234
-
3235
- G3DpreventDefault();
3236
- };
3237
-
3238
-
3239
- Slider.prototype._onMouseUp = function (event) {
3240
- this.frame.style.cursor = 'auto';
3241
-
3242
- // remove event listeners
3243
- G3DremoveEventListener(document, 'mousemove', this.onmousemove);
3244
- G3DremoveEventListener(document, 'mouseup', this.onmouseup);
3245
-
3246
- G3DpreventDefault();
3247
- };
3248
-
3249
-
3250
-
3251
- /**--------------------------------------------------------------------------**/
3252
-
3253
-
3254
-
3255
- /**
3256
- * Retrieve the absolute left value of a DOM element
3257
- * @param {Element} elem A dom element, for example a div
3258
- * @return {Number} left The absolute left position of this element
3259
- * in the browser page.
3260
- */
3261
- getAbsoluteLeft = function(elem) {
3262
- var left = 0;
3263
- while( elem !== null ) {
3264
- left += elem.offsetLeft;
3265
- left -= elem.scrollLeft;
3266
- elem = elem.offsetParent;
3267
- }
3268
- return left;
3269
- };
3270
-
3271
- /**
3272
- * Retrieve the absolute top value of a DOM element
3273
- * @param {Element} elem A dom element, for example a div
3274
- * @return {Number} top The absolute top position of this element
3275
- * in the browser page.
3276
- */
3277
- getAbsoluteTop = function(elem) {
3278
- var top = 0;
3279
- while( elem !== null ) {
3280
- top += elem.offsetTop;
3281
- top -= elem.scrollTop;
3282
- elem = elem.offsetParent;
3283
- }
3284
- return top;
3285
- };
3286
-
3287
- /**
3288
- * Get the horizontal mouse position from a mouse event
3289
- * @param {Event} event
3290
- * @return {Number} mouse x
3291
- */
3292
- getMouseX = function(event) {
3293
- if ('clientX' in event) return event.clientX;
3294
- return event.targetTouches[0] && event.targetTouches[0].clientX || 0;
3295
- };
3296
-
3297
- /**
3298
- * Get the vertical mouse position from a mouse event
3299
- * @param {Event} event
3300
- * @return {Number} mouse y
3301
- */
3302
- getMouseY = function(event) {
3303
- if ('clientY' in event) return event.clientY;
3304
- return event.targetTouches[0] && event.targetTouches[0].clientY || 0;
3305
- };
3306
-