UNPKG

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