vis-rails 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
-