UNPKG

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