UNPKG

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