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.
- checksums.yaml +4 -4
- data/lib/vis/rails/version.rb +1 -1
- data/vendor/assets/javascripts/vis.js +26 -26
- metadata +16 -85
- data/vendor/assets/vis/DataSet.js +0 -926
- data/vendor/assets/vis/DataView.js +0 -283
- data/vendor/assets/vis/graph/Edge.js +0 -957
- data/vendor/assets/vis/graph/Graph.js +0 -2291
- data/vendor/assets/vis/graph/Groups.js +0 -80
- data/vendor/assets/vis/graph/Images.js +0 -41
- data/vendor/assets/vis/graph/Node.js +0 -966
- data/vendor/assets/vis/graph/Popup.js +0 -132
- data/vendor/assets/vis/graph/css/graph-manipulation.css +0 -128
- data/vendor/assets/vis/graph/css/graph-navigation.css +0 -66
- data/vendor/assets/vis/graph/dotparser.js +0 -829
- data/vendor/assets/vis/graph/graphMixins/ClusterMixin.js +0 -1143
- data/vendor/assets/vis/graph/graphMixins/HierarchicalLayoutMixin.js +0 -311
- data/vendor/assets/vis/graph/graphMixins/ManipulationMixin.js +0 -576
- data/vendor/assets/vis/graph/graphMixins/MixinLoader.js +0 -199
- data/vendor/assets/vis/graph/graphMixins/NavigationMixin.js +0 -205
- data/vendor/assets/vis/graph/graphMixins/SectorsMixin.js +0 -552
- data/vendor/assets/vis/graph/graphMixins/SelectionMixin.js +0 -648
- data/vendor/assets/vis/graph/graphMixins/physics/BarnesHut.js +0 -398
- data/vendor/assets/vis/graph/graphMixins/physics/HierarchialRepulsion.js +0 -64
- data/vendor/assets/vis/graph/graphMixins/physics/PhysicsMixin.js +0 -697
- data/vendor/assets/vis/graph/graphMixins/physics/Repulsion.js +0 -66
- data/vendor/assets/vis/graph/img/acceptDeleteIcon.png +0 -0
- data/vendor/assets/vis/graph/img/addNodeIcon.png +0 -0
- data/vendor/assets/vis/graph/img/backIcon.png +0 -0
- data/vendor/assets/vis/graph/img/connectIcon.png +0 -0
- data/vendor/assets/vis/graph/img/cross.png +0 -0
- data/vendor/assets/vis/graph/img/cross2.png +0 -0
- data/vendor/assets/vis/graph/img/deleteIcon.png +0 -0
- data/vendor/assets/vis/graph/img/downArrow.png +0 -0
- data/vendor/assets/vis/graph/img/editIcon.png +0 -0
- data/vendor/assets/vis/graph/img/leftArrow.png +0 -0
- data/vendor/assets/vis/graph/img/minus.png +0 -0
- data/vendor/assets/vis/graph/img/plus.png +0 -0
- data/vendor/assets/vis/graph/img/rightArrow.png +0 -0
- data/vendor/assets/vis/graph/img/upArrow.png +0 -0
- data/vendor/assets/vis/graph/img/zoomExtends.png +0 -0
- data/vendor/assets/vis/graph/shapes.js +0 -225
- data/vendor/assets/vis/graph3d/Graph3d.js +0 -3306
- data/vendor/assets/vis/module/exports.js +0 -65
- data/vendor/assets/vis/module/header.js +0 -24
- data/vendor/assets/vis/module/imports.js +0 -31
- data/vendor/assets/vis/shim.js +0 -252
- data/vendor/assets/vis/timeline/Range.js +0 -532
- data/vendor/assets/vis/timeline/TimeStep.js +0 -466
- data/vendor/assets/vis/timeline/Timeline.js +0 -851
- data/vendor/assets/vis/timeline/component/Component.js +0 -52
- data/vendor/assets/vis/timeline/component/CurrentTime.js +0 -128
- data/vendor/assets/vis/timeline/component/CustomTime.js +0 -182
- data/vendor/assets/vis/timeline/component/Group.js +0 -470
- data/vendor/assets/vis/timeline/component/ItemSet.js +0 -1332
- data/vendor/assets/vis/timeline/component/TimeAxis.js +0 -389
- data/vendor/assets/vis/timeline/component/css/animation.css +0 -33
- data/vendor/assets/vis/timeline/component/css/currenttime.css +0 -5
- data/vendor/assets/vis/timeline/component/css/customtime.css +0 -6
- data/vendor/assets/vis/timeline/component/css/item.css +0 -107
- data/vendor/assets/vis/timeline/component/css/itemset.css +0 -33
- data/vendor/assets/vis/timeline/component/css/labelset.css +0 -36
- data/vendor/assets/vis/timeline/component/css/panel.css +0 -71
- data/vendor/assets/vis/timeline/component/css/timeaxis.css +0 -48
- data/vendor/assets/vis/timeline/component/css/timeline.css +0 -2
- data/vendor/assets/vis/timeline/component/item/Item.js +0 -139
- data/vendor/assets/vis/timeline/component/item/ItemBox.js +0 -230
- data/vendor/assets/vis/timeline/component/item/ItemPoint.js +0 -190
- data/vendor/assets/vis/timeline/component/item/ItemRange.js +0 -262
- data/vendor/assets/vis/timeline/component/item/ItemRangeOverflow.js +0 -57
- data/vendor/assets/vis/timeline/img/delete.png +0 -0
- data/vendor/assets/vis/timeline/stack.js +0 -112
- 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
|
-
};
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -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
|
-
|