UNPKG

74.8 kBJavaScriptView Raw
1/**
2 * @license Highcharts JS v5.0.0 (2016-09-29)
3 *
4 * 3D features for Highcharts JS
5 *
6 * @license: www.highcharts.com/license
7 */
8(function(factory) {
9 if (typeof module === 'object' && module.exports) {
10 module.exports = factory;
11 } else {
12 factory(Highcharts);
13 }
14}(function(Highcharts) {
15 (function(H) {
16 /**
17 * (c) 2010-2016 Torstein Honsi
18 *
19 * License: www.highcharts.com/license
20 */
21 'use strict';
22 /**
23 * Mathematical Functionility
24 */
25 var deg2rad = H.deg2rad,
26 pick = H.pick;
27 /**
28 * Apply 3-D rotation
29 * Euler Angles (XYZ): cosA = cos(Alfa|Roll), cosB = cos(Beta|Pitch), cosG = cos(Gamma|Yaw)
30 *
31 * Composite rotation:
32 * | cosB * cosG | cosB * sinG | -sinB |
33 * | sinA * sinB * cosG - cosA * sinG | sinA * sinB * sinG + cosA * cosG | sinA * cosB |
34 * | cosA * sinB * cosG + sinA * sinG | cosA * sinB * sinG - sinA * cosG | cosA * cosB |
35 *
36 * Now, Gamma/Yaw is not used (angle=0), so we assume cosG = 1 and sinG = 0, so we get:
37 * | cosB | 0 | - sinB |
38 * | sinA * sinB | cosA | sinA * cosB |
39 * | cosA * sinB | - sinA | cosA * cosB |
40 *
41 * But in browsers, y is reversed, so we get sinA => -sinA. The general result is:
42 * | cosB | 0 | - sinB | | x | | px |
43 * | - sinA * sinB | cosA | - sinA * cosB | x | y | = | py |
44 * | cosA * sinB | sinA | cosA * cosB | | z | | pz |
45 */
46 function rotate3D(x, y, z, angles) {
47 return {
48 x: angles.cosB * x - angles.sinB * z,
49 y: -angles.sinA * angles.sinB * x + angles.cosA * y - angles.cosB * angles.sinA * z,
50 z: angles.cosA * angles.sinB * x + angles.sinA * y + angles.cosA * angles.cosB * z
51 };
52 }
53
54 function perspective3D(coordinate, origin, distance) {
55 var projection = ((distance > 0) && (distance < Number.POSITIVE_INFINITY)) ? distance / (coordinate.z + origin.z + distance) : 1;
56 return {
57 x: coordinate.x * projection,
58 y: coordinate.y * projection
59 };
60 }
61
62 /**
63 * Transforms a given array of points according to the angles in chart.options.
64 * Parameters:
65 * - points: the array of points
66 * - chart: the chart
67 * - insidePlotArea: wether to verifiy the points are inside the plotArea
68 * Returns:
69 * - an array of transformed points
70 */
71 H.perspective = function(points, chart, insidePlotArea) {
72 var options3d = chart.options.chart.options3d,
73 inverted = insidePlotArea ? chart.inverted : false,
74 origin = {
75 x: chart.plotWidth / 2,
76 y: chart.plotHeight / 2,
77 z: options3d.depth / 2,
78 vd: pick(options3d.depth, 1) * pick(options3d.viewDistance, 0)
79 },
80 scale = chart.scale3d || 1,
81 beta = deg2rad * options3d.beta * (inverted ? -1 : 1),
82 alpha = deg2rad * options3d.alpha * (inverted ? -1 : 1),
83 angles = {
84 cosA: Math.cos(alpha),
85 cosB: Math.cos(-beta),
86 sinA: Math.sin(alpha),
87 sinB: Math.sin(-beta)
88 };
89
90 if (!insidePlotArea) {
91 origin.x += chart.plotLeft;
92 origin.y += chart.plotTop;
93 }
94
95 // Transform each point
96 return H.map(points, function(point) {
97 var rotated = rotate3D(
98 (inverted ? point.y : point.x) - origin.x,
99 (inverted ? point.x : point.y) - origin.y,
100 (point.z || 0) - origin.z,
101 angles
102 ),
103 coordinate = perspective3D(rotated, origin, origin.vd); // Apply perspective
104
105 // Apply translation
106 coordinate.x = coordinate.x * scale + origin.x;
107 coordinate.y = coordinate.y * scale + origin.y;
108 coordinate.z = rotated.z * scale + origin.z;
109
110 return {
111 x: (inverted ? coordinate.y : coordinate.x),
112 y: (inverted ? coordinate.x : coordinate.y),
113 z: coordinate.z
114 };
115 });
116 };
117
118 }(Highcharts));
119 (function(H) {
120 /**
121 * (c) 2010-2016 Torstein Honsi
122 *
123 * License: www.highcharts.com/license
124 */
125 'use strict';
126 var cos = Math.cos,
127 PI = Math.PI,
128 sin = Math.sin;
129
130
131 var animObject = H.animObject,
132 charts = H.charts,
133 color = H.color,
134 defined = H.defined,
135 deg2rad = H.deg2rad,
136 each = H.each,
137 extend = H.extend,
138 inArray = H.inArray,
139 map = H.map,
140 merge = H.merge,
141 perspective = H.perspective,
142 pick = H.pick,
143 SVGElement = H.SVGElement,
144 SVGRenderer = H.SVGRenderer,
145 wrap = H.wrap;
146 /***
147 EXTENSION TO THE SVG-RENDERER TO ENABLE 3D SHAPES
148 ***/
149 ////// HELPER METHODS //////
150
151 var dFactor = (4 * (Math.sqrt(2) - 1) / 3) / (PI / 2);
152
153
154 //Shoelace algorithm -- http://en.wikipedia.org/wiki/Shoelace_formula
155 function shapeArea(vertexes) {
156 var area = 0,
157 i,
158 j;
159 for (i = 0; i < vertexes.length; i++) {
160 j = (i + 1) % vertexes.length;
161 area += vertexes[i].x * vertexes[j].y - vertexes[j].x * vertexes[i].y;
162 }
163 return area / 2;
164 }
165
166 function averageZ(vertexes) {
167 var z = 0,
168 i;
169 for (i = 0; i < vertexes.length; i++) {
170 z += vertexes[i].z;
171 }
172 return vertexes.length ? z / vertexes.length : 0;
173 }
174
175 /** Method to construct a curved path
176 * Can 'wrap' around more then 180 degrees
177 */
178 function curveTo(cx, cy, rx, ry, start, end, dx, dy) {
179 var result = [],
180 arcAngle = end - start;
181 if ((end > start) && (end - start > Math.PI / 2 + 0.0001)) {
182 result = result.concat(curveTo(cx, cy, rx, ry, start, start + (Math.PI / 2), dx, dy));
183 result = result.concat(curveTo(cx, cy, rx, ry, start + (Math.PI / 2), end, dx, dy));
184 return result;
185 }
186 if ((end < start) && (start - end > Math.PI / 2 + 0.0001)) {
187 result = result.concat(curveTo(cx, cy, rx, ry, start, start - (Math.PI / 2), dx, dy));
188 result = result.concat(curveTo(cx, cy, rx, ry, start - (Math.PI / 2), end, dx, dy));
189 return result;
190 }
191 return [
192 'C',
193 cx + (rx * Math.cos(start)) - ((rx * dFactor * arcAngle) * Math.sin(start)) + dx,
194 cy + (ry * Math.sin(start)) + ((ry * dFactor * arcAngle) * Math.cos(start)) + dy,
195 cx + (rx * Math.cos(end)) + ((rx * dFactor * arcAngle) * Math.sin(end)) + dx,
196 cy + (ry * Math.sin(end)) - ((ry * dFactor * arcAngle) * Math.cos(end)) + dy,
197
198 cx + (rx * Math.cos(end)) + dx,
199 cy + (ry * Math.sin(end)) + dy
200 ];
201 }
202
203
204
205 SVGRenderer.prototype.toLinePath = function(points, closed) {
206 var result = [];
207
208 // Put "L x y" for each point
209 each(points, function(point) {
210 result.push('L', point.x, point.y);
211 });
212
213 if (points.length) {
214 // Set the first element to M
215 result[0] = 'M';
216
217 // If it is a closed line, add Z
218 if (closed) {
219 result.push('Z');
220 }
221 }
222
223 return result;
224 };
225
226 ////// CUBOIDS //////
227 SVGRenderer.prototype.cuboid = function(shapeArgs) {
228
229 var result = this.g(),
230 paths = this.cuboidPath(shapeArgs);
231
232
233 result.attr({
234 'stroke-linejoin': 'round'
235 });
236
237
238 // create the 3 sides
239 result.front = this.path(paths[0]).attr({
240 'class': 'highcharts-3d-front',
241 zIndex: paths[3]
242 }).add(result);
243 result.top = this.path(paths[1]).attr({
244 'class': 'highcharts-3d-top',
245 zIndex: paths[4]
246 }).add(result);
247 result.side = this.path(paths[2]).attr({
248 'class': 'highcharts-3d-side',
249 zIndex: paths[5]
250 }).add(result);
251
252 // apply the fill everywhere, the top a bit brighter, the side a bit darker
253 result.fillSetter = function(fill) {
254 this.front.attr({
255 fill: fill
256 });
257 this.top.attr({
258 fill: color(fill).brighten(0.1).get()
259 });
260 this.side.attr({
261 fill: color(fill).brighten(-0.1).get()
262 });
263
264 this.color = fill;
265 return this;
266 };
267
268 // apply opacaity everywhere
269 result.opacitySetter = function(opacity) {
270 this.front.attr({
271 opacity: opacity
272 });
273 this.top.attr({
274 opacity: opacity
275 });
276 this.side.attr({
277 opacity: opacity
278 });
279 return this;
280 };
281
282 result.attr = function(args) {
283 if (args.shapeArgs || defined(args.x)) {
284 var shapeArgs = args.shapeArgs || args;
285 var paths = this.renderer.cuboidPath(shapeArgs);
286 this.front.attr({
287 d: paths[0],
288 zIndex: paths[3]
289 });
290 this.top.attr({
291 d: paths[1],
292 zIndex: paths[4]
293 });
294 this.side.attr({
295 d: paths[2],
296 zIndex: paths[5]
297 });
298 } else {
299 return H.SVGElement.prototype.attr.call(this, args); // getter returns value
300 }
301
302 return this;
303 };
304
305 result.animate = function(args, duration, complete) {
306 if (defined(args.x) && defined(args.y)) {
307 var paths = this.renderer.cuboidPath(args);
308 this.front.attr({
309 zIndex: paths[3]
310 }).animate({
311 d: paths[0]
312 }, duration, complete);
313 this.top.attr({
314 zIndex: paths[4]
315 }).animate({
316 d: paths[1]
317 }, duration, complete);
318 this.side.attr({
319 zIndex: paths[5]
320 }).animate({
321 d: paths[2]
322 }, duration, complete);
323 this.attr({
324 zIndex: -paths[6] // #4774
325 });
326 } else if (args.opacity) {
327 this.front.animate(args, duration, complete);
328 this.top.animate(args, duration, complete);
329 this.side.animate(args, duration, complete);
330 } else {
331 SVGElement.prototype.animate.call(this, args, duration, complete);
332 }
333 return this;
334 };
335
336 // destroy all children
337 result.destroy = function() {
338 this.front.destroy();
339 this.top.destroy();
340 this.side.destroy();
341
342 return null;
343 };
344
345 // Apply the Z index to the cuboid group
346 result.attr({
347 zIndex: -paths[6]
348 });
349
350 return result;
351 };
352
353 /**
354 * Generates a cuboid
355 */
356 SVGRenderer.prototype.cuboidPath = function(shapeArgs) {
357 var x = shapeArgs.x,
358 y = shapeArgs.y,
359 z = shapeArgs.z,
360 h = shapeArgs.height,
361 w = shapeArgs.width,
362 d = shapeArgs.depth,
363 chart = charts[this.chartIndex];
364
365 // The 8 corners of the cube
366 var pArr = [{
367 x: x,
368 y: y,
369 z: z
370 }, {
371 x: x + w,
372 y: y,
373 z: z
374 }, {
375 x: x + w,
376 y: y + h,
377 z: z
378 }, {
379 x: x,
380 y: y + h,
381 z: z
382 }, {
383 x: x,
384 y: y + h,
385 z: z + d
386 }, {
387 x: x + w,
388 y: y + h,
389 z: z + d
390 }, {
391 x: x + w,
392 y: y,
393 z: z + d
394 }, {
395 x: x,
396 y: y,
397 z: z + d
398 }];
399
400 // apply perspective
401 pArr = perspective(pArr, chart, shapeArgs.insidePlotArea);
402
403 // helper method to decide which side is visible
404 function mapPath(i) {
405 return pArr[i];
406 }
407 var pickShape = function(path1, path2) {
408 var ret = [];
409 path1 = map(path1, mapPath);
410 path2 = map(path2, mapPath);
411 if (shapeArea(path1) < 0) {
412 ret = path1;
413 } else if (shapeArea(path2) < 0) {
414 ret = path2;
415 }
416 return ret;
417 };
418
419 // front or back
420 var front = [3, 2, 1, 0];
421 var back = [7, 6, 5, 4];
422 var path1 = pickShape(front, back);
423
424 // top or bottom
425 var top = [1, 6, 7, 0];
426 var bottom = [4, 5, 2, 3];
427 var path2 = pickShape(top, bottom);
428
429 // side
430 var right = [1, 2, 5, 6];
431 var left = [0, 7, 4, 3];
432 var path3 = pickShape(right, left);
433
434 return [this.toLinePath(path1, true), this.toLinePath(path2, true), this.toLinePath(path3, true), averageZ(path1), averageZ(path2), averageZ(path3), averageZ(map(bottom, mapPath)) * 9e9]; // #4774
435 };
436
437 ////// SECTORS //////
438 H.SVGRenderer.prototype.arc3d = function(attribs) {
439
440 var wrapper = this.g(),
441 renderer = wrapper.renderer,
442 customAttribs = ['x', 'y', 'r', 'innerR', 'start', 'end'];
443
444 /**
445 * Get custom attributes. Mutate the original object and return an object with only custom attr.
446 */
447 function suckOutCustom(params) {
448 var hasCA = false,
449 ca = {};
450 for (var key in params) {
451 if (inArray(key, customAttribs) !== -1) {
452 ca[key] = params[key];
453 delete params[key];
454 hasCA = true;
455 }
456 }
457 return hasCA ? ca : false;
458 }
459
460 attribs = merge(attribs);
461
462 attribs.alpha *= deg2rad;
463 attribs.beta *= deg2rad;
464
465 // Create the different sub sections of the shape
466 wrapper.top = renderer.path();
467 wrapper.side1 = renderer.path();
468 wrapper.side2 = renderer.path();
469 wrapper.inn = renderer.path();
470 wrapper.out = renderer.path();
471
472 /**
473 * Add all faces
474 */
475 wrapper.onAdd = function() {
476 var parent = wrapper.parentGroup,
477 className = wrapper.attr('class');
478 wrapper.top.add(wrapper);
479
480 // These faces are added outside the wrapper group because the z index
481 // relates to neighbour elements as well
482 each(['out', 'inn', 'side1', 'side2'], function(face) {
483 wrapper[face]
484 .addClass(className + ' highcharts-3d-side')
485 .add(parent);
486 });
487 };
488
489 /**
490 * Compute the transformed paths and set them to the composite shapes
491 */
492 wrapper.setPaths = function(attribs) {
493
494 var paths = wrapper.renderer.arc3dPath(attribs),
495 zIndex = paths.zTop * 100;
496
497 wrapper.attribs = attribs;
498
499 wrapper.top.attr({
500 d: paths.top,
501 zIndex: paths.zTop
502 });
503 wrapper.inn.attr({
504 d: paths.inn,
505 zIndex: paths.zInn
506 });
507 wrapper.out.attr({
508 d: paths.out,
509 zIndex: paths.zOut
510 });
511 wrapper.side1.attr({
512 d: paths.side1,
513 zIndex: paths.zSide1
514 });
515 wrapper.side2.attr({
516 d: paths.side2,
517 zIndex: paths.zSide2
518 });
519
520
521 // show all children
522 wrapper.zIndex = zIndex;
523 wrapper.attr({
524 zIndex: zIndex
525 });
526
527 // Set the radial gradient center the first time
528 if (attribs.center) {
529 wrapper.top.setRadialReference(attribs.center);
530 delete attribs.center;
531 }
532 };
533 wrapper.setPaths(attribs);
534
535 // Apply the fill to the top and a darker shade to the sides
536 wrapper.fillSetter = function(value) {
537 var darker = color(value).brighten(-0.1).get();
538
539 this.fill = value;
540
541 this.side1.attr({
542 fill: darker
543 });
544 this.side2.attr({
545 fill: darker
546 });
547 this.inn.attr({
548 fill: darker
549 });
550 this.out.attr({
551 fill: darker
552 });
553 this.top.attr({
554 fill: value
555 });
556 return this;
557 };
558
559 // Apply the same value to all. These properties cascade down to the children
560 // when set to the composite arc3d.
561 each(['opacity', 'translateX', 'translateY', 'visibility'], function(setter) {
562 wrapper[setter + 'Setter'] = function(value, key) {
563 wrapper[key] = value;
564 each(['out', 'inn', 'side1', 'side2', 'top'], function(el) {
565 wrapper[el].attr(key, value);
566 });
567 };
568 });
569
570 /**
571 * Override attr to remove shape attributes and use those to set child paths
572 */
573 wrap(wrapper, 'attr', function(proceed, params, val) {
574 var ca;
575 if (typeof params === 'object') {
576 ca = suckOutCustom(params);
577 if (ca) {
578 extend(wrapper.attribs, ca);
579 wrapper.setPaths(wrapper.attribs);
580 }
581 }
582 return proceed.call(this, params, val);
583 });
584
585 /**
586 * Override the animate function by sucking out custom parameters related to the shapes directly,
587 * and update the shapes from the animation step.
588 */
589 wrap(wrapper, 'animate', function(proceed, params, animation, complete) {
590 var ca,
591 from = this.attribs,
592 to,
593 anim;
594
595 // Attribute-line properties connected to 3D. These shouldn't have been in the
596 // attribs collection in the first place.
597 delete params.center;
598 delete params.z;
599 delete params.depth;
600 delete params.alpha;
601 delete params.beta;
602
603 anim = animObject(pick(animation, this.renderer.globalAnimation));
604
605 if (anim.duration) {
606 params = merge(params); // Don't mutate the original object
607 ca = suckOutCustom(params);
608
609 if (ca) {
610 to = ca;
611 anim.step = function(a, fx) {
612 function interpolate(key) {
613 return from[key] + (pick(to[key], from[key]) - from[key]) * fx.pos;
614 }
615 fx.elem.setPaths(merge(from, {
616 x: interpolate('x'),
617 y: interpolate('y'),
618 r: interpolate('r'),
619 innerR: interpolate('innerR'),
620 start: interpolate('start'),
621 end: interpolate('end')
622 }));
623 };
624 }
625 animation = anim; // Only when duration (#5572)
626 }
627 return proceed.call(this, params, animation, complete);
628 });
629
630 // destroy all children
631 wrapper.destroy = function() {
632 this.top.destroy();
633 this.out.destroy();
634 this.inn.destroy();
635 this.side1.destroy();
636 this.side2.destroy();
637
638 SVGElement.prototype.destroy.call(this);
639 };
640 // hide all children
641 wrapper.hide = function() {
642 this.top.hide();
643 this.out.hide();
644 this.inn.hide();
645 this.side1.hide();
646 this.side2.hide();
647 };
648 wrapper.show = function() {
649 this.top.show();
650 this.out.show();
651 this.inn.show();
652 this.side1.show();
653 this.side2.show();
654 };
655 return wrapper;
656 };
657
658 /**
659 * Generate the paths required to draw a 3D arc
660 */
661 SVGRenderer.prototype.arc3dPath = function(shapeArgs) {
662 var cx = shapeArgs.x, // x coordinate of the center
663 cy = shapeArgs.y, // y coordinate of the center
664 start = shapeArgs.start, // start angle
665 end = shapeArgs.end - 0.00001, // end angle
666 r = shapeArgs.r, // radius
667 ir = shapeArgs.innerR, // inner radius
668 d = shapeArgs.depth, // depth
669 alpha = shapeArgs.alpha, // alpha rotation of the chart
670 beta = shapeArgs.beta; // beta rotation of the chart
671
672 // Derived Variables
673 var cs = Math.cos(start), // cosinus of the start angle
674 ss = Math.sin(start), // sinus of the start angle
675 ce = Math.cos(end), // cosinus of the end angle
676 se = Math.sin(end), // sinus of the end angle
677 rx = r * Math.cos(beta), // x-radius
678 ry = r * Math.cos(alpha), // y-radius
679 irx = ir * Math.cos(beta), // x-radius (inner)
680 iry = ir * Math.cos(alpha), // y-radius (inner)
681 dx = d * Math.sin(beta), // distance between top and bottom in x
682 dy = d * Math.sin(alpha); // distance between top and bottom in y
683
684 // TOP
685 var top = ['M', cx + (rx * cs), cy + (ry * ss)];
686 top = top.concat(curveTo(cx, cy, rx, ry, start, end, 0, 0));
687 top = top.concat([
688 'L', cx + (irx * ce), cy + (iry * se)
689 ]);
690 top = top.concat(curveTo(cx, cy, irx, iry, end, start, 0, 0));
691 top = top.concat(['Z']);
692 // OUTSIDE
693 var b = (beta > 0 ? Math.PI / 2 : 0),
694 a = (alpha > 0 ? 0 : Math.PI / 2);
695
696 var start2 = start > -b ? start : (end > -b ? -b : start),
697 end2 = end < PI - a ? end : (start < PI - a ? PI - a : end),
698 midEnd = 2 * PI - a;
699
700 // When slice goes over bottom middle, need to add both, left and right outer side.
701 // Additionally, when we cross right hand edge, create sharp edge. Outer shape/wall:
702 //
703 // -------
704 // / ^ \
705 // 4) / / \ \ 1)
706 // / / \ \
707 // / / \ \
708 // (c)=> ==== ==== <=(d)
709 // \ \ / /
710 // \ \<=(a)/ /
711 // \ \ / / <=(b)
712 // 3) \ v / 2)
713 // -------
714 //
715 // (a) - inner side
716 // (b) - outer side
717 // (c) - left edge (sharp)
718 // (d) - right edge (sharp)
719 // 1..n - rendering order for startAngle = 0, when set to e.g 90, order changes clockwise (1->2, 2->3, n->1) and counterclockwise for negative startAngle
720
721 var out = ['M', cx + (rx * cos(start2)), cy + (ry * sin(start2))];
722 out = out.concat(curveTo(cx, cy, rx, ry, start2, end2, 0, 0));
723
724 if (end > midEnd && start < midEnd) { // When shape is wide, it can cross both, (c) and (d) edges, when using startAngle
725 // Go to outer side
726 out = out.concat([
727 'L', cx + (rx * cos(end2)) + dx, cy + (ry * sin(end2)) + dy
728 ]);
729 // Curve to the right edge of the slice (d)
730 out = out.concat(curveTo(cx, cy, rx, ry, end2, midEnd, dx, dy));
731 // Go to the inner side
732 out = out.concat([
733 'L', cx + (rx * cos(midEnd)), cy + (ry * sin(midEnd))
734 ]);
735 // Curve to the true end of the slice
736 out = out.concat(curveTo(cx, cy, rx, ry, midEnd, end, 0, 0));
737 // Go to the outer side
738 out = out.concat([
739 'L', cx + (rx * cos(end)) + dx, cy + (ry * sin(end)) + dy
740 ]);
741 // Go back to middle (d)
742 out = out.concat(curveTo(cx, cy, rx, ry, end, midEnd, dx, dy));
743 out = out.concat([
744 'L', cx + (rx * cos(midEnd)), cy + (ry * sin(midEnd))
745 ]);
746 // Go back to the left edge
747 out = out.concat(curveTo(cx, cy, rx, ry, midEnd, end2, 0, 0));
748 } else if (end > PI - a && start < PI - a) { // But shape can cross also only (c) edge:
749 // Go to outer side
750 out = out.concat([
751 'L', cx + (rx * Math.cos(end2)) + dx, cy + (ry * Math.sin(end2)) + dy
752 ]);
753 // Curve to the true end of the slice
754 out = out.concat(curveTo(cx, cy, rx, ry, end2, end, dx, dy));
755 // Go to the inner side
756 out = out.concat([
757 'L', cx + (rx * Math.cos(end)), cy + (ry * Math.sin(end))
758 ]);
759 // Go back to the artifical end2
760 out = out.concat(curveTo(cx, cy, rx, ry, end, end2, 0, 0));
761 }
762
763 out = out.concat([
764 'L', cx + (rx * Math.cos(end2)) + dx, cy + (ry * Math.sin(end2)) + dy
765 ]);
766 out = out.concat(curveTo(cx, cy, rx, ry, end2, start2, dx, dy));
767 out = out.concat(['Z']);
768
769 // INSIDE
770 var inn = ['M', cx + (irx * cs), cy + (iry * ss)];
771 inn = inn.concat(curveTo(cx, cy, irx, iry, start, end, 0, 0));
772 inn = inn.concat([
773 'L', cx + (irx * Math.cos(end)) + dx, cy + (iry * Math.sin(end)) + dy
774 ]);
775 inn = inn.concat(curveTo(cx, cy, irx, iry, end, start, dx, dy));
776 inn = inn.concat(['Z']);
777
778 // SIDES
779 var side1 = [
780 'M', cx + (rx * cs), cy + (ry * ss),
781 'L', cx + (rx * cs) + dx, cy + (ry * ss) + dy,
782 'L', cx + (irx * cs) + dx, cy + (iry * ss) + dy,
783 'L', cx + (irx * cs), cy + (iry * ss),
784 'Z'
785 ];
786 var side2 = [
787 'M', cx + (rx * ce), cy + (ry * se),
788 'L', cx + (rx * ce) + dx, cy + (ry * se) + dy,
789 'L', cx + (irx * ce) + dx, cy + (iry * se) + dy,
790 'L', cx + (irx * ce), cy + (iry * se),
791 'Z'
792 ];
793
794 // correction for changed position of vanishing point caused by alpha and beta rotations
795 var angleCorr = Math.atan2(dy, -dx),
796 angleEnd = Math.abs(end + angleCorr),
797 angleStart = Math.abs(start + angleCorr),
798 angleMid = Math.abs((start + end) / 2 + angleCorr);
799
800 // set to 0-PI range
801 function toZeroPIRange(angle) {
802 angle = angle % (2 * Math.PI);
803 if (angle > Math.PI) {
804 angle = 2 * Math.PI - angle;
805 }
806 return angle;
807 }
808 angleEnd = toZeroPIRange(angleEnd);
809 angleStart = toZeroPIRange(angleStart);
810 angleMid = toZeroPIRange(angleMid);
811
812 // *1e5 is to compensate pInt in zIndexSetter
813 var incPrecision = 1e5,
814 a1 = angleMid * incPrecision,
815 a2 = angleStart * incPrecision,
816 a3 = angleEnd * incPrecision;
817
818 return {
819 top: top,
820 zTop: Math.PI * incPrecision + 1, // max angle is PI, so this is allways higher
821 out: out,
822 zOut: Math.max(a1, a2, a3),
823 inn: inn,
824 zInn: Math.max(a1, a2, a3),
825 side1: side1,
826 zSide1: a3 * 0.99, // to keep below zOut and zInn in case of same values
827 side2: side2,
828 zSide2: a2 * 0.99
829 };
830 };
831
832 }(Highcharts));
833 (function(H) {
834 /**
835 * (c) 2010-2016 Torstein Honsi
836 *
837 * License: www.highcharts.com/license
838 */
839 'use strict';
840 var Chart = H.Chart,
841 each = H.each,
842 merge = H.merge,
843 perspective = H.perspective,
844 pick = H.pick,
845 wrap = H.wrap;
846
847 /***
848 EXTENSION FOR 3D CHARTS
849 ***/
850 // Shorthand to check the is3d flag
851 Chart.prototype.is3d = function() {
852 return this.options.chart.options3d && this.options.chart.options3d.enabled; // #4280
853 };
854
855 Chart.prototype.propsRequireDirtyBox.push('chart.options3d');
856 Chart.prototype.propsRequireUpdateSeries.push('chart.options3d');
857
858 /**
859 * Calculate scale of the 3D view. That is required to
860 * fit chart's 3D projection into the actual plotting area. Reported as #4933.
861 * @notice This function should ideally take the plot values instead of a chart object,
862 * but since the chart object is needed for perspective it is not practical.
863 * Possible to make both getScale and perspective more logical and also immutable.
864 * @param {Object} chart Chart object
865 * @param {Number} chart.plotLeft
866 * @param {Number} chart.plotWidth
867 * @param {Number} chart.plotTop
868 * @param {Number} chart.plotHeight
869 * @param {Number} depth The depth of the chart
870 * @return {Number} The scale to fit the 3D chart into the plotting area.
871 */
872 function getScale(chart, depth) {
873 var plotLeft = chart.plotLeft,
874 plotRight = chart.plotWidth + plotLeft,
875 plotTop = chart.plotTop,
876 plotBottom = chart.plotHeight + plotTop,
877 originX = plotLeft + chart.plotWidth / 2,
878 originY = plotTop + chart.plotHeight / 2,
879 bbox3d = {
880 minX: Number.MAX_VALUE,
881 maxX: -Number.MAX_VALUE,
882 minY: Number.MAX_VALUE,
883 maxY: -Number.MAX_VALUE
884 },
885 corners,
886 scale = 1;
887
888 // Top left corners:
889 corners = [{
890 x: plotLeft,
891 y: plotTop,
892 z: 0
893 }, {
894 x: plotLeft,
895 y: plotTop,
896 z: depth
897 }];
898
899 // Top right corners:
900 each([0, 1], function(i) {
901 corners.push({
902 x: plotRight,
903 y: corners[i].y,
904 z: corners[i].z
905 });
906 });
907
908 // All bottom corners:
909 each([0, 1, 2, 3], function(i) {
910 corners.push({
911 x: corners[i].x,
912 y: plotBottom,
913 z: corners[i].z
914 });
915 });
916
917 // Calculate 3D corners:
918 corners = perspective(corners, chart, false);
919
920 // Get bounding box of 3D element:
921 each(corners, function(corner) {
922 bbox3d.minX = Math.min(bbox3d.minX, corner.x);
923 bbox3d.maxX = Math.max(bbox3d.maxX, corner.x);
924 bbox3d.minY = Math.min(bbox3d.minY, corner.y);
925 bbox3d.maxY = Math.max(bbox3d.maxY, corner.y);
926 });
927
928 // Left edge:
929 if (plotLeft > bbox3d.minX) {
930 scale = Math.min(scale, 1 - Math.abs((plotLeft + originX) / (bbox3d.minX + originX)) % 1);
931 }
932
933 // Right edge:
934 if (plotRight < bbox3d.maxX) {
935 scale = Math.min(scale, (plotRight - originX) / (bbox3d.maxX - originX));
936 }
937
938 // Top edge:
939 if (plotTop > bbox3d.minY) {
940 if (bbox3d.minY < 0) {
941 scale = Math.min(scale, (plotTop + originY) / (-bbox3d.minY + plotTop + originY));
942 } else {
943 scale = Math.min(scale, 1 - (plotTop + originY) / (bbox3d.minY + originY) % 1);
944 }
945 }
946
947 // Bottom edge:
948 if (plotBottom < bbox3d.maxY) {
949 scale = Math.min(scale, Math.abs((plotBottom - originY) / (bbox3d.maxY - originY)));
950 }
951
952 return scale;
953 }
954
955
956
957 H.wrap(H.Chart.prototype, 'isInsidePlot', function(proceed) {
958 return this.is3d() || proceed.apply(this, [].slice.call(arguments, 1));
959 });
960
961 var defaultOptions = H.getOptions();
962 merge(true, defaultOptions, {
963 chart: {
964 options3d: {
965 enabled: false,
966 alpha: 0,
967 beta: 0,
968 depth: 100,
969 fitToPlot: true,
970 viewDistance: 25,
971 frame: {
972 bottom: {
973 size: 1
974 },
975 side: {
976 size: 1
977 },
978 back: {
979 size: 1
980 }
981 }
982 }
983 }
984
985 });
986
987 wrap(Chart.prototype, 'setClassName', function(proceed) {
988 proceed.apply(this, [].slice.call(arguments, 1));
989
990 if (this.is3d()) {
991 this.container.className += ' highcharts-3d-chart';
992 }
993 });
994
995 H.wrap(H.Chart.prototype, 'setChartSize', function(proceed) {
996 var chart = this,
997 options3d = chart.options.chart.options3d;
998
999 proceed.apply(chart, [].slice.call(arguments, 1));
1000
1001 if (chart.is3d()) {
1002 var inverted = chart.inverted,
1003 clipBox = chart.clipBox,
1004 margin = chart.margin,
1005 x = inverted ? 'y' : 'x',
1006 y = inverted ? 'x' : 'y',
1007 w = inverted ? 'height' : 'width',
1008 h = inverted ? 'width' : 'height';
1009
1010 clipBox[x] = -(margin[3] || 0);
1011 clipBox[y] = -(margin[0] || 0);
1012 clipBox[w] = chart.chartWidth + (margin[3] || 0) + (margin[1] || 0);
1013 clipBox[h] = chart.chartHeight + (margin[0] || 0) + (margin[2] || 0);
1014
1015 // Set scale, used later in perspective method():
1016 chart.scale3d = 1; // @notice getScale uses perspective, so scale3d has to be reset.
1017 if (options3d.fitToPlot === true) {
1018 chart.scale3d = getScale(chart, options3d.depth);
1019 }
1020 }
1021 });
1022
1023 wrap(Chart.prototype, 'redraw', function(proceed) {
1024 if (this.is3d()) {
1025 // Set to force a redraw of all elements
1026 this.isDirtyBox = true;
1027 }
1028 proceed.apply(this, [].slice.call(arguments, 1));
1029 });
1030
1031 // Draw the series in the reverse order (#3803, #3917)
1032 wrap(Chart.prototype, 'renderSeries', function(proceed) {
1033 var series,
1034 i = this.series.length;
1035
1036 if (this.is3d()) {
1037 while (i--) {
1038 series = this.series[i];
1039 series.translate();
1040 series.render();
1041 }
1042 } else {
1043 proceed.call(this);
1044 }
1045 });
1046
1047 Chart.prototype.retrieveStacks = function(stacking) {
1048 var series = this.series,
1049 stacks = {},
1050 stackNumber,
1051 i = 1;
1052
1053 each(this.series, function(s) {
1054 stackNumber = pick(s.options.stack, (stacking ? 0 : series.length - 1 - s.index)); // #3841, #4532
1055 if (!stacks[stackNumber]) {
1056 stacks[stackNumber] = {
1057 series: [s],
1058 position: i
1059 };
1060 i++;
1061 } else {
1062 stacks[stackNumber].series.push(s);
1063 }
1064 });
1065
1066 stacks.totalStacks = i + 1;
1067 return stacks;
1068 };
1069
1070 }(Highcharts));
1071 (function(H) {
1072 /**
1073 * (c) 2010-2016 Torstein Honsi
1074 *
1075 * License: www.highcharts.com/license
1076 */
1077 'use strict';
1078 var ZAxis,
1079
1080 Axis = H.Axis,
1081 Chart = H.Chart,
1082 each = H.each,
1083 extend = H.extend,
1084 merge = H.merge,
1085 perspective = H.perspective,
1086 pick = H.pick,
1087 splat = H.splat,
1088 Tick = H.Tick,
1089 wrap = H.wrap;
1090 /***
1091 EXTENSION TO THE AXIS
1092 ***/
1093 wrap(Axis.prototype, 'setOptions', function(proceed, userOptions) {
1094 var options;
1095 proceed.call(this, userOptions);
1096 if (this.chart.is3d()) {
1097 options = this.options;
1098 options.tickWidth = pick(options.tickWidth, 0);
1099 options.gridLineWidth = pick(options.gridLineWidth, 1);
1100 }
1101 });
1102
1103 wrap(Axis.prototype, 'render', function(proceed) {
1104 proceed.apply(this, [].slice.call(arguments, 1));
1105
1106 // Do not do this if the chart is not 3D
1107 if (!this.chart.is3d()) {
1108 return;
1109 }
1110
1111 var chart = this.chart,
1112 renderer = chart.renderer,
1113 options3d = chart.options.chart.options3d,
1114 frame = options3d.frame,
1115 fbottom = frame.bottom,
1116 fback = frame.back,
1117 fside = frame.side,
1118 depth = options3d.depth,
1119 height = this.height,
1120 width = this.width,
1121 left = this.left,
1122 top = this.top;
1123
1124 if (this.isZAxis) {
1125 return;
1126 }
1127 if (this.horiz) {
1128 var bottomShape = {
1129 x: left,
1130 y: top + (chart.xAxis[0].opposite ? -fbottom.size : height),
1131 z: 0,
1132 width: width,
1133 height: fbottom.size,
1134 depth: depth,
1135 insidePlotArea: false
1136 };
1137 if (!this.bottomFrame) {
1138 this.bottomFrame = renderer.cuboid(bottomShape).attr({
1139 'class': 'highcharts-3d-frame highcharts-3d-frame-bottom',
1140 'zIndex': (chart.yAxis[0].reversed && options3d.alpha > 0 ? 4 : -1)
1141 }).add();
1142
1143
1144 this.bottomFrame.attr({
1145 fill: fbottom.color || 'none',
1146 stroke: fbottom.color || 'none'
1147 });
1148
1149 } else {
1150 this.bottomFrame.animate(bottomShape);
1151 }
1152 } else {
1153 // BACK
1154 var backShape = {
1155 x: left + (chart.yAxis[0].opposite ? 0 : -fside.size),
1156 y: top + (chart.xAxis[0].opposite ? -fbottom.size : 0),
1157 z: depth,
1158 width: width + fside.size,
1159 height: height + fbottom.size,
1160 depth: fback.size,
1161 insidePlotArea: false
1162 };
1163 if (!this.backFrame) {
1164 this.backFrame = renderer.cuboid(backShape).attr({
1165 'class': 'highcharts-3d-frame highcharts-3d-frame-back',
1166 zIndex: -3
1167 }).add();
1168
1169
1170 this.backFrame.attr({
1171 fill: fback.color || 'none',
1172 stroke: fback.color || 'none'
1173 });
1174
1175 } else {
1176 this.backFrame.animate(backShape);
1177 }
1178 var sideShape = {
1179 x: left + (chart.yAxis[0].opposite ? width : -fside.size),
1180 y: top + (chart.xAxis[0].opposite ? -fbottom.size : 0),
1181 z: 0,
1182 width: fside.size,
1183 height: height + fbottom.size,
1184 depth: depth,
1185 insidePlotArea: false
1186 };
1187 if (!this.sideFrame) {
1188 this.sideFrame = renderer.cuboid(sideShape).attr({
1189 'class': 'highcharts-3d-frame highcharts-3d-frame-side',
1190 zIndex: -2
1191 }).add();
1192
1193
1194 this.sideFrame.attr({
1195 fill: fside.color || 'none',
1196 stroke: fside.color || 'none'
1197 });
1198
1199
1200 } else {
1201 this.sideFrame.animate(sideShape);
1202 }
1203 }
1204 });
1205
1206 wrap(Axis.prototype, 'getPlotLinePath', function(proceed) {
1207 var path = proceed.apply(this, [].slice.call(arguments, 1));
1208
1209 // Do not do this if the chart is not 3D
1210 if (!this.chart.is3d()) {
1211 return path;
1212 }
1213
1214 if (path === null) {
1215 return path;
1216 }
1217
1218 var chart = this.chart,
1219 options3d = chart.options.chart.options3d,
1220 d = this.isZAxis ? chart.plotWidth : options3d.depth,
1221 opposite = this.opposite;
1222 if (this.horiz) {
1223 opposite = !opposite;
1224 }
1225 var pArr = [
1226 this.swapZ({
1227 x: path[1],
1228 y: path[2],
1229 z: (opposite ? d : 0)
1230 }),
1231 this.swapZ({
1232 x: path[1],
1233 y: path[2],
1234 z: d
1235 }),
1236 this.swapZ({
1237 x: path[4],
1238 y: path[5],
1239 z: d
1240 }),
1241 this.swapZ({
1242 x: path[4],
1243 y: path[5],
1244 z: (opposite ? 0 : d)
1245 })
1246 ];
1247
1248 pArr = perspective(pArr, this.chart, false);
1249 path = this.chart.renderer.toLinePath(pArr, false);
1250
1251 return path;
1252 });
1253
1254 // Do not draw axislines in 3D
1255 wrap(Axis.prototype, 'getLinePath', function(proceed) {
1256 return this.chart.is3d() ? [] : proceed.apply(this, [].slice.call(arguments, 1));
1257 });
1258
1259 wrap(Axis.prototype, 'getPlotBandPath', function(proceed) {
1260 // Do not do this if the chart is not 3D
1261 if (!this.chart.is3d()) {
1262 return proceed.apply(this, [].slice.call(arguments, 1));
1263 }
1264
1265 var args = arguments,
1266 from = args[1],
1267 to = args[2],
1268 toPath = this.getPlotLinePath(to),
1269 path = this.getPlotLinePath(from);
1270
1271 if (path && toPath) {
1272 path.push(
1273 'L',
1274 toPath[10], // These two do not exist in the regular getPlotLine
1275 toPath[11], // ---- # 3005
1276 'L',
1277 toPath[7],
1278 toPath[8],
1279 'L',
1280 toPath[4],
1281 toPath[5],
1282 'L',
1283 toPath[1],
1284 toPath[2]
1285 );
1286 } else { // outside the axis area
1287 path = null;
1288 }
1289
1290 return path;
1291 });
1292
1293 /***
1294 EXTENSION TO THE TICKS
1295 ***/
1296
1297 wrap(Tick.prototype, 'getMarkPath', function(proceed) {
1298 var path = proceed.apply(this, [].slice.call(arguments, 1));
1299
1300 // Do not do this if the chart is not 3D
1301 if (!this.axis.chart.is3d()) {
1302 return path;
1303 }
1304
1305 var pArr = [
1306 this.axis.swapZ({
1307 x: path[1],
1308 y: path[2],
1309 z: 0
1310 }),
1311 this.axis.swapZ({
1312 x: path[4],
1313 y: path[5],
1314 z: 0
1315 })
1316 ];
1317
1318 pArr = perspective(pArr, this.axis.chart, false);
1319 path = [
1320 'M', pArr[0].x, pArr[0].y,
1321 'L', pArr[1].x, pArr[1].y
1322 ];
1323 return path;
1324 });
1325
1326 wrap(Tick.prototype, 'getLabelPosition', function(proceed) {
1327 var pos = proceed.apply(this, [].slice.call(arguments, 1));
1328
1329 // Do not do this if the chart is not 3D
1330 if (this.axis.chart.is3d()) {
1331 pos = perspective([this.axis.swapZ({
1332 x: pos.x,
1333 y: pos.y,
1334 z: 0
1335 })], this.axis.chart, false)[0];
1336 }
1337 return pos;
1338 });
1339
1340 H.wrap(Axis.prototype, 'getTitlePosition', function(proceed) {
1341 var is3d = this.chart.is3d(),
1342 pos,
1343 axisTitleMargin;
1344
1345 // Pull out the axis title margin, that is not subject to the perspective
1346 if (is3d) {
1347 axisTitleMargin = this.axisTitleMargin;
1348 this.axisTitleMargin = 0;
1349 }
1350
1351 pos = proceed.apply(this, [].slice.call(arguments, 1));
1352
1353 if (is3d) {
1354 pos = perspective([this.swapZ({
1355 x: pos.x,
1356 y: pos.y,
1357 z: 0
1358 })], this.chart, false)[0];
1359
1360 // Re-apply the axis title margin outside the perspective
1361 pos[this.horiz ? 'y' : 'x'] += (this.horiz ? 1 : -1) * // horizontal axis reverses the margin ...
1362 (this.opposite ? -1 : 1) * // ... so does opposite axes
1363 axisTitleMargin;
1364 this.axisTitleMargin = axisTitleMargin;
1365 }
1366 return pos;
1367 });
1368
1369 wrap(Axis.prototype, 'drawCrosshair', function(proceed) {
1370 var args = arguments;
1371 if (this.chart.is3d()) {
1372 if (args[2]) {
1373 args[2] = {
1374 plotX: args[2].plotXold || args[2].plotX,
1375 plotY: args[2].plotYold || args[2].plotY
1376 };
1377 }
1378 }
1379 proceed.apply(this, [].slice.call(args, 1));
1380 });
1381
1382 /***
1383 Z-AXIS
1384 ***/
1385
1386 Axis.prototype.swapZ = function(p, insidePlotArea) {
1387 if (this.isZAxis) {
1388 var plotLeft = insidePlotArea ? 0 : this.chart.plotLeft;
1389 var chart = this.chart;
1390 return {
1391 x: plotLeft + (chart.yAxis[0].opposite ? p.z : chart.xAxis[0].width - p.z),
1392 y: p.y,
1393 z: p.x - plotLeft
1394 };
1395 }
1396 return p;
1397 };
1398
1399 ZAxis = H.ZAxis = function() {
1400 this.isZAxis = true;
1401 this.init.apply(this, arguments);
1402 };
1403 extend(ZAxis.prototype, Axis.prototype);
1404 extend(ZAxis.prototype, {
1405 setOptions: function(userOptions) {
1406 userOptions = merge({
1407 offset: 0,
1408 lineWidth: 0
1409 }, userOptions);
1410 Axis.prototype.setOptions.call(this, userOptions);
1411 this.coll = 'zAxis';
1412 },
1413 setAxisSize: function() {
1414 Axis.prototype.setAxisSize.call(this);
1415 this.width = this.len = this.chart.options.chart.options3d.depth;
1416 this.right = this.chart.chartWidth - this.width - this.left;
1417 },
1418 getSeriesExtremes: function() {
1419 var axis = this,
1420 chart = axis.chart;
1421
1422 axis.hasVisibleSeries = false;
1423
1424 // Reset properties in case we're redrawing (#3353)
1425 axis.dataMin = axis.dataMax = axis.ignoreMinPadding = axis.ignoreMaxPadding = null;
1426
1427 if (axis.buildStacks) {
1428 axis.buildStacks();
1429 }
1430
1431 // loop through this axis' series
1432 each(axis.series, function(series) {
1433
1434 if (series.visible || !chart.options.chart.ignoreHiddenSeries) {
1435
1436 var seriesOptions = series.options,
1437 zData,
1438 threshold = seriesOptions.threshold;
1439
1440 axis.hasVisibleSeries = true;
1441
1442 // Validate threshold in logarithmic axes
1443 if (axis.isLog && threshold <= 0) {
1444 threshold = null;
1445 }
1446
1447 zData = series.zData;
1448 if (zData.length) {
1449 axis.dataMin = Math.min(pick(axis.dataMin, zData[0]), Math.min.apply(null, zData));
1450 axis.dataMax = Math.max(pick(axis.dataMax, zData[0]), Math.max.apply(null, zData));
1451 }
1452 }
1453 });
1454 }
1455 });
1456
1457
1458 /**
1459 * Extend the chart getAxes method to also get the color axis
1460 */
1461 wrap(Chart.prototype, 'getAxes', function(proceed) {
1462 var chart = this,
1463 options = this.options,
1464 zAxisOptions = options.zAxis = splat(options.zAxis || {});
1465
1466 proceed.call(this);
1467
1468 if (!chart.is3d()) {
1469 return;
1470 }
1471 this.zAxis = [];
1472 each(zAxisOptions, function(axisOptions, i) {
1473 axisOptions.index = i;
1474 axisOptions.isX = true; //Z-Axis is shown horizontally, so it's kind of a X-Axis
1475 var zAxis = new ZAxis(chart, axisOptions);
1476 zAxis.setScale();
1477 });
1478 });
1479
1480 }(Highcharts));
1481 (function(H) {
1482 /**
1483 * (c) 2010-2016 Torstein Honsi
1484 *
1485 * License: www.highcharts.com/license
1486 */
1487 'use strict';
1488 var each = H.each,
1489 perspective = H.perspective,
1490 pick = H.pick,
1491 Series = H.Series,
1492 seriesTypes = H.seriesTypes,
1493 svg = H.svg,
1494 wrap = H.wrap;
1495 /***
1496 EXTENSION FOR 3D COLUMNS
1497 ***/
1498 wrap(seriesTypes.column.prototype, 'translate', function(proceed) {
1499 proceed.apply(this, [].slice.call(arguments, 1));
1500
1501 // Do not do this if the chart is not 3D
1502 if (!this.chart.is3d()) {
1503 return;
1504 }
1505
1506 var series = this,
1507 chart = series.chart,
1508 seriesOptions = series.options,
1509 depth = seriesOptions.depth || 25;
1510
1511 var stack = seriesOptions.stacking ? (seriesOptions.stack || 0) : series._i;
1512 var z = stack * (depth + (seriesOptions.groupZPadding || 1));
1513
1514 if (seriesOptions.grouping !== false) {
1515 z = 0;
1516 }
1517
1518 z += (seriesOptions.groupZPadding || 1);
1519
1520 each(series.data, function(point) {
1521 if (point.y !== null) {
1522 var shapeArgs = point.shapeArgs,
1523 tooltipPos = point.tooltipPos;
1524
1525 point.shapeType = 'cuboid';
1526 shapeArgs.z = z;
1527 shapeArgs.depth = depth;
1528 shapeArgs.insidePlotArea = true;
1529
1530 // Translate the tooltip position in 3d space
1531 tooltipPos = perspective([{
1532 x: tooltipPos[0],
1533 y: tooltipPos[1],
1534 z: z
1535 }], chart, true)[0];
1536 point.tooltipPos = [tooltipPos.x, tooltipPos.y];
1537 }
1538 });
1539 // store for later use #4067
1540 series.z = z;
1541 });
1542
1543 wrap(seriesTypes.column.prototype, 'animate', function(proceed) {
1544 if (!this.chart.is3d()) {
1545 proceed.apply(this, [].slice.call(arguments, 1));
1546 } else {
1547 var args = arguments,
1548 init = args[1],
1549 yAxis = this.yAxis,
1550 series = this,
1551 reversed = this.yAxis.reversed;
1552
1553 if (svg) { // VML is too slow anyway
1554 if (init) {
1555 each(series.data, function(point) {
1556 if (point.y !== null) {
1557 point.height = point.shapeArgs.height;
1558 point.shapey = point.shapeArgs.y; //#2968
1559 point.shapeArgs.height = 1;
1560 if (!reversed) {
1561 if (point.stackY) {
1562 point.shapeArgs.y = point.plotY + yAxis.translate(point.stackY);
1563 } else {
1564 point.shapeArgs.y = point.plotY + (point.negative ? -point.height : point.height);
1565 }
1566 }
1567 }
1568 });
1569
1570 } else { // run the animation
1571 each(series.data, function(point) {
1572 if (point.y !== null) {
1573 point.shapeArgs.height = point.height;
1574 point.shapeArgs.y = point.shapey; //#2968
1575 // null value do not have a graphic
1576 if (point.graphic) {
1577 point.graphic.animate(point.shapeArgs, series.options.animation);
1578 }
1579 }
1580 });
1581
1582 // redraw datalabels to the correct position
1583 this.drawDataLabels();
1584
1585 // delete this function to allow it only once
1586 series.animate = null;
1587 }
1588 }
1589 }
1590 });
1591
1592 wrap(seriesTypes.column.prototype, 'init', function(proceed) {
1593 proceed.apply(this, [].slice.call(arguments, 1));
1594
1595 if (this.chart.is3d()) {
1596 var seriesOptions = this.options,
1597 grouping = seriesOptions.grouping,
1598 stacking = seriesOptions.stacking,
1599 reversedStacks = pick(this.yAxis.options.reversedStacks, true),
1600 z = 0;
1601
1602 if (!(grouping !== undefined && !grouping)) {
1603 var stacks = this.chart.retrieveStacks(stacking),
1604 stack = seriesOptions.stack || 0,
1605 i; // position within the stack
1606 for (i = 0; i < stacks[stack].series.length; i++) {
1607 if (stacks[stack].series[i] === this) {
1608 break;
1609 }
1610 }
1611 z = (10 * (stacks.totalStacks - stacks[stack].position)) + (reversedStacks ? i : -i); // #4369
1612
1613 // In case when axis is reversed, columns are also reversed inside the group (#3737)
1614 if (!this.xAxis.reversed) {
1615 z = (stacks.totalStacks * 10) - z;
1616 }
1617 }
1618
1619 seriesOptions.zIndex = z;
1620 }
1621 });
1622
1623
1624 function pointAttribs(proceed) {
1625 var attr = proceed.apply(this, [].slice.call(arguments, 1));
1626
1627 if (this.chart.is3d()) {
1628 // Set the fill color to the fill color to provide a smooth edge
1629 attr.stroke = this.options.edgeColor || attr.fill;
1630 attr['stroke-width'] = pick(this.options.edgeWidth, 1); // #4055
1631 }
1632
1633 return attr;
1634 }
1635
1636 wrap(seriesTypes.column.prototype, 'pointAttribs', pointAttribs);
1637 if (seriesTypes.columnrange) {
1638 wrap(seriesTypes.columnrange.prototype, 'pointAttribs', pointAttribs);
1639 }
1640
1641
1642 function draw3DPoints(proceed) {
1643 // Do not do this if the chart is not 3D
1644 if (this.chart.is3d()) {
1645 var grouping = this.chart.options.plotOptions.column.grouping;
1646 if (grouping !== undefined && !grouping && this.group.zIndex !== undefined && !this.zIndexSet) {
1647 this.group.attr({
1648 zIndex: this.group.zIndex * 10
1649 });
1650 this.zIndexSet = true; // #4062 set zindex only once
1651 }
1652 }
1653
1654 proceed.apply(this, [].slice.call(arguments, 1));
1655 }
1656
1657 wrap(Series.prototype, 'alignDataLabel', function(proceed) {
1658
1659 // Only do this for 3D columns and columnranges
1660 if (this.chart.is3d() && (this.type === 'column' || this.type === 'columnrange')) {
1661 var series = this,
1662 chart = series.chart;
1663
1664 var args = arguments,
1665 alignTo = args[4];
1666
1667 var pos = ({
1668 x: alignTo.x,
1669 y: alignTo.y,
1670 z: series.z
1671 });
1672 pos = perspective([pos], chart, true)[0];
1673 alignTo.x = pos.x;
1674 alignTo.y = pos.y;
1675 }
1676
1677 proceed.apply(this, [].slice.call(arguments, 1));
1678 });
1679
1680 if (seriesTypes.columnrange) {
1681 wrap(seriesTypes.columnrange.prototype, 'drawPoints', draw3DPoints);
1682 }
1683
1684 wrap(seriesTypes.column.prototype, 'drawPoints', draw3DPoints);
1685
1686 /***
1687 EXTENSION FOR 3D CYLINDRICAL COLUMNS
1688 Not supported
1689 ***/
1690 /*
1691 var defaultOptions = H.getOptions();
1692 defaultOptions.plotOptions.cylinder = H.merge(defaultOptions.plotOptions.column);
1693 var CylinderSeries = H.extendClass(seriesTypes.column, {
1694 type: 'cylinder'
1695 });
1696 seriesTypes.cylinder = CylinderSeries;
1697
1698 wrap(seriesTypes.cylinder.prototype, 'translate', function (proceed) {
1699 proceed.apply(this, [].slice.call(arguments, 1));
1700
1701 // Do not do this if the chart is not 3D
1702 if (!this.chart.is3d()) {
1703 return;
1704 }
1705
1706 var series = this,
1707 chart = series.chart,
1708 options = chart.options,
1709 cylOptions = options.plotOptions.cylinder,
1710 options3d = options.chart.options3d,
1711 depth = cylOptions.depth || 0,
1712 alpha = chart.alpha3d;
1713
1714 var z = cylOptions.stacking ? (this.options.stack || 0) * depth : series._i * depth;
1715 z += depth / 2;
1716
1717 if (cylOptions.grouping !== false) { z = 0; }
1718
1719 each(series.data, function (point) {
1720 var shapeArgs = point.shapeArgs,
1721 deg2rad = H.deg2rad;
1722 point.shapeType = 'arc3d';
1723 shapeArgs.x += depth / 2;
1724 shapeArgs.z = z;
1725 shapeArgs.start = 0;
1726 shapeArgs.end = 2 * PI;
1727 shapeArgs.r = depth * 0.95;
1728 shapeArgs.innerR = 0;
1729 shapeArgs.depth = shapeArgs.height * (1 / sin((90 - alpha) * deg2rad)) - z;
1730 shapeArgs.alpha = 90 - alpha;
1731 shapeArgs.beta = 0;
1732 });
1733 });
1734 */
1735
1736 }(Highcharts));
1737 (function(H) {
1738 /**
1739 * (c) 2010-2016 Torstein Honsi
1740 *
1741 * License: www.highcharts.com/license
1742 */
1743 'use strict';
1744 var deg2rad = H.deg2rad,
1745 each = H.each,
1746 pick = H.pick,
1747 seriesTypes = H.seriesTypes,
1748 svg = H.svg,
1749 wrap = H.wrap;
1750
1751 /***
1752 EXTENSION FOR 3D PIES
1753 ***/
1754
1755 wrap(seriesTypes.pie.prototype, 'translate', function(proceed) {
1756 proceed.apply(this, [].slice.call(arguments, 1));
1757
1758 // Do not do this if the chart is not 3D
1759 if (!this.chart.is3d()) {
1760 return;
1761 }
1762
1763 var series = this,
1764 seriesOptions = series.options,
1765 depth = seriesOptions.depth || 0,
1766 options3d = series.chart.options.chart.options3d,
1767 alpha = options3d.alpha,
1768 beta = options3d.beta,
1769 z = seriesOptions.stacking ? (seriesOptions.stack || 0) * depth : series._i * depth;
1770
1771 z += depth / 2;
1772
1773 if (seriesOptions.grouping !== false) {
1774 z = 0;
1775 }
1776
1777 each(series.data, function(point) {
1778
1779 var shapeArgs = point.shapeArgs,
1780 angle;
1781
1782 point.shapeType = 'arc3d';
1783
1784 shapeArgs.z = z;
1785 shapeArgs.depth = depth * 0.75;
1786 shapeArgs.alpha = alpha;
1787 shapeArgs.beta = beta;
1788 shapeArgs.center = series.center;
1789
1790 angle = (shapeArgs.end + shapeArgs.start) / 2;
1791
1792 point.slicedTranslation = {
1793 translateX: Math.round(Math.cos(angle) * seriesOptions.slicedOffset * Math.cos(alpha * deg2rad)),
1794 translateY: Math.round(Math.sin(angle) * seriesOptions.slicedOffset * Math.cos(alpha * deg2rad))
1795 };
1796 });
1797 });
1798
1799 wrap(seriesTypes.pie.prototype.pointClass.prototype, 'haloPath', function(proceed) {
1800 var args = arguments;
1801 return this.series.chart.is3d() ? [] : proceed.call(this, args[1]);
1802 });
1803
1804
1805 wrap(seriesTypes.pie.prototype, 'pointAttribs', function(proceed, point, state) {
1806 var attr = proceed.call(this, point, state),
1807 options = this.options;
1808
1809 if (this.chart.is3d()) {
1810 attr.stroke = options.edgeColor || point.color || this.color;
1811 attr['stroke-width'] = pick(options.edgeWidth, 1);
1812 }
1813
1814 return attr;
1815 });
1816
1817
1818 wrap(seriesTypes.pie.prototype, 'drawPoints', function(proceed) {
1819 proceed.apply(this, [].slice.call(arguments, 1));
1820
1821 if (this.chart.is3d()) {
1822 each(this.points, function(point) {
1823 var graphic = point.graphic;
1824
1825 // #4584 Check if has graphic - null points don't have it
1826 if (graphic) {
1827 // Hide null or 0 points (#3006, 3650)
1828 graphic[point.y && point.visible ? 'show' : 'hide']();
1829 }
1830 });
1831 }
1832 });
1833
1834 wrap(seriesTypes.pie.prototype, 'drawDataLabels', function(proceed) {
1835 if (this.chart.is3d()) {
1836 var series = this,
1837 chart = series.chart,
1838 options3d = chart.options.chart.options3d;
1839 each(series.data, function(point) {
1840 var shapeArgs = point.shapeArgs,
1841 r = shapeArgs.r,
1842 a1 = (shapeArgs.alpha || options3d.alpha) * deg2rad, //#3240 issue with datalabels for 0 and null values
1843 b1 = (shapeArgs.beta || options3d.beta) * deg2rad,
1844 a2 = (shapeArgs.start + shapeArgs.end) / 2,
1845 labelPos = point.labelPos,
1846 labelIndexes = [0, 2, 4], // [x1, y1, x2, y2, x3, y3]
1847 yOffset = (-r * (1 - Math.cos(a1)) * Math.sin(a2)), // + (sin(a2) > 0 ? sin(a1) * d : 0)
1848 xOffset = r * (Math.cos(b1) - 1) * Math.cos(a2);
1849
1850 // Apply perspective on label positions
1851 each(labelIndexes, function(index) {
1852 labelPos[index] += xOffset;
1853 labelPos[index + 1] += yOffset;
1854 });
1855 });
1856 }
1857
1858 proceed.apply(this, [].slice.call(arguments, 1));
1859 });
1860
1861 wrap(seriesTypes.pie.prototype, 'addPoint', function(proceed) {
1862 proceed.apply(this, [].slice.call(arguments, 1));
1863 if (this.chart.is3d()) {
1864 // destroy (and rebuild) everything!!!
1865 this.update(this.userOptions, true); // #3845 pass the old options
1866 }
1867 });
1868
1869 wrap(seriesTypes.pie.prototype, 'animate', function(proceed) {
1870 if (!this.chart.is3d()) {
1871 proceed.apply(this, [].slice.call(arguments, 1));
1872 } else {
1873 var args = arguments,
1874 init = args[1],
1875 animation = this.options.animation,
1876 attribs,
1877 center = this.center,
1878 group = this.group,
1879 markerGroup = this.markerGroup;
1880
1881 if (svg) { // VML is too slow anyway
1882
1883 if (animation === true) {
1884 animation = {};
1885 }
1886 // Initialize the animation
1887 if (init) {
1888
1889 // Scale down the group and place it in the center
1890 group.oldtranslateX = group.translateX;
1891 group.oldtranslateY = group.translateY;
1892 attribs = {
1893 translateX: center[0],
1894 translateY: center[1],
1895 scaleX: 0.001, // #1499
1896 scaleY: 0.001
1897 };
1898
1899 group.attr(attribs);
1900 if (markerGroup) {
1901 markerGroup.attrSetters = group.attrSetters;
1902 markerGroup.attr(attribs);
1903 }
1904
1905 // Run the animation
1906 } else {
1907 attribs = {
1908 translateX: group.oldtranslateX,
1909 translateY: group.oldtranslateY,
1910 scaleX: 1,
1911 scaleY: 1
1912 };
1913 group.animate(attribs, animation);
1914
1915 if (markerGroup) {
1916 markerGroup.animate(attribs, animation);
1917 }
1918
1919 // Delete this function to allow it only once
1920 this.animate = null;
1921 }
1922
1923 }
1924 }
1925 });
1926
1927 }(Highcharts));
1928 (function(H) {
1929 /**
1930 * (c) 2010-2016 Torstein Honsi
1931 *
1932 * License: www.highcharts.com/license
1933 */
1934 'use strict';
1935 var perspective = H.perspective,
1936 pick = H.pick,
1937 seriesTypes = H.seriesTypes,
1938 wrap = H.wrap;
1939
1940 /***
1941 EXTENSION FOR 3D SCATTER CHART
1942 ***/
1943
1944 wrap(seriesTypes.scatter.prototype, 'translate', function(proceed) {
1945 //function translate3d(proceed) {
1946 proceed.apply(this, [].slice.call(arguments, 1));
1947
1948 if (!this.chart.is3d()) {
1949 return;
1950 }
1951
1952 var series = this,
1953 chart = series.chart,
1954 zAxis = pick(series.zAxis, chart.options.zAxis[0]),
1955 rawPoints = [],
1956 rawPoint,
1957 projectedPoints,
1958 projectedPoint,
1959 zValue,
1960 i;
1961
1962 for (i = 0; i < series.data.length; i++) {
1963 rawPoint = series.data[i];
1964 zValue = zAxis.isLog && zAxis.val2lin ? zAxis.val2lin(rawPoint.z) : rawPoint.z; // #4562
1965 rawPoint.plotZ = zAxis.translate(zValue);
1966
1967 rawPoint.isInside = rawPoint.isInside ? (zValue >= zAxis.min && zValue <= zAxis.max) : false;
1968
1969 rawPoints.push({
1970 x: rawPoint.plotX,
1971 y: rawPoint.plotY,
1972 z: rawPoint.plotZ
1973 });
1974 }
1975
1976 projectedPoints = perspective(rawPoints, chart, true);
1977
1978 for (i = 0; i < series.data.length; i++) {
1979 rawPoint = series.data[i];
1980 projectedPoint = projectedPoints[i];
1981
1982 rawPoint.plotXold = rawPoint.plotX;
1983 rawPoint.plotYold = rawPoint.plotY;
1984 rawPoint.plotZold = rawPoint.plotZ;
1985
1986 rawPoint.plotX = projectedPoint.x;
1987 rawPoint.plotY = projectedPoint.y;
1988 rawPoint.plotZ = projectedPoint.z;
1989
1990
1991 }
1992
1993 });
1994
1995 wrap(seriesTypes.scatter.prototype, 'init', function(proceed, chart, options) {
1996 if (chart.is3d()) {
1997 // add a third coordinate
1998 this.axisTypes = ['xAxis', 'yAxis', 'zAxis'];
1999 this.pointArrayMap = ['x', 'y', 'z'];
2000 this.parallelArrays = ['x', 'y', 'z'];
2001
2002 // Require direct touch rather than using the k-d-tree, because the k-d-tree currently doesn't
2003 // take the xyz coordinate system into account (#4552)
2004 this.directTouch = true;
2005 }
2006
2007 var result = proceed.apply(this, [chart, options]);
2008
2009 if (this.chart.is3d()) {
2010 // Set a new default tooltip formatter
2011 var default3dScatterTooltip = 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>z: <b>{point.z}</b><br/>';
2012 if (this.userOptions.tooltip) {
2013 this.tooltipOptions.pointFormat = this.userOptions.tooltip.pointFormat || default3dScatterTooltip;
2014 } else {
2015 this.tooltipOptions.pointFormat = default3dScatterTooltip;
2016 }
2017 }
2018 return result;
2019 });
2020
2021 }(Highcharts));
2022 (function(H) {
2023 /**
2024 * (c) 2010-2016 Torstein Honsi
2025 *
2026 * License: www.highcharts.com/license
2027 */
2028 'use strict';
2029
2030 var Axis = H.Axis,
2031 SVGRenderer = H.SVGRenderer,
2032 VMLRenderer = H.VMLRenderer;
2033
2034 /**
2035 * Extension to the VML Renderer
2036 */
2037 if (VMLRenderer) {
2038
2039 H.setOptions({
2040 animate: false
2041 });
2042
2043 VMLRenderer.prototype.cuboid = SVGRenderer.prototype.cuboid;
2044 VMLRenderer.prototype.cuboidPath = SVGRenderer.prototype.cuboidPath;
2045
2046 VMLRenderer.prototype.toLinePath = SVGRenderer.prototype.toLinePath;
2047
2048 VMLRenderer.prototype.createElement3D = SVGRenderer.prototype.createElement3D;
2049
2050 VMLRenderer.prototype.arc3d = function(shapeArgs) {
2051 var result = SVGRenderer.prototype.arc3d.call(this, shapeArgs);
2052 result.css({
2053 zIndex: result.zIndex
2054 });
2055 return result;
2056 };
2057
2058 H.VMLRenderer.prototype.arc3dPath = H.SVGRenderer.prototype.arc3dPath;
2059
2060 H.wrap(Axis.prototype, 'render', function(proceed) {
2061 proceed.apply(this, [].slice.call(arguments, 1));
2062 // VML doesn't support a negative z-index
2063 if (this.sideFrame) {
2064 this.sideFrame.css({
2065 zIndex: 0
2066 });
2067 this.sideFrame.front.attr({
2068 fill: this.sideFrame.color
2069 });
2070 }
2071 if (this.bottomFrame) {
2072 this.bottomFrame.css({
2073 zIndex: 1
2074 });
2075 this.bottomFrame.front.attr({
2076 fill: this.bottomFrame.color
2077 });
2078 }
2079 if (this.backFrame) {
2080 this.backFrame.css({
2081 zIndex: 0
2082 });
2083 this.backFrame.front.attr({
2084 fill: this.backFrame.color
2085 });
2086 }
2087 });
2088
2089 }
2090
2091
2092 }(Highcharts));
2093}));