UNPKG

170 kBJavaScriptView Raw
1/**
2 * @license Highcharts JS v6.0.3 (2017-11-14)
3 *
4 * 3D features for Highcharts JS
5 *
6 * @license: www.highcharts.com/license
7 */
8'use strict';
9(function(factory) {
10 if (typeof module === 'object' && module.exports) {
11 module.exports = factory;
12 } else {
13 factory(Highcharts);
14 }
15}(function(Highcharts) {
16 (function(H) {
17 /**
18 * (c) 2010-2017 Torstein Honsi
19 *
20 * License: www.highcharts.com/license
21 */
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 /**
138 * Calculate area of a 2D polygon using Shoelace algorithm
139 * http://en.wikipedia.org/wiki/Shoelace_formula
140 */
141 H.shapeArea = function(vertexes) {
142 var area = 0,
143 i,
144 j;
145 for (i = 0; i < vertexes.length; i++) {
146 j = (i + 1) % vertexes.length;
147 area += vertexes[i].x * vertexes[j].y - vertexes[j].x * vertexes[i].y;
148 }
149 return area / 2;
150 };
151
152 /**
153 * Calculate area of a 3D polygon after perspective projection
154 */
155 H.shapeArea3d = function(vertexes, chart, insidePlotArea) {
156 return H.shapeArea(H.perspective(vertexes, chart, insidePlotArea));
157 };
158
159
160 }(Highcharts));
161 (function(H) {
162 /**
163 * (c) 2010-2017 Torstein Honsi
164 *
165 * License: www.highcharts.com/license
166 */
167 var cos = Math.cos,
168 PI = Math.PI,
169 sin = Math.sin;
170
171
172 var animObject = H.animObject,
173 charts = H.charts,
174 color = H.color,
175 defined = H.defined,
176 deg2rad = H.deg2rad,
177 each = H.each,
178 extend = H.extend,
179 inArray = H.inArray,
180 map = H.map,
181 merge = H.merge,
182 perspective = H.perspective,
183 pick = H.pick,
184 SVGElement = H.SVGElement,
185 SVGRenderer = H.SVGRenderer,
186 wrap = H.wrap;
187 /*
188 EXTENSION TO THE SVG-RENDERER TO ENABLE 3D SHAPES
189 */
190 // HELPER METHODS //
191
192 var dFactor = (4 * (Math.sqrt(2) - 1) / 3) / (PI / 2);
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 SVGRenderer.prototype.toLineSegments = function(points) {
246 var result = [];
247
248 var m = true;
249 each(points, function(point) {
250 result.push(m ? 'M' : 'L', point.x, point.y);
251 m = !m;
252 });
253
254 return result;
255 };
256
257 /**
258 * A 3-D Face is defined by it's 3D vertexes, and is only
259 * visible if it's vertexes are counter-clockwise (Back-face culling).
260 * It is used as a polyhedron Element
261 */
262 SVGRenderer.prototype.face3d = function(args) {
263 var renderer = this,
264 ret = this.createElement('path');
265 ret.vertexes = [];
266 ret.insidePlotArea = false;
267 ret.enabled = true;
268
269 wrap(ret, 'attr', function(proceed, hash) {
270 if (typeof hash === 'object' &&
271 (defined(hash.enabled) || defined(hash.vertexes) || defined(hash.insidePlotArea))) {
272 this.enabled = pick(hash.enabled, this.enabled);
273 this.vertexes = pick(hash.vertexes, this.vertexes);
274 this.insidePlotArea = pick(hash.insidePlotArea, this.insidePlotArea);
275 delete hash.enabled;
276 delete hash.vertexes;
277 delete hash.insidePlotArea;
278
279 var chart = charts[renderer.chartIndex],
280 vertexes2d = perspective(this.vertexes, chart, this.insidePlotArea),
281 path = renderer.toLinePath(vertexes2d, true),
282 area = H.shapeArea(vertexes2d),
283 visibility = (this.enabled && area > 0) ? 'visible' : 'hidden';
284
285 hash.d = path;
286 hash.visibility = visibility;
287 }
288 return proceed.apply(this, [].slice.call(arguments, 1));
289 });
290
291 wrap(ret, 'animate', function(proceed, params) {
292 if (typeof params === 'object' &&
293 (defined(params.enabled) || defined(params.vertexes) || defined(params.insidePlotArea))) {
294 this.enabled = pick(params.enabled, this.enabled);
295 this.vertexes = pick(params.vertexes, this.vertexes);
296 this.insidePlotArea = pick(params.insidePlotArea, this.insidePlotArea);
297 delete params.enabled;
298 delete params.vertexes;
299 delete params.insidePlotArea;
300
301 var chart = charts[renderer.chartIndex],
302 vertexes2d = perspective(this.vertexes, chart, this.insidePlotArea),
303 path = renderer.toLinePath(vertexes2d, true),
304 area = H.shapeArea(vertexes2d),
305 visibility = (this.enabled && area > 0) ? 'visible' : 'hidden';
306
307 params.d = path;
308 this.attr('visibility', visibility);
309 }
310
311 return proceed.apply(this, [].slice.call(arguments, 1));
312 });
313
314 return ret.attr(args);
315 };
316
317 /**
318 * A Polyhedron is a handy way of defining a group of 3-D faces.
319 * It's only attribute is `faces`, an array of attributes of each one of it's Face3D instances.
320 */
321 SVGRenderer.prototype.polyhedron = function(args) {
322 var renderer = this,
323 result = this.g(),
324 destroy = result.destroy;
325
326
327 result.attr({
328 'stroke-linejoin': 'round'
329 });
330
331
332 result.faces = [];
333
334
335 // destroy all children
336 result.destroy = function() {
337 for (var i = 0; i < result.faces.length; i++) {
338 result.faces[i].destroy();
339 }
340 return destroy.call(this);
341 };
342
343 wrap(result, 'attr', function(proceed, hash, val, complete, continueAnimation) {
344 if (typeof hash === 'object' && defined(hash.faces)) {
345 while (result.faces.length > hash.faces.length) {
346 result.faces.pop().destroy();
347 }
348 while (result.faces.length < hash.faces.length) {
349 result.faces.push(renderer.face3d().add(result));
350 }
351 for (var i = 0; i < hash.faces.length; i++) {
352 result.faces[i].attr(hash.faces[i], null, complete, continueAnimation);
353 }
354 delete hash.faces;
355 }
356 return proceed.apply(this, [].slice.call(arguments, 1));
357 });
358
359 wrap(result, 'animate', function(proceed, params, duration, complete) {
360 if (params && params.faces) {
361 while (result.faces.length > params.faces.length) {
362 result.faces.pop().destroy();
363 }
364 while (result.faces.length < params.faces.length) {
365 result.faces.push(renderer.face3d().add(result));
366 }
367 for (var i = 0; i < params.faces.length; i++) {
368 result.faces[i].animate(params.faces[i], duration, complete);
369 }
370 delete params.faces;
371 }
372 return proceed.apply(this, [].slice.call(arguments, 1));
373 });
374
375 return result.attr(args);
376 };
377
378 // CUBOIDS //
379 SVGRenderer.prototype.cuboid = function(shapeArgs) {
380
381 var result = this.g(),
382 destroy = result.destroy,
383 paths = this.cuboidPath(shapeArgs);
384
385
386 result.attr({
387 'stroke-linejoin': 'round'
388 });
389
390
391 // create the 3 sides
392 result.front = this.path(paths[0]).attr({
393 'class': 'highcharts-3d-front'
394 }).add(result); // Front, top and side are never overlapping in our case so it is redundant to set zIndex of every element.
395 result.top = this.path(paths[1]).attr({
396 'class': 'highcharts-3d-top'
397 }).add(result);
398 result.side = this.path(paths[2]).attr({
399 'class': 'highcharts-3d-side'
400 }).add(result);
401
402 // apply the fill everywhere, the top a bit brighter, the side a bit darker
403 result.fillSetter = function(fill) {
404 this.front.attr({
405 fill: fill
406 });
407 this.top.attr({
408 fill: color(fill).brighten(0.1).get()
409 });
410 this.side.attr({
411 fill: color(fill).brighten(-0.1).get()
412 });
413 this.color = fill;
414
415 // for animation getter (#6776)
416 result.fill = fill;
417
418 return this;
419 };
420
421 // apply opacaity everywhere
422 result.opacitySetter = function(opacity) {
423 this.front.attr({
424 opacity: opacity
425 });
426 this.top.attr({
427 opacity: opacity
428 });
429 this.side.attr({
430 opacity: opacity
431 });
432 return this;
433 };
434
435 result.attr = function(args, val, complete, continueAnimation) {
436
437 // Resolve setting attributes by string name
438 if (typeof args === 'string' && typeof val !== 'undefined') {
439 var key = args;
440 args = {};
441 args[key] = val;
442 }
443
444 if (args.shapeArgs || defined(args.x)) {
445 var shapeArgs = args.shapeArgs || args;
446 var paths = this.renderer.cuboidPath(shapeArgs);
447 this.front.attr({
448 d: paths[0]
449 });
450 this.top.attr({
451 d: paths[1]
452 });
453 this.side.attr({
454 d: paths[2]
455 });
456 } else {
457 // getter returns value
458 return SVGElement.prototype.attr.call(
459 this, args, undefined, complete, continueAnimation
460 );
461 }
462
463 return this;
464 };
465
466 result.animate = function(args, duration, complete) {
467 if (defined(args.x) && defined(args.y)) {
468 var paths = this.renderer.cuboidPath(args);
469 this.front.animate({
470 d: paths[0]
471 }, duration, complete);
472 this.top.animate({
473 d: paths[1]
474 }, duration, complete);
475 this.side.animate({
476 d: paths[2]
477 }, duration, complete);
478 this.attr({
479 zIndex: -paths[3] // #4774
480 });
481 } else if (args.opacity) {
482 this.front.animate(args, duration, complete);
483 this.top.animate(args, duration, complete);
484 this.side.animate(args, duration, complete);
485 } else {
486 SVGElement.prototype.animate.call(this, args, duration, complete);
487 }
488 return this;
489 };
490
491 // destroy all children
492 result.destroy = function() {
493 this.front.destroy();
494 this.top.destroy();
495 this.side.destroy();
496
497 return destroy.call(this);
498 };
499
500 // Apply the Z index to the cuboid group
501 result.attr({
502 zIndex: -paths[3]
503 });
504
505 return result;
506 };
507
508 /**
509 * Generates a cuboid
510 */
511 H.SVGRenderer.prototype.cuboidPath = function(shapeArgs) {
512 var x = shapeArgs.x,
513 y = shapeArgs.y,
514 z = shapeArgs.z,
515 h = shapeArgs.height,
516 w = shapeArgs.width,
517 d = shapeArgs.depth,
518 chart = charts[this.chartIndex],
519 front,
520 back,
521 top,
522 bottom,
523 left,
524 right,
525 shape,
526 path1,
527 path2,
528 path3,
529 isFront,
530 isTop,
531 isRight,
532 options3d = chart.options.chart.options3d,
533 alpha = options3d.alpha,
534 // Priority for x axis is the biggest,
535 // because of x direction has biggest influence on zIndex
536 incrementX = 10000,
537 // y axis has the smallest priority in case of our charts
538 // (needs to be set because of stacking)
539 incrementY = 10,
540 incrementZ = 100,
541 zIndex = 0;
542
543 // The 8 corners of the cube
544 var pArr = [{
545 x: x,
546 y: y,
547 z: z
548 }, {
549 x: x + w,
550 y: y,
551 z: z
552 }, {
553 x: x + w,
554 y: y + h,
555 z: z
556 }, {
557 x: x,
558 y: y + h,
559 z: z
560 }, {
561 x: x,
562 y: y + h,
563 z: z + d
564 }, {
565 x: x + w,
566 y: y + h,
567 z: z + d
568 }, {
569 x: x + w,
570 y: y,
571 z: z + d
572 }, {
573 x: x,
574 y: y,
575 z: z + d
576 }];
577
578 // apply perspective
579 pArr = perspective(pArr, chart, shapeArgs.insidePlotArea);
580
581 // helper method to decide which side is visible
582 function mapPath(i) {
583 return pArr[i];
584 }
585
586 /*
587 * First value - path with specific side
588 * Second value - added information about side for later calculations.
589 * Possible second values are 0 for path1, 1 for path2 and -1 for no path choosed.
590 */
591 var pickShape = function(path1, path2) {
592 var ret = [
593 [], -1
594 ];
595 path1 = map(path1, mapPath);
596 path2 = map(path2, mapPath);
597 if (H.shapeArea(path1) < 0) {
598 ret = [path1, 0];
599 } else if (H.shapeArea(path2) < 0) {
600 ret = [path2, 1];
601 }
602 return ret;
603 };
604
605 // front or back
606 front = [3, 2, 1, 0];
607 back = [7, 6, 5, 4];
608 shape = pickShape(front, back);
609 path1 = shape[0];
610 isFront = shape[1];
611
612
613 // top or bottom
614 top = [1, 6, 7, 0];
615 bottom = [4, 5, 2, 3];
616 shape = pickShape(top, bottom);
617 path2 = shape[0];
618 isTop = shape[1];
619
620 // side
621 right = [1, 2, 5, 6];
622 left = [0, 7, 4, 3];
623 shape = pickShape(right, left);
624 path3 = shape[0];
625 isRight = shape[1];
626
627 /*
628 * New block used for calculating zIndex. It is basing on X, Y and Z position of specific columns.
629 * All zIndexes (for X, Y and Z values) are added to the final zIndex, where every value has different priority.
630 * The biggest priority is in X and Z directions, the lowest index is for stacked columns (Y direction and the same X and Z positions).
631 * Big differents between priorities is made because we need to ensure that even for big changes in Y and Z parameters
632 * all columns will be drawn correctly.
633 */
634
635 if (isRight === 1) {
636 zIndex += incrementX * (1000 - x);
637 } else if (!isRight) {
638 zIndex += incrementX * x;
639 }
640
641 zIndex += incrementY * (!isTop ||
642 (alpha >= 0 && alpha <= 180 || alpha < 360 && alpha > 357.5) ? // Numbers checked empirically
643 chart.plotHeight - y : 10 + y
644 );
645
646 if (isFront === 1) {
647 zIndex += incrementZ * (z);
648 } else if (!isFront) {
649 zIndex += incrementZ * (1000 - z);
650 }
651
652 zIndex = -Math.round(zIndex);
653
654 return [
655 this.toLinePath(path1, true),
656 this.toLinePath(path2, true),
657 this.toLinePath(path3, true),
658 zIndex
659 ]; // #4774
660 };
661
662 // SECTORS //
663 H.SVGRenderer.prototype.arc3d = function(attribs) {
664
665 var wrapper = this.g(),
666 renderer = wrapper.renderer,
667 customAttribs = ['x', 'y', 'r', 'innerR', 'start', 'end'];
668
669 /**
670 * Get custom attributes. Don't mutate the original object and return an object with only custom attr.
671 */
672 function suckOutCustom(params) {
673 var hasCA = false,
674 ca = {};
675
676 params = merge(params); // Don't mutate the original object
677
678 for (var key in params) {
679 if (inArray(key, customAttribs) !== -1) {
680 ca[key] = params[key];
681 delete params[key];
682 hasCA = true;
683 }
684 }
685 return hasCA ? ca : false;
686 }
687
688 attribs = merge(attribs);
689
690 attribs.alpha *= deg2rad;
691 attribs.beta *= deg2rad;
692
693 // Create the different sub sections of the shape
694 wrapper.top = renderer.path();
695 wrapper.side1 = renderer.path();
696 wrapper.side2 = renderer.path();
697 wrapper.inn = renderer.path();
698 wrapper.out = renderer.path();
699
700 /**
701 * Add all faces
702 */
703 wrapper.onAdd = function() {
704 var parent = wrapper.parentGroup,
705 className = wrapper.attr('class');
706 wrapper.top.add(wrapper);
707
708 // These faces are added outside the wrapper group because the z index
709 // relates to neighbour elements as well
710 each(['out', 'inn', 'side1', 'side2'], function(face) {
711 wrapper[face]
712 .attr({
713 'class': className + ' highcharts-3d-side'
714 })
715 .add(parent);
716 });
717 };
718
719 // Cascade to faces
720 each(['addClass', 'removeClass'], function(fn) {
721 wrapper[fn] = function() {
722 var args = arguments;
723 each(['top', 'out', 'inn', 'side1', 'side2'], function(face) {
724 wrapper[face][fn].apply(wrapper[face], args);
725 });
726 };
727 });
728
729 /**
730 * Compute the transformed paths and set them to the composite shapes
731 */
732 wrapper.setPaths = function(attribs) {
733
734 var paths = wrapper.renderer.arc3dPath(attribs),
735 zIndex = paths.zTop * 100;
736
737 wrapper.attribs = attribs;
738
739 wrapper.top.attr({
740 d: paths.top,
741 zIndex: paths.zTop
742 });
743 wrapper.inn.attr({
744 d: paths.inn,
745 zIndex: paths.zInn
746 });
747 wrapper.out.attr({
748 d: paths.out,
749 zIndex: paths.zOut
750 });
751 wrapper.side1.attr({
752 d: paths.side1,
753 zIndex: paths.zSide1
754 });
755 wrapper.side2.attr({
756 d: paths.side2,
757 zIndex: paths.zSide2
758 });
759
760
761 // show all children
762 wrapper.zIndex = zIndex;
763 wrapper.attr({
764 zIndex: zIndex
765 });
766
767 // Set the radial gradient center the first time
768 if (attribs.center) {
769 wrapper.top.setRadialReference(attribs.center);
770 delete attribs.center;
771 }
772 };
773 wrapper.setPaths(attribs);
774
775 // Apply the fill to the top and a darker shade to the sides
776 wrapper.fillSetter = function(value) {
777 var darker = color(value).brighten(-0.1).get();
778
779 this.fill = value;
780
781 this.side1.attr({
782 fill: darker
783 });
784 this.side2.attr({
785 fill: darker
786 });
787 this.inn.attr({
788 fill: darker
789 });
790 this.out.attr({
791 fill: darker
792 });
793 this.top.attr({
794 fill: value
795 });
796 return this;
797 };
798
799 // Apply the same value to all. These properties cascade down to the children
800 // when set to the composite arc3d.
801 each(['opacity', 'translateX', 'translateY', 'visibility'], function(setter) {
802 wrapper[setter + 'Setter'] = function(value, key) {
803 wrapper[key] = value;
804 each(['out', 'inn', 'side1', 'side2', 'top'], function(el) {
805 wrapper[el].attr(key, value);
806 });
807 };
808 });
809
810 /**
811 * Override attr to remove shape attributes and use those to set child paths
812 */
813 wrap(wrapper, 'attr', function(proceed, params) {
814 var ca;
815 if (typeof params === 'object') {
816 ca = suckOutCustom(params);
817 if (ca) {
818 extend(wrapper.attribs, ca);
819 wrapper.setPaths(wrapper.attribs);
820 }
821 }
822 return proceed.apply(this, [].slice.call(arguments, 1));
823 });
824
825 /**
826 * Override the animate function by sucking out custom parameters related to the shapes directly,
827 * and update the shapes from the animation step.
828 */
829 wrap(wrapper, 'animate', function(proceed, params, animation, complete) {
830 var ca,
831 from = this.attribs,
832 to,
833 anim;
834
835 // Attribute-line properties connected to 3D. These shouldn't have been in the
836 // attribs collection in the first place.
837 delete params.center;
838 delete params.z;
839 delete params.depth;
840 delete params.alpha;
841 delete params.beta;
842
843 anim = animObject(pick(animation, this.renderer.globalAnimation));
844
845 if (anim.duration) {
846 ca = suckOutCustom(params);
847 params.dummy = 1; // Params need to have a property in order for the step to run (#5765)
848
849 if (ca) {
850 to = ca;
851 anim.step = function(a, fx) {
852 function interpolate(key) {
853 return from[key] + (pick(to[key], from[key]) - from[key]) * fx.pos;
854 }
855
856 if (fx.prop === 'dummy') {
857 fx.elem.setPaths(merge(from, {
858 x: interpolate('x'),
859 y: interpolate('y'),
860 r: interpolate('r'),
861 innerR: interpolate('innerR'),
862 start: interpolate('start'),
863 end: interpolate('end')
864 }));
865 }
866 };
867 }
868 animation = anim; // Only when duration (#5572)
869 }
870 return proceed.call(this, params, animation, complete);
871 });
872
873 // destroy all children
874 wrapper.destroy = function() {
875 this.top.destroy();
876 this.out.destroy();
877 this.inn.destroy();
878 this.side1.destroy();
879 this.side2.destroy();
880
881 SVGElement.prototype.destroy.call(this);
882 };
883 // hide all children
884 wrapper.hide = function() {
885 this.top.hide();
886 this.out.hide();
887 this.inn.hide();
888 this.side1.hide();
889 this.side2.hide();
890 };
891 wrapper.show = function() {
892 this.top.show();
893 this.out.show();
894 this.inn.show();
895 this.side1.show();
896 this.side2.show();
897 };
898 return wrapper;
899 };
900
901 /**
902 * Generate the paths required to draw a 3D arc
903 */
904 SVGRenderer.prototype.arc3dPath = function(shapeArgs) {
905 var cx = shapeArgs.x, // x coordinate of the center
906 cy = shapeArgs.y, // y coordinate of the center
907 start = shapeArgs.start, // start angle
908 end = shapeArgs.end - 0.00001, // end angle
909 r = shapeArgs.r, // radius
910 ir = shapeArgs.innerR, // inner radius
911 d = shapeArgs.depth, // depth
912 alpha = shapeArgs.alpha, // alpha rotation of the chart
913 beta = shapeArgs.beta; // beta rotation of the chart
914
915 // Derived Variables
916 var cs = Math.cos(start), // cosinus of the start angle
917 ss = Math.sin(start), // sinus of the start angle
918 ce = Math.cos(end), // cosinus of the end angle
919 se = Math.sin(end), // sinus of the end angle
920 rx = r * Math.cos(beta), // x-radius
921 ry = r * Math.cos(alpha), // y-radius
922 irx = ir * Math.cos(beta), // x-radius (inner)
923 iry = ir * Math.cos(alpha), // y-radius (inner)
924 dx = d * Math.sin(beta), // distance between top and bottom in x
925 dy = d * Math.sin(alpha); // distance between top and bottom in y
926
927 // TOP
928 var top = ['M', cx + (rx * cs), cy + (ry * ss)];
929 top = top.concat(curveTo(cx, cy, rx, ry, start, end, 0, 0));
930 top = top.concat([
931 'L', cx + (irx * ce), cy + (iry * se)
932 ]);
933 top = top.concat(curveTo(cx, cy, irx, iry, end, start, 0, 0));
934 top = top.concat(['Z']);
935 // OUTSIDE
936 var b = (beta > 0 ? Math.PI / 2 : 0),
937 a = (alpha > 0 ? 0 : Math.PI / 2);
938
939 var start2 = start > -b ? start : (end > -b ? -b : start),
940 end2 = end < PI - a ? end : (start < PI - a ? PI - a : end),
941 midEnd = 2 * PI - a;
942
943 // When slice goes over bottom middle, need to add both, left and right outer side.
944 // Additionally, when we cross right hand edge, create sharp edge. Outer shape/wall:
945 //
946 // -------
947 // / ^ \
948 // 4) / / \ \ 1)
949 // / / \ \
950 // / / \ \
951 // (c)=> ==== ==== <=(d)
952 // \ \ / /
953 // \ \<=(a)/ /
954 // \ \ / / <=(b)
955 // 3) \ v / 2)
956 // -------
957 //
958 // (a) - inner side
959 // (b) - outer side
960 // (c) - left edge (sharp)
961 // (d) - right edge (sharp)
962 // 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
963
964 var out = ['M', cx + (rx * cos(start2)), cy + (ry * sin(start2))];
965 out = out.concat(curveTo(cx, cy, rx, ry, start2, end2, 0, 0));
966
967 if (end > midEnd && start < midEnd) { // When shape is wide, it can cross both, (c) and (d) edges, when using startAngle
968 // Go to outer side
969 out = out.concat([
970 'L', cx + (rx * cos(end2)) + dx, cy + (ry * sin(end2)) + dy
971 ]);
972 // Curve to the right edge of the slice (d)
973 out = out.concat(curveTo(cx, cy, rx, ry, end2, midEnd, dx, dy));
974 // Go to the inner side
975 out = out.concat([
976 'L', cx + (rx * cos(midEnd)), cy + (ry * sin(midEnd))
977 ]);
978 // Curve to the true end of the slice
979 out = out.concat(curveTo(cx, cy, rx, ry, midEnd, end, 0, 0));
980 // Go to the outer side
981 out = out.concat([
982 'L', cx + (rx * cos(end)) + dx, cy + (ry * sin(end)) + dy
983 ]);
984 // Go back to middle (d)
985 out = out.concat(curveTo(cx, cy, rx, ry, end, midEnd, dx, dy));
986 out = out.concat([
987 'L', cx + (rx * cos(midEnd)), cy + (ry * sin(midEnd))
988 ]);
989 // Go back to the left edge
990 out = out.concat(curveTo(cx, cy, rx, ry, midEnd, end2, 0, 0));
991 } else if (end > PI - a && start < PI - a) { // But shape can cross also only (c) edge:
992 // Go to outer side
993 out = out.concat([
994 'L', cx + (rx * Math.cos(end2)) + dx, cy + (ry * Math.sin(end2)) + dy
995 ]);
996 // Curve to the true end of the slice
997 out = out.concat(curveTo(cx, cy, rx, ry, end2, end, dx, dy));
998 // Go to the inner side
999 out = out.concat([
1000 'L', cx + (rx * Math.cos(end)), cy + (ry * Math.sin(end))
1001 ]);
1002 // Go back to the artifical end2
1003 out = out.concat(curveTo(cx, cy, rx, ry, end, end2, 0, 0));
1004 }
1005
1006 out = out.concat([
1007 'L', cx + (rx * Math.cos(end2)) + dx, cy + (ry * Math.sin(end2)) + dy
1008 ]);
1009 out = out.concat(curveTo(cx, cy, rx, ry, end2, start2, dx, dy));
1010 out = out.concat(['Z']);
1011
1012 // INSIDE
1013 var inn = ['M', cx + (irx * cs), cy + (iry * ss)];
1014 inn = inn.concat(curveTo(cx, cy, irx, iry, start, end, 0, 0));
1015 inn = inn.concat([
1016 'L', cx + (irx * Math.cos(end)) + dx, cy + (iry * Math.sin(end)) + dy
1017 ]);
1018 inn = inn.concat(curveTo(cx, cy, irx, iry, end, start, dx, dy));
1019 inn = inn.concat(['Z']);
1020
1021 // SIDES
1022 var side1 = [
1023 'M', cx + (rx * cs), cy + (ry * ss),
1024 'L', cx + (rx * cs) + dx, cy + (ry * ss) + dy,
1025 'L', cx + (irx * cs) + dx, cy + (iry * ss) + dy,
1026 'L', cx + (irx * cs), cy + (iry * ss),
1027 'Z'
1028 ];
1029 var side2 = [
1030 'M', cx + (rx * ce), cy + (ry * se),
1031 'L', cx + (rx * ce) + dx, cy + (ry * se) + dy,
1032 'L', cx + (irx * ce) + dx, cy + (iry * se) + dy,
1033 'L', cx + (irx * ce), cy + (iry * se),
1034 'Z'
1035 ];
1036
1037 // correction for changed position of vanishing point caused by alpha and beta rotations
1038 var angleCorr = Math.atan2(dy, -dx),
1039 angleEnd = Math.abs(end + angleCorr),
1040 angleStart = Math.abs(start + angleCorr),
1041 angleMid = Math.abs((start + end) / 2 + angleCorr);
1042
1043 // set to 0-PI range
1044 function toZeroPIRange(angle) {
1045 angle = angle % (2 * Math.PI);
1046 if (angle > Math.PI) {
1047 angle = 2 * Math.PI - angle;
1048 }
1049 return angle;
1050 }
1051 angleEnd = toZeroPIRange(angleEnd);
1052 angleStart = toZeroPIRange(angleStart);
1053 angleMid = toZeroPIRange(angleMid);
1054
1055 // *1e5 is to compensate pInt in zIndexSetter
1056 var incPrecision = 1e5,
1057 a1 = angleMid * incPrecision,
1058 a2 = angleStart * incPrecision,
1059 a3 = angleEnd * incPrecision;
1060
1061 return {
1062 top: top,
1063 zTop: Math.PI * incPrecision + 1, // max angle is PI, so this is allways higher
1064 out: out,
1065 zOut: Math.max(a1, a2, a3),
1066 inn: inn,
1067 zInn: Math.max(a1, a2, a3),
1068 side1: side1,
1069 zSide1: a3 * 0.99, // to keep below zOut and zInn in case of same values
1070 side2: side2,
1071 zSide2: a2 * 0.99
1072 };
1073 };
1074
1075 }(Highcharts));
1076 (function(H) {
1077 /**
1078 * (c) 2010-2017 Torstein Honsi
1079 *
1080 * Extension for 3D charts
1081 *
1082 * License: www.highcharts.com/license
1083 */
1084 var Chart = H.Chart,
1085 each = H.each,
1086 merge = H.merge,
1087 perspective = H.perspective,
1088 pick = H.pick,
1089 wrap = H.wrap;
1090
1091 // Shorthand to check the is3d flag
1092 Chart.prototype.is3d = function() {
1093 return this.options.chart.options3d && this.options.chart.options3d.enabled; // #4280
1094 };
1095
1096 Chart.prototype.propsRequireDirtyBox.push('chart.options3d');
1097 Chart.prototype.propsRequireUpdateSeries.push('chart.options3d');
1098
1099 // Legacy support for HC < 6 to make 'scatter' series in a 3D chart route to the
1100 // real 'scatter3d' series type.
1101 wrap(Chart.prototype, 'initSeries', function(proceed, options) {
1102 var type = options.type ||
1103 this.options.chart.type ||
1104 this.options.chart.defaultSeriesType;
1105 if (this.is3d() && type === 'scatter') {
1106 options.type = 'scatter3d';
1107 }
1108 return proceed.call(this, options);
1109 });
1110
1111 /**
1112 * Calculate scale of the 3D view. That is required to
1113 * fit chart's 3D projection into the actual plotting area. Reported as #4933.
1114 * @notice This function should ideally take the plot values instead of a chart object,
1115 * but since the chart object is needed for perspective it is not practical.
1116 * Possible to make both getScale and perspective more logical and also immutable.
1117 * @param {Object} chart Chart object
1118 * @param {Number} chart.plotLeft
1119 * @param {Number} chart.plotWidth
1120 * @param {Number} chart.plotTop
1121 * @param {Number} chart.plotHeight
1122 * @param {Number} depth The depth of the chart
1123 * @return {Number} The scale to fit the 3D chart into the plotting area.
1124 */
1125 function getScale(chart, depth) {
1126 var plotLeft = chart.plotLeft,
1127 plotRight = chart.plotWidth + plotLeft,
1128 plotTop = chart.plotTop,
1129 plotBottom = chart.plotHeight + plotTop,
1130 originX = plotLeft + chart.plotWidth / 2,
1131 originY = plotTop + chart.plotHeight / 2,
1132 bbox3d = {
1133 minX: Number.MAX_VALUE,
1134 maxX: -Number.MAX_VALUE,
1135 minY: Number.MAX_VALUE,
1136 maxY: -Number.MAX_VALUE
1137 },
1138 corners,
1139 scale = 1;
1140
1141 // Top left corners:
1142 corners = [{
1143 x: plotLeft,
1144 y: plotTop,
1145 z: 0
1146 }, {
1147 x: plotLeft,
1148 y: plotTop,
1149 z: depth
1150 }];
1151
1152 // Top right corners:
1153 each([0, 1], function(i) {
1154 corners.push({
1155 x: plotRight,
1156 y: corners[i].y,
1157 z: corners[i].z
1158 });
1159 });
1160
1161 // All bottom corners:
1162 each([0, 1, 2, 3], function(i) {
1163 corners.push({
1164 x: corners[i].x,
1165 y: plotBottom,
1166 z: corners[i].z
1167 });
1168 });
1169
1170 // Calculate 3D corners:
1171 corners = perspective(corners, chart, false);
1172
1173 // Get bounding box of 3D element:
1174 each(corners, function(corner) {
1175 bbox3d.minX = Math.min(bbox3d.minX, corner.x);
1176 bbox3d.maxX = Math.max(bbox3d.maxX, corner.x);
1177 bbox3d.minY = Math.min(bbox3d.minY, corner.y);
1178 bbox3d.maxY = Math.max(bbox3d.maxY, corner.y);
1179 });
1180
1181 // Left edge:
1182 if (plotLeft > bbox3d.minX) {
1183 scale = Math.min(scale, 1 - Math.abs((plotLeft + originX) / (bbox3d.minX + originX)) % 1);
1184 }
1185
1186 // Right edge:
1187 if (plotRight < bbox3d.maxX) {
1188 scale = Math.min(scale, (plotRight - originX) / (bbox3d.maxX - originX));
1189 }
1190
1191 // Top edge:
1192 if (plotTop > bbox3d.minY) {
1193 if (bbox3d.minY < 0) {
1194 scale = Math.min(scale, (plotTop + originY) / (-bbox3d.minY + plotTop + originY));
1195 } else {
1196 scale = Math.min(scale, 1 - (plotTop + originY) / (bbox3d.minY + originY) % 1);
1197 }
1198 }
1199
1200 // Bottom edge:
1201 if (plotBottom < bbox3d.maxY) {
1202 scale = Math.min(scale, Math.abs((plotBottom - originY) / (bbox3d.maxY - originY)));
1203 }
1204
1205 return scale;
1206 }
1207
1208
1209
1210 H.wrap(H.Chart.prototype, 'isInsidePlot', function(proceed) {
1211 return this.is3d() || proceed.apply(this, [].slice.call(arguments, 1));
1212 });
1213
1214 var defaultOptions = H.getOptions();
1215
1216 /**
1217 * Options to render charts in 3 dimensions.
1218 * This feature requires highcharts-3d.js, found in the download package,
1219 * or online at code.highcharts.com/highcharts-3d.js.
1220 * @optionparent
1221 */
1222 var extendedOptions = {
1223
1224 /**
1225 * Options regarding the chart area and plot area as well as general
1226 * chart options.
1227 *
1228 */
1229 chart: {
1230
1231 /**
1232 * Options to render charts in 3 dimensions. This feature requires
1233 * `highcharts-3d.js`, found in the download package or online at
1234 * [code.highcharts.com/highcharts-3d.js](http://code.highcharts.com/highcharts-
1235 * 3d.js).
1236 *
1237 * @since 4.0
1238 * @product highcharts
1239 */
1240 options3d: {
1241
1242 /**
1243 * Wether to render the chart using the 3D functionality.
1244 *
1245 * @type {Boolean}
1246 * @default false
1247 * @since 4.0
1248 * @product highcharts
1249 */
1250 enabled: false,
1251
1252 /**
1253 * One of the two rotation angles for the chart.
1254 *
1255 * @type {Number}
1256 * @default 0
1257 * @since 4.0
1258 * @product highcharts
1259 */
1260 alpha: 0,
1261
1262 /**
1263 * One of the two rotation angles for the chart.
1264 *
1265 * @type {Number}
1266 * @default 0
1267 * @since 4.0
1268 * @product highcharts
1269 */
1270 beta: 0,
1271
1272 /**
1273 * The total depth of the chart.
1274 *
1275 * @type {Number}
1276 * @default 100
1277 * @since 4.0
1278 * @product highcharts
1279 */
1280 depth: 100,
1281
1282 /**
1283 * Whether the 3d box should automatically adjust to the chart plot
1284 * area.
1285 *
1286 * @type {Boolean}
1287 * @default true
1288 * @since 4.2.4
1289 * @product highcharts
1290 */
1291 fitToPlot: true,
1292
1293 /**
1294 * Defines the distance the viewer is standing in front of the chart,
1295 * this setting is important to calculate the perspective effect
1296 * in column and scatter charts. It is not used for 3D pie charts.
1297 *
1298 * @type {Number}
1299 * @default 100
1300 * @since 4.0
1301 * @product highcharts
1302 */
1303 viewDistance: 25,
1304
1305 /**
1306 * Set it to `"auto"` to automatically move the labels to the best
1307 * edge.
1308 *
1309 * @validvalue [null, "auto"]
1310 * @type {String}
1311 * @default null
1312 * @since 5.0.12
1313 * @product highcharts
1314 */
1315 axisLabelPosition: 'default',
1316
1317 /**
1318 * Provides the option to draw a frame around the charts by defining
1319 * a bottom, front and back panel.
1320 *
1321 * @since 4.0
1322 * @product highcharts
1323 */
1324 frame: {
1325
1326 /**
1327 * Whether the frames are visible.
1328 */
1329 visible: 'default',
1330
1331 /**
1332 * General pixel thickness for the frame faces.
1333 */
1334 size: 1,
1335
1336 /**
1337 * The bottom of the frame around a 3D chart.
1338 *
1339 * @since 4.0
1340 * @product highcharts
1341 */
1342 bottom: {
1343 /**
1344 * The color of the panel.
1345 *
1346 * @type {Color}
1347 * @default transparent
1348 * @since 4.0
1349 * @product highcharts
1350 * @apioption chart.options3d.frame.bottom.color
1351 */
1352
1353 /**
1354 * The thickness of the panel.
1355 *
1356 * @type {Number}
1357 * @default 1
1358 * @since 4.0
1359 * @product highcharts
1360 * @apioption chart.options3d.frame.bottom.size
1361 */
1362
1363 /**
1364 * Whether to display the frame. Possible values are `true`, `false`,
1365 * `"auto"` to display only the frames behind the data, and `"default"`
1366 * to display faces behind the data based on the axis layout, ignoring
1367 * the point of view.
1368 *
1369 * @validvalue ["default", "auto", true, false]
1370 * @type {Boolean|String}
1371 * @sample {highcharts} highcharts/3d/scatter-frame/ Auto frames
1372 * @default default
1373 * @since 5.0.12
1374 * @product highcharts
1375 * @apioption chart.options3d.frame.bottom.visible
1376 */
1377 },
1378
1379 /**
1380 * The top of the frame around a 3D chart.
1381 *
1382 * @extends {chart.options3d.frame.bottom}
1383 */
1384 top: {},
1385
1386 /**
1387 * The left side of the frame around a 3D chart.
1388 *
1389 * @extends {chart.options3d.frame.bottom}
1390 */
1391 left: {},
1392
1393 /**
1394 * The right of the frame around a 3D chart.
1395 *
1396 * @extends {chart.options3d.frame.bottom}
1397 */
1398 right: {},
1399
1400 /**
1401 * The back side of the frame around a 3D chart.
1402 *
1403 * @extends {chart.options3d.frame.bottom}
1404 */
1405 back: {},
1406
1407 /**
1408 * The front of the frame around a 3D chart.
1409 *
1410 * @extends {chart.options3d.frame.bottom}
1411 */
1412 front: {}
1413 }
1414 }
1415 }
1416 };
1417
1418 merge(true, defaultOptions, extendedOptions);
1419
1420
1421
1422 wrap(Chart.prototype, 'setClassName', function(proceed) {
1423 proceed.apply(this, [].slice.call(arguments, 1));
1424
1425 if (this.is3d()) {
1426 this.container.className += ' highcharts-3d-chart';
1427 }
1428 });
1429
1430 H.wrap(H.Chart.prototype, 'setChartSize', function(proceed) {
1431 var chart = this,
1432 options3d = chart.options.chart.options3d;
1433
1434 proceed.apply(chart, [].slice.call(arguments, 1));
1435
1436 if (chart.is3d()) {
1437 var inverted = chart.inverted,
1438 clipBox = chart.clipBox,
1439 margin = chart.margin,
1440 x = inverted ? 'y' : 'x',
1441 y = inverted ? 'x' : 'y',
1442 w = inverted ? 'height' : 'width',
1443 h = inverted ? 'width' : 'height';
1444
1445 clipBox[x] = -(margin[3] || 0);
1446 clipBox[y] = -(margin[0] || 0);
1447 clipBox[w] = chart.chartWidth + (margin[3] || 0) + (margin[1] || 0);
1448 clipBox[h] = chart.chartHeight + (margin[0] || 0) + (margin[2] || 0);
1449
1450 // Set scale, used later in perspective method():
1451 chart.scale3d = 1; // @notice getScale uses perspective, so scale3d has to be reset.
1452 if (options3d.fitToPlot === true) {
1453 chart.scale3d = getScale(chart, options3d.depth);
1454 }
1455 }
1456 });
1457
1458 wrap(Chart.prototype, 'redraw', function(proceed) {
1459 if (this.is3d()) {
1460 // Set to force a redraw of all elements
1461 this.isDirtyBox = true;
1462 this.frame3d = this.get3dFrame();
1463 }
1464 proceed.apply(this, [].slice.call(arguments, 1));
1465 });
1466
1467 wrap(Chart.prototype, 'render', function(proceed) {
1468 if (this.is3d()) {
1469 this.frame3d = this.get3dFrame();
1470 }
1471 proceed.apply(this, [].slice.call(arguments, 1));
1472 });
1473
1474 // Draw the series in the reverse order (#3803, #3917)
1475 wrap(Chart.prototype, 'renderSeries', function(proceed) {
1476 var series,
1477 i = this.series.length;
1478
1479 if (this.is3d()) {
1480 while (i--) {
1481 series = this.series[i];
1482 series.translate();
1483 series.render();
1484 }
1485 } else {
1486 proceed.call(this);
1487 }
1488 });
1489
1490 wrap(Chart.prototype, 'drawChartBox', function(proceed) {
1491 if (this.is3d()) {
1492 var chart = this,
1493 renderer = chart.renderer,
1494 options3d = this.options.chart.options3d,
1495 frame = chart.get3dFrame(),
1496 xm = this.plotLeft,
1497 xp = this.plotLeft + this.plotWidth,
1498 ym = this.plotTop,
1499 yp = this.plotTop + this.plotHeight,
1500 zm = 0,
1501 zp = options3d.depth,
1502 xmm = xm - (frame.left.visible ? frame.left.size : 0),
1503 xpp = xp + (frame.right.visible ? frame.right.size : 0),
1504 ymm = ym - (frame.top.visible ? frame.top.size : 0),
1505 ypp = yp + (frame.bottom.visible ? frame.bottom.size : 0),
1506 zmm = zm - (frame.front.visible ? frame.front.size : 0),
1507 zpp = zp + (frame.back.visible ? frame.back.size : 0),
1508 verb = chart.hasRendered ? 'animate' : 'attr';
1509
1510 this.frame3d = frame;
1511
1512 if (!this.frameShapes) {
1513 this.frameShapes = {
1514 bottom: renderer.polyhedron().add(),
1515 top: renderer.polyhedron().add(),
1516 left: renderer.polyhedron().add(),
1517 right: renderer.polyhedron().add(),
1518 back: renderer.polyhedron().add(),
1519 front: renderer.polyhedron().add()
1520 };
1521 }
1522
1523 this.frameShapes.bottom[verb]({
1524 'class': 'highcharts-3d-frame highcharts-3d-frame-bottom',
1525 zIndex: frame.bottom.frontFacing ? -1000 : 1000,
1526 faces: [{ // bottom
1527 fill: H.color(frame.bottom.color).brighten(0.1).get(),
1528 vertexes: [{
1529 x: xmm,
1530 y: ypp,
1531 z: zmm
1532 }, {
1533 x: xpp,
1534 y: ypp,
1535 z: zmm
1536 }, {
1537 x: xpp,
1538 y: ypp,
1539 z: zpp
1540 }, {
1541 x: xmm,
1542 y: ypp,
1543 z: zpp
1544 }],
1545 enabled: frame.bottom.visible
1546 },
1547 { // top
1548 fill: H.color(frame.bottom.color).brighten(0.1).get(),
1549 vertexes: [{
1550 x: xm,
1551 y: yp,
1552 z: zp
1553 }, {
1554 x: xp,
1555 y: yp,
1556 z: zp
1557 }, {
1558 x: xp,
1559 y: yp,
1560 z: zm
1561 }, {
1562 x: xm,
1563 y: yp,
1564 z: zm
1565 }],
1566 enabled: frame.bottom.visible
1567 },
1568 { // left
1569 fill: H.color(frame.bottom.color).brighten(-0.1).get(),
1570 vertexes: [{
1571 x: xmm,
1572 y: ypp,
1573 z: zmm
1574 }, {
1575 x: xmm,
1576 y: ypp,
1577 z: zpp
1578 }, {
1579 x: xm,
1580 y: yp,
1581 z: zp
1582 }, {
1583 x: xm,
1584 y: yp,
1585 z: zm
1586 }],
1587 enabled: frame.bottom.visible && !frame.left.visible
1588 },
1589 { // right
1590 fill: H.color(frame.bottom.color).brighten(-0.1).get(),
1591 vertexes: [{
1592 x: xpp,
1593 y: ypp,
1594 z: zpp
1595 }, {
1596 x: xpp,
1597 y: ypp,
1598 z: zmm
1599 }, {
1600 x: xp,
1601 y: yp,
1602 z: zm
1603 }, {
1604 x: xp,
1605 y: yp,
1606 z: zp
1607 }],
1608 enabled: frame.bottom.visible && !frame.right.visible
1609 },
1610 { // front
1611 fill: H.color(frame.bottom.color).get(),
1612 vertexes: [{
1613 x: xpp,
1614 y: ypp,
1615 z: zmm
1616 }, {
1617 x: xmm,
1618 y: ypp,
1619 z: zmm
1620 }, {
1621 x: xm,
1622 y: yp,
1623 z: zm
1624 }, {
1625 x: xp,
1626 y: yp,
1627 z: zm
1628 }],
1629 enabled: frame.bottom.visible && !frame.front.visible
1630 },
1631 { // back
1632 fill: H.color(frame.bottom.color).get(),
1633 vertexes: [{
1634 x: xmm,
1635 y: ypp,
1636 z: zpp
1637 }, {
1638 x: xpp,
1639 y: ypp,
1640 z: zpp
1641 }, {
1642 x: xp,
1643 y: yp,
1644 z: zp
1645 }, {
1646 x: xm,
1647 y: yp,
1648 z: zp
1649 }],
1650 enabled: frame.bottom.visible && !frame.back.visible
1651 }
1652 ]
1653 });
1654 this.frameShapes.top[verb]({
1655 'class': 'highcharts-3d-frame highcharts-3d-frame-top',
1656 zIndex: frame.top.frontFacing ? -1000 : 1000,
1657 faces: [{ // bottom
1658 fill: H.color(frame.top.color).brighten(0.1).get(),
1659 vertexes: [{
1660 x: xmm,
1661 y: ymm,
1662 z: zpp
1663 }, {
1664 x: xpp,
1665 y: ymm,
1666 z: zpp
1667 }, {
1668 x: xpp,
1669 y: ymm,
1670 z: zmm
1671 }, {
1672 x: xmm,
1673 y: ymm,
1674 z: zmm
1675 }],
1676 enabled: frame.top.visible
1677 },
1678 { // top
1679 fill: H.color(frame.top.color).brighten(0.1).get(),
1680 vertexes: [{
1681 x: xm,
1682 y: ym,
1683 z: zm
1684 }, {
1685 x: xp,
1686 y: ym,
1687 z: zm
1688 }, {
1689 x: xp,
1690 y: ym,
1691 z: zp
1692 }, {
1693 x: xm,
1694 y: ym,
1695 z: zp
1696 }],
1697 enabled: frame.top.visible
1698 },
1699 { // left
1700 fill: H.color(frame.top.color).brighten(-0.1).get(),
1701 vertexes: [{
1702 x: xmm,
1703 y: ymm,
1704 z: zpp
1705 }, {
1706 x: xmm,
1707 y: ymm,
1708 z: zmm
1709 }, {
1710 x: xm,
1711 y: ym,
1712 z: zm
1713 }, {
1714 x: xm,
1715 y: ym,
1716 z: zp
1717 }],
1718 enabled: frame.top.visible && !frame.left.visible
1719 },
1720 { // right
1721 fill: H.color(frame.top.color).brighten(-0.1).get(),
1722 vertexes: [{
1723 x: xpp,
1724 y: ymm,
1725 z: zmm
1726 }, {
1727 x: xpp,
1728 y: ymm,
1729 z: zpp
1730 }, {
1731 x: xp,
1732 y: ym,
1733 z: zp
1734 }, {
1735 x: xp,
1736 y: ym,
1737 z: zm
1738 }],
1739 enabled: frame.top.visible && !frame.right.visible
1740 },
1741 { // front
1742 fill: H.color(frame.top.color).get(),
1743 vertexes: [{
1744 x: xmm,
1745 y: ymm,
1746 z: zmm
1747 }, {
1748 x: xpp,
1749 y: ymm,
1750 z: zmm
1751 }, {
1752 x: xp,
1753 y: ym,
1754 z: zm
1755 }, {
1756 x: xm,
1757 y: ym,
1758 z: zm
1759 }],
1760 enabled: frame.top.visible && !frame.front.visible
1761 },
1762 { // back
1763 fill: H.color(frame.top.color).get(),
1764 vertexes: [{
1765 x: xpp,
1766 y: ymm,
1767 z: zpp
1768 }, {
1769 x: xmm,
1770 y: ymm,
1771 z: zpp
1772 }, {
1773 x: xm,
1774 y: ym,
1775 z: zp
1776 }, {
1777 x: xp,
1778 y: ym,
1779 z: zp
1780 }],
1781 enabled: frame.top.visible && !frame.back.visible
1782 }
1783 ]
1784 });
1785 this.frameShapes.left[verb]({
1786 'class': 'highcharts-3d-frame highcharts-3d-frame-left',
1787 zIndex: frame.left.frontFacing ? -1000 : 1000,
1788 faces: [{ // bottom
1789 fill: H.color(frame.left.color).brighten(0.1).get(),
1790 vertexes: [{
1791 x: xmm,
1792 y: ypp,
1793 z: zmm
1794 }, {
1795 x: xm,
1796 y: yp,
1797 z: zm
1798 }, {
1799 x: xm,
1800 y: yp,
1801 z: zp
1802 }, {
1803 x: xmm,
1804 y: ypp,
1805 z: zpp
1806 }],
1807 enabled: frame.left.visible && !frame.bottom.visible
1808 },
1809 { // top
1810 fill: H.color(frame.left.color).brighten(0.1).get(),
1811 vertexes: [{
1812 x: xmm,
1813 y: ymm,
1814 z: zpp
1815 }, {
1816 x: xm,
1817 y: ym,
1818 z: zp
1819 }, {
1820 x: xm,
1821 y: ym,
1822 z: zm
1823 }, {
1824 x: xmm,
1825 y: ymm,
1826 z: zmm
1827 }],
1828 enabled: frame.left.visible && !frame.top.visible
1829 },
1830 { // left
1831 fill: H.color(frame.left.color).brighten(-0.1).get(),
1832 vertexes: [{
1833 x: xmm,
1834 y: ypp,
1835 z: zpp
1836 }, {
1837 x: xmm,
1838 y: ymm,
1839 z: zpp
1840 }, {
1841 x: xmm,
1842 y: ymm,
1843 z: zmm
1844 }, {
1845 x: xmm,
1846 y: ypp,
1847 z: zmm
1848 }],
1849 enabled: frame.left.visible
1850 },
1851 { // right
1852 fill: H.color(frame.left.color).brighten(-0.1).get(),
1853 vertexes: [{
1854 x: xm,
1855 y: ym,
1856 z: zp
1857 }, {
1858 x: xm,
1859 y: yp,
1860 z: zp
1861 }, {
1862 x: xm,
1863 y: yp,
1864 z: zm
1865 }, {
1866 x: xm,
1867 y: ym,
1868 z: zm
1869 }],
1870 enabled: frame.left.visible
1871 },
1872 { // front
1873 fill: H.color(frame.left.color).get(),
1874 vertexes: [{
1875 x: xmm,
1876 y: ypp,
1877 z: zmm
1878 }, {
1879 x: xmm,
1880 y: ymm,
1881 z: zmm
1882 }, {
1883 x: xm,
1884 y: ym,
1885 z: zm
1886 }, {
1887 x: xm,
1888 y: yp,
1889 z: zm
1890 }],
1891 enabled: frame.left.visible && !frame.front.visible
1892 },
1893 { // back
1894 fill: H.color(frame.left.color).get(),
1895 vertexes: [{
1896 x: xmm,
1897 y: ymm,
1898 z: zpp
1899 }, {
1900 x: xmm,
1901 y: ypp,
1902 z: zpp
1903 }, {
1904 x: xm,
1905 y: yp,
1906 z: zp
1907 }, {
1908 x: xm,
1909 y: ym,
1910 z: zp
1911 }],
1912 enabled: frame.left.visible && !frame.back.visible
1913 }
1914 ]
1915 });
1916 this.frameShapes.right[verb]({
1917 'class': 'highcharts-3d-frame highcharts-3d-frame-right',
1918 zIndex: frame.right.frontFacing ? -1000 : 1000,
1919 faces: [{ // bottom
1920 fill: H.color(frame.right.color).brighten(0.1).get(),
1921 vertexes: [{
1922 x: xpp,
1923 y: ypp,
1924 z: zpp
1925 }, {
1926 x: xp,
1927 y: yp,
1928 z: zp
1929 }, {
1930 x: xp,
1931 y: yp,
1932 z: zm
1933 }, {
1934 x: xpp,
1935 y: ypp,
1936 z: zmm
1937 }],
1938 enabled: frame.right.visible && !frame.bottom.visible
1939 },
1940 { // top
1941 fill: H.color(frame.right.color).brighten(0.1).get(),
1942 vertexes: [{
1943 x: xpp,
1944 y: ymm,
1945 z: zmm
1946 }, {
1947 x: xp,
1948 y: ym,
1949 z: zm
1950 }, {
1951 x: xp,
1952 y: ym,
1953 z: zp
1954 }, {
1955 x: xpp,
1956 y: ymm,
1957 z: zpp
1958 }],
1959 enabled: frame.right.visible && !frame.top.visible
1960 },
1961 { // left
1962 fill: H.color(frame.right.color).brighten(-0.1).get(),
1963 vertexes: [{
1964 x: xp,
1965 y: ym,
1966 z: zm
1967 }, {
1968 x: xp,
1969 y: yp,
1970 z: zm
1971 }, {
1972 x: xp,
1973 y: yp,
1974 z: zp
1975 }, {
1976 x: xp,
1977 y: ym,
1978 z: zp
1979 }],
1980 enabled: frame.right.visible
1981 },
1982 { // right
1983 fill: H.color(frame.right.color).brighten(-0.1).get(),
1984 vertexes: [{
1985 x: xpp,
1986 y: ypp,
1987 z: zmm
1988 }, {
1989 x: xpp,
1990 y: ymm,
1991 z: zmm
1992 }, {
1993 x: xpp,
1994 y: ymm,
1995 z: zpp
1996 }, {
1997 x: xpp,
1998 y: ypp,
1999 z: zpp
2000 }],
2001 enabled: frame.right.visible
2002 },
2003 { // front
2004 fill: H.color(frame.right.color).get(),
2005 vertexes: [{
2006 x: xpp,
2007 y: ymm,
2008 z: zmm
2009 }, {
2010 x: xpp,
2011 y: ypp,
2012 z: zmm
2013 }, {
2014 x: xp,
2015 y: yp,
2016 z: zm
2017 }, {
2018 x: xp,
2019 y: ym,
2020 z: zm
2021 }],
2022 enabled: frame.right.visible && !frame.front.visible
2023 },
2024 { // back
2025 fill: H.color(frame.right.color).get(),
2026 vertexes: [{
2027 x: xpp,
2028 y: ypp,
2029 z: zpp
2030 }, {
2031 x: xpp,
2032 y: ymm,
2033 z: zpp
2034 }, {
2035 x: xp,
2036 y: ym,
2037 z: zp
2038 }, {
2039 x: xp,
2040 y: yp,
2041 z: zp
2042 }],
2043 enabled: frame.right.visible && !frame.back.visible
2044 }
2045 ]
2046 });
2047 this.frameShapes.back[verb]({
2048 'class': 'highcharts-3d-frame highcharts-3d-frame-back',
2049 zIndex: frame.back.frontFacing ? -1000 : 1000,
2050 faces: [{ // bottom
2051 fill: H.color(frame.back.color).brighten(0.1).get(),
2052 vertexes: [{
2053 x: xpp,
2054 y: ypp,
2055 z: zpp
2056 }, {
2057 x: xmm,
2058 y: ypp,
2059 z: zpp
2060 }, {
2061 x: xm,
2062 y: yp,
2063 z: zp
2064 }, {
2065 x: xp,
2066 y: yp,
2067 z: zp
2068 }],
2069 enabled: frame.back.visible && !frame.bottom.visible
2070 },
2071 { // top
2072 fill: H.color(frame.back.color).brighten(0.1).get(),
2073 vertexes: [{
2074 x: xmm,
2075 y: ymm,
2076 z: zpp
2077 }, {
2078 x: xpp,
2079 y: ymm,
2080 z: zpp
2081 }, {
2082 x: xp,
2083 y: ym,
2084 z: zp
2085 }, {
2086 x: xm,
2087 y: ym,
2088 z: zp
2089 }],
2090 enabled: frame.back.visible && !frame.top.visible
2091 },
2092 { // left
2093 fill: H.color(frame.back.color).brighten(-0.1).get(),
2094 vertexes: [{
2095 x: xmm,
2096 y: ypp,
2097 z: zpp
2098 }, {
2099 x: xmm,
2100 y: ymm,
2101 z: zpp
2102 }, {
2103 x: xm,
2104 y: ym,
2105 z: zp
2106 }, {
2107 x: xm,
2108 y: yp,
2109 z: zp
2110 }],
2111 enabled: frame.back.visible && !frame.left.visible
2112 },
2113 { // right
2114 fill: H.color(frame.back.color).brighten(-0.1).get(),
2115 vertexes: [{
2116 x: xpp,
2117 y: ymm,
2118 z: zpp
2119 }, {
2120 x: xpp,
2121 y: ypp,
2122 z: zpp
2123 }, {
2124 x: xp,
2125 y: yp,
2126 z: zp
2127 }, {
2128 x: xp,
2129 y: ym,
2130 z: zp
2131 }],
2132 enabled: frame.back.visible && !frame.right.visible
2133 },
2134 { // front
2135 fill: H.color(frame.back.color).get(),
2136 vertexes: [{
2137 x: xm,
2138 y: ym,
2139 z: zp
2140 }, {
2141 x: xp,
2142 y: ym,
2143 z: zp
2144 }, {
2145 x: xp,
2146 y: yp,
2147 z: zp
2148 }, {
2149 x: xm,
2150 y: yp,
2151 z: zp
2152 }],
2153 enabled: frame.back.visible
2154 },
2155 { // back
2156 fill: H.color(frame.back.color).get(),
2157 vertexes: [{
2158 x: xmm,
2159 y: ypp,
2160 z: zpp
2161 }, {
2162 x: xpp,
2163 y: ypp,
2164 z: zpp
2165 }, {
2166 x: xpp,
2167 y: ymm,
2168 z: zpp
2169 }, {
2170 x: xmm,
2171 y: ymm,
2172 z: zpp
2173 }],
2174 enabled: frame.back.visible
2175 }
2176 ]
2177 });
2178 this.frameShapes.front[verb]({
2179 'class': 'highcharts-3d-frame highcharts-3d-frame-front',
2180 zIndex: frame.front.frontFacing ? -1000 : 1000,
2181 faces: [{ // bottom
2182 fill: H.color(frame.front.color).brighten(0.1).get(),
2183 vertexes: [{
2184 x: xmm,
2185 y: ypp,
2186 z: zmm
2187 }, {
2188 x: xpp,
2189 y: ypp,
2190 z: zmm
2191 }, {
2192 x: xp,
2193 y: yp,
2194 z: zm
2195 }, {
2196 x: xm,
2197 y: yp,
2198 z: zm
2199 }],
2200 enabled: frame.front.visible && !frame.bottom.visible
2201 },
2202 { // top
2203 fill: H.color(frame.front.color).brighten(0.1).get(),
2204 vertexes: [{
2205 x: xpp,
2206 y: ymm,
2207 z: zmm
2208 }, {
2209 x: xmm,
2210 y: ymm,
2211 z: zmm
2212 }, {
2213 x: xm,
2214 y: ym,
2215 z: zm
2216 }, {
2217 x: xp,
2218 y: ym,
2219 z: zm
2220 }],
2221 enabled: frame.front.visible && !frame.top.visible
2222 },
2223 { // left
2224 fill: H.color(frame.front.color).brighten(-0.1).get(),
2225 vertexes: [{
2226 x: xmm,
2227 y: ymm,
2228 z: zmm
2229 }, {
2230 x: xmm,
2231 y: ypp,
2232 z: zmm
2233 }, {
2234 x: xm,
2235 y: yp,
2236 z: zm
2237 }, {
2238 x: xm,
2239 y: ym,
2240 z: zm
2241 }],
2242 enabled: frame.front.visible && !frame.left.visible
2243 },
2244 { // right
2245 fill: H.color(frame.front.color).brighten(-0.1).get(),
2246 vertexes: [{
2247 x: xpp,
2248 y: ypp,
2249 z: zmm
2250 }, {
2251 x: xpp,
2252 y: ymm,
2253 z: zmm
2254 }, {
2255 x: xp,
2256 y: ym,
2257 z: zm
2258 }, {
2259 x: xp,
2260 y: yp,
2261 z: zm
2262 }],
2263 enabled: frame.front.visible && !frame.right.visible
2264 },
2265 { // front
2266 fill: H.color(frame.front.color).get(),
2267 vertexes: [{
2268 x: xp,
2269 y: ym,
2270 z: zm
2271 }, {
2272 x: xm,
2273 y: ym,
2274 z: zm
2275 }, {
2276 x: xm,
2277 y: yp,
2278 z: zm
2279 }, {
2280 x: xp,
2281 y: yp,
2282 z: zm
2283 }],
2284 enabled: frame.front.visible
2285 },
2286 { // back
2287 fill: H.color(frame.front.color).get(),
2288 vertexes: [{
2289 x: xpp,
2290 y: ypp,
2291 z: zmm
2292 }, {
2293 x: xmm,
2294 y: ypp,
2295 z: zmm
2296 }, {
2297 x: xmm,
2298 y: ymm,
2299 z: zmm
2300 }, {
2301 x: xpp,
2302 y: ymm,
2303 z: zmm
2304 }],
2305 enabled: frame.front.visible
2306 }
2307 ]
2308 });
2309 }
2310
2311 return proceed.apply(this, [].slice.call(arguments, 1));
2312 });
2313
2314 Chart.prototype.retrieveStacks = function(stacking) {
2315 var series = this.series,
2316 stacks = {},
2317 stackNumber,
2318 i = 1;
2319
2320 each(this.series, function(s) {
2321 stackNumber = pick(s.options.stack, (stacking ? 0 : series.length - 1 - s.index)); // #3841, #4532
2322 if (!stacks[stackNumber]) {
2323 stacks[stackNumber] = {
2324 series: [s],
2325 position: i
2326 };
2327 i++;
2328 } else {
2329 stacks[stackNumber].series.push(s);
2330 }
2331 });
2332
2333 stacks.totalStacks = i + 1;
2334 return stacks;
2335 };
2336
2337 Chart.prototype.get3dFrame = function() {
2338 var chart = this,
2339 options3d = chart.options.chart.options3d,
2340 frameOptions = options3d.frame,
2341 xm = chart.plotLeft,
2342 xp = chart.plotLeft + chart.plotWidth,
2343 ym = chart.plotTop,
2344 yp = chart.plotTop + chart.plotHeight,
2345 zm = 0,
2346 zp = options3d.depth,
2347 faceOrientation = function(vertexes) {
2348 var area = H.shapeArea3d(vertexes, chart);
2349 // Give it 0.5 squared-pixel as a margin for rounding errors.
2350 if (area > 0.5) {
2351 return 1;
2352 }
2353 if (area < -0.5) {
2354 return -1;
2355 }
2356 return 0;
2357 },
2358 bottomOrientation = faceOrientation([{
2359 x: xm,
2360 y: yp,
2361 z: zp
2362 }, {
2363 x: xp,
2364 y: yp,
2365 z: zp
2366 }, {
2367 x: xp,
2368 y: yp,
2369 z: zm
2370 }, {
2371 x: xm,
2372 y: yp,
2373 z: zm
2374 }]),
2375 topOrientation = faceOrientation([{
2376 x: xm,
2377 y: ym,
2378 z: zm
2379 }, {
2380 x: xp,
2381 y: ym,
2382 z: zm
2383 }, {
2384 x: xp,
2385 y: ym,
2386 z: zp
2387 }, {
2388 x: xm,
2389 y: ym,
2390 z: zp
2391 }]),
2392 leftOrientation = faceOrientation([{
2393 x: xm,
2394 y: ym,
2395 z: zm
2396 }, {
2397 x: xm,
2398 y: ym,
2399 z: zp
2400 }, {
2401 x: xm,
2402 y: yp,
2403 z: zp
2404 }, {
2405 x: xm,
2406 y: yp,
2407 z: zm
2408 }]),
2409 rightOrientation = faceOrientation([{
2410 x: xp,
2411 y: ym,
2412 z: zp
2413 }, {
2414 x: xp,
2415 y: ym,
2416 z: zm
2417 }, {
2418 x: xp,
2419 y: yp,
2420 z: zm
2421 }, {
2422 x: xp,
2423 y: yp,
2424 z: zp
2425 }]),
2426 frontOrientation = faceOrientation([{
2427 x: xm,
2428 y: yp,
2429 z: zm
2430 }, {
2431 x: xp,
2432 y: yp,
2433 z: zm
2434 }, {
2435 x: xp,
2436 y: ym,
2437 z: zm
2438 }, {
2439 x: xm,
2440 y: ym,
2441 z: zm
2442 }]),
2443 backOrientation = faceOrientation([{
2444 x: xm,
2445 y: ym,
2446 z: zp
2447 }, {
2448 x: xp,
2449 y: ym,
2450 z: zp
2451 }, {
2452 x: xp,
2453 y: yp,
2454 z: zp
2455 }, {
2456 x: xm,
2457 y: yp,
2458 z: zp
2459 }]),
2460 defaultShowBottom = false,
2461 defaultShowTop = false,
2462 defaultShowLeft = false,
2463 defaultShowRight = false,
2464 defaultShowFront = false,
2465 defaultShowBack = true;
2466
2467 // The 'default' criteria to visible faces of the frame is looking up every
2468 // axis to decide whenever the left/right//top/bottom sides of the frame
2469 // will be shown
2470 each([].concat(chart.xAxis, chart.yAxis, chart.zAxis), function(axis) {
2471 if (axis) {
2472 if (axis.horiz) {
2473 if (axis.opposite) {
2474 defaultShowTop = true;
2475 } else {
2476 defaultShowBottom = true;
2477 }
2478 } else {
2479 if (axis.opposite) {
2480 defaultShowRight = true;
2481 } else {
2482 defaultShowLeft = true;
2483 }
2484 }
2485 }
2486 });
2487
2488 var getFaceOptions = function(sources, faceOrientation, defaultVisible) {
2489 var faceAttrs = ['size', 'color', 'visible'];
2490 var options = {};
2491 for (var i = 0; i < faceAttrs.length; i++) {
2492 var attr = faceAttrs[i];
2493 for (var j = 0; j < sources.length; j++) {
2494 if (typeof sources[j] === 'object') {
2495 var val = sources[j][attr];
2496 if (val !== undefined && val !== null) {
2497 options[attr] = val;
2498 break;
2499 }
2500 }
2501 }
2502 }
2503 var isVisible = defaultVisible;
2504 if (options.visible === true || options.visible === false) {
2505 isVisible = options.visible;
2506 } else if (options.visible === 'auto') {
2507 isVisible = faceOrientation > 0;
2508 }
2509
2510 return {
2511 size: pick(options.size, 1),
2512 color: pick(options.color, 'none'),
2513 frontFacing: faceOrientation > 0,
2514 visible: isVisible
2515 };
2516 };
2517
2518 // docs @TODO: Add all frame options (left, right, top, bottom, front, back) to
2519 // apioptions JSDoc once the new system is up.
2520 var ret = {
2521 // FIXME: Previously, left/right, top/bottom and front/back pairs shared
2522 // size and color.
2523 // For compatibility and consistency sake, when one face have
2524 // size/color/visibility set, the opposite face will default to the same
2525 // values. Also, left/right used to be called 'side', so that's also
2526 // added as a fallback
2527 bottom: getFaceOptions(
2528 [frameOptions.bottom, frameOptions.top, frameOptions],
2529 bottomOrientation,
2530 defaultShowBottom
2531 ),
2532 top: getFaceOptions(
2533 [frameOptions.top, frameOptions.bottom, frameOptions],
2534 topOrientation,
2535 defaultShowTop
2536 ),
2537 left: getFaceOptions(
2538 [
2539 frameOptions.left,
2540 frameOptions.right,
2541 frameOptions.side,
2542 frameOptions
2543 ],
2544 leftOrientation,
2545 defaultShowLeft
2546 ),
2547 right: getFaceOptions(
2548 [
2549 frameOptions.right,
2550 frameOptions.left,
2551 frameOptions.side,
2552 frameOptions
2553 ],
2554 rightOrientation,
2555 defaultShowRight
2556 ),
2557 back: getFaceOptions(
2558 [frameOptions.back, frameOptions.front, frameOptions],
2559 backOrientation,
2560 defaultShowBack
2561 ),
2562 front: getFaceOptions(
2563 [frameOptions.front, frameOptions.back, frameOptions],
2564 frontOrientation,
2565 defaultShowFront
2566 )
2567 };
2568
2569
2570 // Decide the bast place to put axis title/labels based on the visible faces.
2571 // Ideally, The labels can only be on the edge between a visible face and an invisble one.
2572 // Also, the Y label should be one the left-most edge (right-most if opposite),
2573 if (options3d.axisLabelPosition === 'auto') {
2574 var isValidEdge = function(face1, face2) {
2575 return (face1.visible !== face2.visible) ||
2576 (face1.visible && face2.visible && (face1.frontFacing !== face2.frontFacing));
2577 };
2578
2579 var yEdges = [];
2580 if (isValidEdge(ret.left, ret.front)) {
2581 yEdges.push({
2582 y: (ym + yp) / 2,
2583 x: xm,
2584 z: zm,
2585 xDir: {
2586 x: 1,
2587 y: 0,
2588 z: 0
2589 }
2590 });
2591 }
2592 if (isValidEdge(ret.left, ret.back)) {
2593 yEdges.push({
2594 y: (ym + yp) / 2,
2595 x: xm,
2596 z: zp,
2597 xDir: {
2598 x: 0,
2599 y: 0,
2600 z: -1
2601 }
2602 });
2603 }
2604 if (isValidEdge(ret.right, ret.front)) {
2605 yEdges.push({
2606 y: (ym + yp) / 2,
2607 x: xp,
2608 z: zm,
2609 xDir: {
2610 x: 0,
2611 y: 0,
2612 z: 1
2613 }
2614 });
2615 }
2616 if (isValidEdge(ret.right, ret.back)) {
2617 yEdges.push({
2618 y: (ym + yp) / 2,
2619 x: xp,
2620 z: zp,
2621 xDir: {
2622 x: -1,
2623 y: 0,
2624 z: 0
2625 }
2626 });
2627 }
2628
2629 var xBottomEdges = [];
2630 if (isValidEdge(ret.bottom, ret.front)) {
2631 xBottomEdges.push({
2632 x: (xm + xp) / 2,
2633 y: yp,
2634 z: zm,
2635 xDir: {
2636 x: 1,
2637 y: 0,
2638 z: 0
2639 }
2640 });
2641 }
2642 if (isValidEdge(ret.bottom, ret.back)) {
2643 xBottomEdges.push({
2644 x: (xm + xp) / 2,
2645 y: yp,
2646 z: zp,
2647 xDir: {
2648 x: -1,
2649 y: 0,
2650 z: 0
2651 }
2652 });
2653 }
2654
2655 var xTopEdges = [];
2656 if (isValidEdge(ret.top, ret.front)) {
2657 xTopEdges.push({
2658 x: (xm + xp) / 2,
2659 y: ym,
2660 z: zm,
2661 xDir: {
2662 x: 1,
2663 y: 0,
2664 z: 0
2665 }
2666 });
2667 }
2668 if (isValidEdge(ret.top, ret.back)) {
2669 xTopEdges.push({
2670 x: (xm + xp) / 2,
2671 y: ym,
2672 z: zp,
2673 xDir: {
2674 x: -1,
2675 y: 0,
2676 z: 0
2677 }
2678 });
2679 }
2680
2681 var zBottomEdges = [];
2682 if (isValidEdge(ret.bottom, ret.left)) {
2683 zBottomEdges.push({
2684 z: (zm + zp) / 2,
2685 y: yp,
2686 x: xm,
2687 xDir: {
2688 x: 0,
2689 y: 0,
2690 z: -1
2691 }
2692 });
2693 }
2694 if (isValidEdge(ret.bottom, ret.right)) {
2695 zBottomEdges.push({
2696 z: (zm + zp) / 2,
2697 y: yp,
2698 x: xp,
2699 xDir: {
2700 x: 0,
2701 y: 0,
2702 z: 1
2703 }
2704 });
2705 }
2706
2707 var zTopEdges = [];
2708 if (isValidEdge(ret.top, ret.left)) {
2709 zTopEdges.push({
2710 z: (zm + zp) / 2,
2711 y: ym,
2712 x: xm,
2713 xDir: {
2714 x: 0,
2715 y: 0,
2716 z: -1
2717 }
2718 });
2719 }
2720 if (isValidEdge(ret.top, ret.right)) {
2721 zTopEdges.push({
2722 z: (zm + zp) / 2,
2723 y: ym,
2724 x: xp,
2725 xDir: {
2726 x: 0,
2727 y: 0,
2728 z: 1
2729 }
2730 });
2731 }
2732
2733 var pickEdge = function(edges, axis, mult) {
2734 if (edges.length === 0) {
2735 return null;
2736 } else if (edges.length === 1) {
2737 return edges[0];
2738 }
2739 var best = 0,
2740 projections = perspective(edges, chart, false);
2741 for (var i = 1; i < projections.length; i++) {
2742 if (mult * projections[i][axis] > mult * projections[best][axis]) {
2743 best = i;
2744 } else if ((mult * projections[i][axis] === mult * projections[best][axis]) && (projections[i].z < projections[best].z)) {
2745 best = i;
2746 }
2747 }
2748 return edges[best];
2749 };
2750 ret.axes = {
2751 y: {
2752 'left': pickEdge(yEdges, 'x', -1),
2753 'right': pickEdge(yEdges, 'x', +1)
2754 },
2755 x: {
2756 'top': pickEdge(xTopEdges, 'y', -1),
2757 'bottom': pickEdge(xBottomEdges, 'y', +1)
2758 },
2759 z: {
2760 'top': pickEdge(zTopEdges, 'y', -1),
2761 'bottom': pickEdge(zBottomEdges, 'y', +1)
2762 }
2763 };
2764 } else {
2765 ret.axes = {
2766 y: {
2767 'left': {
2768 x: xm,
2769 z: zm,
2770 xDir: {
2771 x: 1,
2772 y: 0,
2773 z: 0
2774 }
2775 },
2776 'right': {
2777 x: xp,
2778 z: zm,
2779 xDir: {
2780 x: 0,
2781 y: 0,
2782 z: 1
2783 }
2784 }
2785 },
2786 x: {
2787 'top': {
2788 y: ym,
2789 z: zm,
2790 xDir: {
2791 x: 1,
2792 y: 0,
2793 z: 0
2794 }
2795 },
2796 'bottom': {
2797 y: yp,
2798 z: zm,
2799 xDir: {
2800 x: 1,
2801 y: 0,
2802 z: 0
2803 }
2804 }
2805 },
2806 z: {
2807 'top': {
2808 x: defaultShowLeft ? xp : xm,
2809 y: ym,
2810 xDir: defaultShowLeft ? {
2811 x: 0,
2812 y: 0,
2813 z: 1
2814 } : {
2815 x: 0,
2816 y: 0,
2817 z: -1
2818 }
2819 },
2820 'bottom': {
2821 x: defaultShowLeft ? xp : xm,
2822 y: yp,
2823 xDir: defaultShowLeft ? {
2824 x: 0,
2825 y: 0,
2826 z: 1
2827 } : {
2828 x: 0,
2829 y: 0,
2830 z: -1
2831 }
2832 }
2833 }
2834 };
2835 }
2836
2837 return ret;
2838 };
2839
2840 /**
2841 * Animation setter for matrix property.
2842 */
2843 H.Fx.prototype.matrixSetter = function() {
2844 var interpolated;
2845 if (this.pos < 1 &&
2846 (H.isArray(this.start) || H.isArray(this.end))) {
2847 var start = this.start || [1, 0, 0, 1, 0, 0];
2848 var end = this.end || [1, 0, 0, 1, 0, 0];
2849 interpolated = [];
2850 for (var i = 0; i < 6; i++) {
2851 interpolated.push(this.pos * end[i] + (1 - this.pos) * start[i]);
2852 }
2853 } else {
2854 interpolated = this.end;
2855 }
2856
2857 this.elem.attr(
2858 this.prop,
2859 interpolated,
2860 null,
2861 true
2862 );
2863 };
2864
2865 /**
2866 * Note: As of v5.0.12, `frame.left` or `frame.right` should be used
2867 * instead.
2868 *
2869 * The side for the frame around a 3D chart.
2870 *
2871 * @since 4.0
2872 * @product highcharts
2873 * @apioption chart.options3d.frame.side
2874 */
2875
2876 /**
2877 * The color of the panel.
2878 *
2879 * @type {Color}
2880 * @default transparent
2881 * @since 4.0
2882 * @product highcharts
2883 * @apioption chart.options3d.frame.side.color
2884 */
2885
2886 /**
2887 * The thickness of the panel.
2888 *
2889 * @type {Number}
2890 * @default 1
2891 * @since 4.0
2892 * @product highcharts
2893 * @apioption chart.options3d.frame.side.size
2894 */
2895
2896
2897 }(Highcharts));
2898 (function(H) {
2899 /**
2900 * (c) 2010-2017 Torstein Honsi
2901 *
2902 * Extenstion for 3d axes
2903 *
2904 * License: www.highcharts.com/license
2905 */
2906 var ZAxis,
2907
2908 Axis = H.Axis,
2909 Chart = H.Chart,
2910 deg2rad = H.deg2rad,
2911 each = H.each,
2912 extend = H.extend,
2913 merge = H.merge,
2914 perspective = H.perspective,
2915 pick = H.pick,
2916 shapeArea = H.shapeArea,
2917 splat = H.splat,
2918 Tick = H.Tick,
2919 wrap = H.wrap;
2920
2921 /**
2922 * @optionparent xAxis
2923 */
2924 var extendedOptions = {
2925 labels: {
2926 /**
2927 * Defines how the labels are be repositioned according to the 3D chart
2928 * orientation.
2929 * - `'offset'`: Maintain a fixed horizontal/vertical distance from the
2930 * tick marks, despite the chart orientation. This is the backwards
2931 * compatible behavior, and causes skewing of X and Z axes.
2932 * - `'chart'`: Preserve 3D position relative to the chart.
2933 * This looks nice, but hard to read if the text isn't
2934 * forward-facing.
2935 * - `'flap'`: Rotated text along the axis to compensate for the chart
2936 * orientation. This tries to maintain text as legible as possible on
2937 * all orientations.
2938 * - `'ortho'`: Rotated text along the axis direction so that the labels
2939 * are orthogonal to the axis. This is very similar to `'flap'`, but
2940 * prevents skewing the labels (X and Y scaling are still present).
2941 *
2942 * @validvalue ['offset', 'chart', 'flap', 'ortho']
2943 * @sample highcharts/3d/skewed-labels/ Skewed labels
2944 * @since 5.0.15
2945 * @product highcharts
2946 */
2947 position3d: 'offset',
2948
2949 /**
2950 * If enabled, the axis labels will skewed to follow the perspective.
2951 *
2952 * This will fix overlapping labels and titles, but texts become less
2953 * legible due to the distortion.
2954 *
2955 * The final appearance depends heavily on `labels.position3d`.
2956 *
2957 * @since 5.0.15
2958 * @sample highcharts/3d/skewed-labels/ Skewed labels
2959 * @product highcharts
2960 */
2961 skew3d: false
2962 },
2963 title: {
2964 /**
2965 * Defines how the title is repositioned according to the 3D chart
2966 * orientation.
2967 * - `'offset'`: Maintain a fixed horizontal/vertical distance from the
2968 * tick marks, despite the chart orientation. This is the backwards
2969 * compatible behavior, and causes skewing of X and Z axes.
2970 * - `'chart'`: Preserve 3D position relative to the chart.
2971 * This looks nice, but hard to read if the text isn't
2972 * forward-facing.
2973 * - `'flap'`: Rotated text along the axis to compensate for the chart
2974 * orientation. This tries to maintain text as legible as possible on
2975 * all orientations.
2976 * - `'ortho'`: Rotated text along the axis direction so that the labels
2977 * are orthogonal to the axis. This is very similar to `'flap'`, but
2978 * prevents skewing the labels (X and Y scaling are still present).
2979 * - `null`: Will use the config from `labels.position3d`
2980 *
2981 * @validvalue ['offset', 'chart', 'flap', 'ortho', null]
2982 * @type {String}
2983 * @since 5.0.15
2984 * @sample highcharts/3d/skewed-labels/ Skewed labels
2985 * @product highcharts
2986 */
2987 position3d: null,
2988
2989 /**
2990 * If enabled, the axis title will skewed to follow the perspective.
2991 *
2992 * This will fix overlapping labels and titles, but texts become less
2993 * legible due to the distortion.
2994 *
2995 * The final appearance depends heavily on `title.position3d`.
2996 *
2997 * A `null` value will use the config from `labels.skew3d`.
2998 *
2999 * @validvalue [false, true, null]
3000 * @type {Boolean}
3001 * @sample highcharts/3d/skewed-labels/ Skewed labels
3002 * @since 5.0.15
3003 * @product highcharts
3004 */
3005 skew3d: null
3006 }
3007 };
3008
3009 merge(true, Axis.prototype.defaultOptions, extendedOptions);
3010
3011
3012 wrap(Axis.prototype, 'setOptions', function(proceed, userOptions) {
3013 var options;
3014 proceed.call(this, userOptions);
3015 if (this.chart.is3d && this.chart.is3d() && this.coll !== 'colorAxis') {
3016 options = this.options;
3017 options.tickWidth = pick(options.tickWidth, 0);
3018 options.gridLineWidth = pick(options.gridLineWidth, 1);
3019 }
3020 });
3021
3022 wrap(Axis.prototype, 'getPlotLinePath', function(proceed) {
3023 var path = proceed.apply(this, [].slice.call(arguments, 1));
3024
3025 // Do not do this if the chart is not 3D
3026 if (!this.chart.is3d() || this.coll === 'colorAxis') {
3027 return path;
3028 }
3029
3030 if (path === null) {
3031 return path;
3032 }
3033
3034 var chart = this.chart,
3035 options3d = chart.options.chart.options3d,
3036 d = this.isZAxis ? chart.plotWidth : options3d.depth,
3037 frame = chart.frame3d;
3038
3039 var pArr = [
3040 this.swapZ({
3041 x: path[1],
3042 y: path[2],
3043 z: 0
3044 }),
3045 this.swapZ({
3046 x: path[1],
3047 y: path[2],
3048 z: d
3049 }),
3050 this.swapZ({
3051 x: path[4],
3052 y: path[5],
3053 z: 0
3054 }),
3055 this.swapZ({
3056 x: path[4],
3057 y: path[5],
3058 z: d
3059 })
3060 ];
3061
3062 var pathSegments = [];
3063 if (!this.horiz) { // Y-Axis
3064 if (frame.front.visible) {
3065 pathSegments.push(pArr[0], pArr[2]);
3066 }
3067 if (frame.back.visible) {
3068 pathSegments.push(pArr[1], pArr[3]);
3069 }
3070 if (frame.left.visible) {
3071 pathSegments.push(pArr[0], pArr[1]);
3072 }
3073 if (frame.right.visible) {
3074 pathSegments.push(pArr[2], pArr[3]);
3075 }
3076 } else if (this.isZAxis) { // Z-Axis
3077 if (frame.left.visible) {
3078 pathSegments.push(pArr[0], pArr[2]);
3079 }
3080 if (frame.right.visible) {
3081 pathSegments.push(pArr[1], pArr[3]);
3082 }
3083 if (frame.top.visible) {
3084 pathSegments.push(pArr[0], pArr[1]);
3085 }
3086 if (frame.bottom.visible) {
3087 pathSegments.push(pArr[2], pArr[3]);
3088 }
3089 } else { // X-Axis
3090 if (frame.front.visible) {
3091 pathSegments.push(pArr[0], pArr[2]);
3092 }
3093 if (frame.back.visible) {
3094 pathSegments.push(pArr[1], pArr[3]);
3095 }
3096 if (frame.top.visible) {
3097 pathSegments.push(pArr[0], pArr[1]);
3098 }
3099 if (frame.bottom.visible) {
3100 pathSegments.push(pArr[2], pArr[3]);
3101 }
3102 }
3103
3104 pathSegments = perspective(pathSegments, this.chart, false);
3105
3106 return this.chart.renderer.toLineSegments(pathSegments);
3107 });
3108
3109 // Do not draw axislines in 3D
3110 wrap(Axis.prototype, 'getLinePath', function(proceed) {
3111 // Do not do this if the chart is not 3D
3112 if (!this.chart.is3d() || this.coll === 'colorAxis') {
3113 return proceed.apply(this, [].slice.call(arguments, 1));
3114 }
3115
3116 return [];
3117 });
3118
3119 wrap(Axis.prototype, 'getPlotBandPath', function(proceed) {
3120 // Do not do this if the chart is not 3D
3121 if (!this.chart.is3d() || this.coll === 'colorAxis') {
3122 return proceed.apply(this, [].slice.call(arguments, 1));
3123 }
3124
3125 var args = arguments,
3126 from = args[1],
3127 to = args[2],
3128 path = [],
3129 fromPath = this.getPlotLinePath(from),
3130 toPath = this.getPlotLinePath(to);
3131
3132 if (fromPath && toPath) {
3133 for (var i = 0; i < fromPath.length; i += 6) {
3134 path.push(
3135 'M', fromPath[i + 1], fromPath[i + 2],
3136 'L', fromPath[i + 4], fromPath[i + 5],
3137 'L', toPath[i + 4], toPath[i + 5],
3138 'L', toPath[i + 1], toPath[i + 2],
3139 'Z');
3140 }
3141 }
3142
3143 return path;
3144 });
3145
3146
3147 function fix3dPosition(axis, pos, isTitle) {
3148 // Do not do this if the chart is not 3D
3149 if (!axis.chart.is3d() || axis.coll === 'colorAxis') {
3150 return pos;
3151 }
3152
3153 var chart = axis.chart,
3154 alpha = deg2rad * chart.options.chart.options3d.alpha,
3155 beta = deg2rad * chart.options.chart.options3d.beta,
3156 positionMode = pick(
3157 isTitle && axis.options.title.position3d,
3158 axis.options.labels.position3d
3159 ),
3160 skew = pick(
3161 isTitle && axis.options.title.skew3d,
3162 axis.options.labels.skew3d
3163 ),
3164 frame = chart.frame3d,
3165 plotLeft = chart.plotLeft,
3166 plotRight = chart.plotWidth + plotLeft,
3167 plotTop = chart.plotTop,
3168 plotBottom = chart.plotHeight + plotTop,
3169 // Indicates we are labelling an X or Z axis on the "back" of the chart
3170 reverseFlap = false,
3171 offsetX = 0,
3172 offsetY = 0,
3173 vecX,
3174 vecY = {
3175 x: 0,
3176 y: 1,
3177 z: 0
3178 };
3179
3180 pos = axis.swapZ({
3181 x: pos.x,
3182 y: pos.y,
3183 z: 0
3184 });
3185
3186
3187 if (axis.isZAxis) { // Z Axis
3188 if (axis.opposite) {
3189 if (frame.axes.z.top === null) {
3190 return {};
3191 }
3192 offsetY = pos.y - plotTop;
3193 pos.x = frame.axes.z.top.x;
3194 pos.y = frame.axes.z.top.y;
3195 vecX = frame.axes.z.top.xDir;
3196 reverseFlap = !frame.top.frontFacing;
3197 } else {
3198 if (frame.axes.z.bottom === null) {
3199 return {};
3200 }
3201 offsetY = pos.y - plotBottom;
3202 pos.x = frame.axes.z.bottom.x;
3203 pos.y = frame.axes.z.bottom.y;
3204 vecX = frame.axes.z.bottom.xDir;
3205 reverseFlap = !frame.bottom.frontFacing;
3206 }
3207 } else if (axis.horiz) { // X Axis
3208 if (axis.opposite) {
3209 if (frame.axes.x.top === null) {
3210 return {};
3211 }
3212 offsetY = pos.y - plotTop;
3213 pos.y = frame.axes.x.top.y;
3214 pos.z = frame.axes.x.top.z;
3215 vecX = frame.axes.x.top.xDir;
3216 reverseFlap = !frame.top.frontFacing;
3217 } else {
3218 if (frame.axes.x.bottom === null) {
3219 return {};
3220 }
3221 offsetY = pos.y - plotBottom;
3222 pos.y = frame.axes.x.bottom.y;
3223 pos.z = frame.axes.x.bottom.z;
3224 vecX = frame.axes.x.bottom.xDir;
3225 reverseFlap = !frame.bottom.frontFacing;
3226 }
3227 } else { // Y Axis
3228 if (axis.opposite) {
3229 if (frame.axes.y.right === null) {
3230 return {};
3231 }
3232 offsetX = pos.x - plotRight;
3233 pos.x = frame.axes.y.right.x;
3234 pos.z = frame.axes.y.right.z;
3235 vecX = frame.axes.y.right.xDir;
3236 // Rotate 90º on opposite edge
3237 vecX = {
3238 x: vecX.z,
3239 y: vecX.y,
3240 z: -vecX.x
3241 };
3242 } else {
3243 if (frame.axes.y.left === null) {
3244 return {};
3245 }
3246 offsetX = pos.x - plotLeft;
3247 pos.x = frame.axes.y.left.x;
3248 pos.z = frame.axes.y.left.z;
3249 vecX = frame.axes.y.left.xDir;
3250 }
3251 }
3252
3253 if (positionMode === 'chart') {
3254 // Labels preserve their direction relative to the chart
3255 // nothing to do
3256
3257 } else if (positionMode === 'flap') {
3258 // Labels are be rotated around the axis direction to face the screen
3259 if (!axis.horiz) { // Y Axis
3260 vecX = {
3261 x: Math.cos(beta),
3262 y: 0,
3263 z: Math.sin(beta)
3264 };
3265 } else { // X and Z Axis
3266 var sin = Math.sin(alpha);
3267 var cos = Math.cos(alpha);
3268 if (axis.opposite) {
3269 sin = -sin;
3270 }
3271 if (reverseFlap) {
3272 sin = -sin;
3273 }
3274 vecY = {
3275 x: vecX.z * sin,
3276 y: cos,
3277 z: -vecX.x * sin
3278 };
3279 }
3280 } else if (positionMode === 'ortho') {
3281 // Labels will be rotated to be ortogonal to the axis
3282 if (!axis.horiz) { // Y Axis
3283 vecX = {
3284 x: Math.cos(beta),
3285 y: 0,
3286 z: Math.sin(beta)
3287 };
3288 } else { // X and Z Axis
3289 var sina = Math.sin(alpha);
3290 var cosa = Math.cos(alpha);
3291 var sinb = Math.sin(beta);
3292 var cosb = Math.cos(beta);
3293 var vecZ = {
3294 x: sinb * cosa,
3295 y: -sina,
3296 z: -cosa * cosb
3297 };
3298 vecY = {
3299 x: vecX.y * vecZ.z - vecX.z * vecZ.y,
3300 y: vecX.z * vecZ.x - vecX.x * vecZ.z,
3301 z: vecX.x * vecZ.y - vecX.y * vecZ.x
3302 };
3303 var scale = 1 / Math.sqrt(
3304 vecY.x * vecY.x + vecY.y * vecY.y + vecY.z * vecY.z
3305 );
3306 if (reverseFlap) {
3307 scale = -scale;
3308 }
3309 vecY = {
3310 x: scale * vecY.x,
3311 y: scale * vecY.y,
3312 z: scale * vecY.z
3313 };
3314 }
3315 } else { // positionMode == 'offset'
3316 // Labels will be skewd to maintain vertical / horizontal offsets from
3317 // axis
3318 if (!axis.horiz) { // Y Axis
3319 vecX = {
3320 x: Math.cos(beta),
3321 y: 0,
3322 z: Math.sin(beta)
3323 };
3324 } else { // X and Z Axis
3325 vecY = {
3326 x: Math.sin(beta) * Math.sin(alpha),
3327 y: Math.cos(alpha),
3328 z: -Math.cos(beta) * Math.sin(alpha)
3329 };
3330 }
3331 }
3332 pos.x += offsetX * vecX.x + offsetY * vecY.x;
3333 pos.y += offsetX * vecX.y + offsetY * vecY.y;
3334 pos.z += offsetX * vecX.z + offsetY * vecY.z;
3335
3336 var projected = perspective([pos], axis.chart)[0];
3337
3338 if (skew) {
3339 // Check if the label text would be mirrored
3340 var isMirrored = shapeArea(perspective([
3341 pos,
3342 {
3343 x: pos.x + vecX.x,
3344 y: pos.y + vecX.y,
3345 z: pos.z + vecX.z
3346 },
3347 {
3348 x: pos.x + vecY.x,
3349 y: pos.y + vecY.y,
3350 z: pos.z + vecY.z
3351 }
3352 ], axis.chart)) < 0;
3353 if (isMirrored) {
3354 vecX = {
3355 x: -vecX.x,
3356 y: -vecX.y,
3357 z: -vecX.z
3358 };
3359 }
3360
3361 var pointsProjected = perspective([{
3362 x: pos.x,
3363 y: pos.y,
3364 z: pos.z
3365 },
3366 {
3367 x: pos.x + vecX.x,
3368 y: pos.y + vecX.y,
3369 z: pos.z + vecX.z
3370 },
3371 {
3372 x: pos.x + vecY.x,
3373 y: pos.y + vecY.y,
3374 z: pos.z + vecY.z
3375 }
3376 ], axis.chart);
3377
3378 projected.matrix = [
3379 pointsProjected[1].x - pointsProjected[0].x,
3380 pointsProjected[1].y - pointsProjected[0].y,
3381 pointsProjected[2].x - pointsProjected[0].x,
3382 pointsProjected[2].y - pointsProjected[0].y,
3383 projected.x,
3384 projected.y
3385 ];
3386 projected.matrix[4] -= projected.x * projected.matrix[0] +
3387 projected.y * projected.matrix[2];
3388 projected.matrix[5] -= projected.x * projected.matrix[1] +
3389 projected.y * projected.matrix[3];
3390 } else {
3391 projected.matrix = null;
3392 }
3393
3394 return projected;
3395 }
3396
3397 /*
3398 Tick extensions
3399 */
3400 wrap(Tick.prototype, 'getMarkPath', function(proceed) {
3401 var path = proceed.apply(this, [].slice.call(arguments, 1));
3402
3403 var pArr = [
3404 fix3dPosition(this.axis, {
3405 x: path[1],
3406 y: path[2],
3407 z: 0
3408 }),
3409 fix3dPosition(this.axis, {
3410 x: path[4],
3411 y: path[5],
3412 z: 0
3413 })
3414 ];
3415
3416 return this.axis.chart.renderer.toLineSegments(pArr);
3417 });
3418
3419 wrap(Tick.prototype, 'getLabelPosition', function(proceed) {
3420 var pos = proceed.apply(this, [].slice.call(arguments, 1));
3421 return fix3dPosition(this.axis, pos);
3422 });
3423
3424 wrap(Axis.prototype, 'getTitlePosition', function(proceed) {
3425 var pos = proceed.apply(this, [].slice.call(arguments, 1));
3426 return fix3dPosition(this, pos, true);
3427 });
3428
3429 wrap(Axis.prototype, 'drawCrosshair', function(proceed) {
3430 var args = arguments;
3431 if (this.chart.is3d() && this.coll !== 'colorAxis') {
3432 if (args[2]) {
3433 args[2] = {
3434 plotX: args[2].plotXold || args[2].plotX,
3435 plotY: args[2].plotYold || args[2].plotY
3436 };
3437 }
3438 }
3439 proceed.apply(this, [].slice.call(args, 1));
3440 });
3441
3442 wrap(Axis.prototype, 'destroy', function(proceed) {
3443 each(['backFrame', 'bottomFrame', 'sideFrame'], function(prop) {
3444 if (this[prop]) {
3445 this[prop] = this[prop].destroy();
3446 }
3447 }, this);
3448 proceed.apply(this, [].slice.call(arguments, 1));
3449 });
3450
3451 /*
3452 Z-AXIS
3453 */
3454
3455 Axis.prototype.swapZ = function(p, insidePlotArea) {
3456 if (this.isZAxis) {
3457 var plotLeft = insidePlotArea ? 0 : this.chart.plotLeft;
3458 return {
3459 x: plotLeft + p.z,
3460 y: p.y,
3461 z: p.x - plotLeft
3462 };
3463 }
3464 return p;
3465 };
3466
3467 ZAxis = H.ZAxis = function() {
3468 this.init.apply(this, arguments);
3469 };
3470 extend(ZAxis.prototype, Axis.prototype);
3471 extend(ZAxis.prototype, {
3472 isZAxis: true,
3473 setOptions: function(userOptions) {
3474 userOptions = merge({
3475 offset: 0,
3476 lineWidth: 0
3477 }, userOptions);
3478 Axis.prototype.setOptions.call(this, userOptions);
3479 this.coll = 'zAxis';
3480 },
3481 setAxisSize: function() {
3482 Axis.prototype.setAxisSize.call(this);
3483 this.width = this.len = this.chart.options.chart.options3d.depth;
3484 this.right = this.chart.chartWidth - this.width - this.left;
3485 },
3486 getSeriesExtremes: function() {
3487 var axis = this,
3488 chart = axis.chart;
3489
3490 axis.hasVisibleSeries = false;
3491
3492 // Reset properties in case we're redrawing (#3353)
3493 axis.dataMin =
3494 axis.dataMax =
3495 axis.ignoreMinPadding =
3496 axis.ignoreMaxPadding = null;
3497
3498 if (axis.buildStacks) {
3499 axis.buildStacks();
3500 }
3501
3502 // loop through this axis' series
3503 each(axis.series, function(series) {
3504
3505 if (series.visible || !chart.options.chart.ignoreHiddenSeries) {
3506
3507 var seriesOptions = series.options,
3508 zData,
3509 threshold = seriesOptions.threshold;
3510
3511 axis.hasVisibleSeries = true;
3512
3513 // Validate threshold in logarithmic axes
3514 if (axis.positiveValuesOnly && threshold <= 0) {
3515 threshold = null;
3516 }
3517
3518 zData = series.zData;
3519 if (zData.length) {
3520 axis.dataMin = Math.min(
3521 pick(axis.dataMin, zData[0]),
3522 Math.min.apply(null, zData)
3523 );
3524 axis.dataMax = Math.max(
3525 pick(axis.dataMax, zData[0]),
3526 Math.max.apply(null, zData)
3527 );
3528 }
3529 }
3530 });
3531 }
3532 });
3533
3534
3535 /**
3536 * Extend the chart getAxes method to also get the color axis
3537 */
3538 wrap(Chart.prototype, 'getAxes', function(proceed) {
3539 var chart = this,
3540 options = this.options,
3541 zAxisOptions = options.zAxis = splat(options.zAxis || {});
3542
3543 proceed.call(this);
3544
3545 if (!chart.is3d()) {
3546 return;
3547 }
3548 this.zAxis = [];
3549 each(zAxisOptions, function(axisOptions, i) {
3550 axisOptions.index = i;
3551 // Z-Axis is shown horizontally, so it's kind of a X-Axis
3552 axisOptions.isX = true;
3553 var zAxis = new ZAxis(chart, axisOptions);
3554 zAxis.setScale();
3555 });
3556 });
3557
3558 }(Highcharts));
3559 (function(H) {
3560 /**
3561 * (c) 2010-2017 Torstein Honsi
3562 *
3563 * Extension to the Series object in 3D charts.
3564 *
3565 * License: www.highcharts.com/license
3566 */
3567 var perspective = H.perspective,
3568 pick = H.pick,
3569 wrap = H.wrap;
3570
3571 // Wrap the translate method to post-translate points into 3D perspective
3572 wrap(H.Series.prototype, 'translate', function(proceed) {
3573 proceed.apply(this, [].slice.call(arguments, 1));
3574
3575 if (this.chart.is3d()) {
3576 this.translate3dPoints();
3577 }
3578
3579 });
3580
3581 /**
3582 * Translate the plotX, plotY properties and add plotZ.
3583 */
3584 H.Series.prototype.translate3dPoints = function() {
3585 var series = this,
3586 chart = series.chart,
3587 zAxis = pick(series.zAxis, chart.options.zAxis[0]),
3588 rawPoints = [],
3589 rawPoint,
3590 projectedPoints,
3591 projectedPoint,
3592 zValue,
3593 i;
3594
3595 for (i = 0; i < series.data.length; i++) {
3596 rawPoint = series.data[i];
3597
3598 if (zAxis && zAxis.translate) {
3599 zValue = zAxis.isLog && zAxis.val2lin ?
3600 zAxis.val2lin(rawPoint.z) :
3601 rawPoint.z; // #4562
3602 rawPoint.plotZ = zAxis.translate(zValue);
3603 rawPoint.isInside = rawPoint.isInside ?
3604 (zValue >= zAxis.min && zValue <= zAxis.max) :
3605 false;
3606 }
3607
3608 rawPoints.push({
3609 x: pick(rawPoint.plotXold, rawPoint.plotX),
3610 y: pick(rawPoint.plotYold, rawPoint.plotY),
3611 z: pick(rawPoint.plotZold, rawPoint.plotZ)
3612 });
3613 }
3614
3615 projectedPoints = perspective(rawPoints, chart, true);
3616
3617 for (i = 0; i < series.data.length; i++) {
3618 rawPoint = series.data[i];
3619 projectedPoint = projectedPoints[i];
3620
3621 rawPoint.plotXold = rawPoint.plotX;
3622 rawPoint.plotYold = rawPoint.plotY;
3623 rawPoint.plotZold = rawPoint.plotZ;
3624
3625 rawPoint.plotX = projectedPoint.x;
3626 rawPoint.plotY = projectedPoint.y;
3627 rawPoint.plotZ = projectedPoint.z;
3628 }
3629 };
3630
3631
3632 }(Highcharts));
3633 (function(H) {
3634 /**
3635 * (c) 2010-2017 Torstein Honsi
3636 *
3637 * License: www.highcharts.com/license
3638 */
3639 var each = H.each,
3640 perspective = H.perspective,
3641 pick = H.pick,
3642 Series = H.Series,
3643 seriesTypes = H.seriesTypes,
3644 inArray = H.inArray,
3645 svg = H.svg,
3646 wrap = H.wrap;
3647
3648
3649
3650 /**
3651 * Depth of the columns in a 3D column chart. Requires `highcharts-3d.
3652 * js`.
3653 *
3654 * @type {Number}
3655 * @default 25
3656 * @since 4.0
3657 * @product highcharts
3658 * @apioption plotOptions.column.depth
3659 */
3660
3661 /**
3662 * 3D columns only. The color of the edges. Similar to `borderColor`,
3663 * except it defaults to the same color as the column.
3664 *
3665 * @type {Color}
3666 * @product highcharts
3667 * @apioption plotOptions.column.edgeColor
3668 */
3669
3670 /**
3671 * 3D columns only. The width of the colored edges.
3672 *
3673 * @type {Number}
3674 * @default 1
3675 * @product highcharts
3676 * @apioption plotOptions.column.edgeWidth
3677 */
3678
3679 /**
3680 * The spacing between columns on the Z Axis in a 3D chart. Requires
3681 * `highcharts-3d.js`.
3682 *
3683 * @type {Number}
3684 * @default 1
3685 * @since 4.0
3686 * @product highcharts
3687 * @apioption plotOptions.column.groupZPadding
3688 */
3689
3690 wrap(seriesTypes.column.prototype, 'translate', function(proceed) {
3691 proceed.apply(this, [].slice.call(arguments, 1));
3692
3693 // Do not do this if the chart is not 3D
3694 if (this.chart.is3d()) {
3695 this.translate3dShapes();
3696 }
3697 });
3698
3699 seriesTypes.column.prototype.translate3dPoints = function() {};
3700 seriesTypes.column.prototype.translate3dShapes = function() {
3701
3702 var series = this,
3703 chart = series.chart,
3704 seriesOptions = series.options,
3705 depth = seriesOptions.depth || 25,
3706 stack = seriesOptions.stacking ?
3707 (seriesOptions.stack || 0) :
3708 series.index, // #4743
3709 z = stack * (depth + (seriesOptions.groupZPadding || 1)),
3710 borderCrisp = series.borderWidth % 2 ? 0.5 : 0;
3711
3712 if (chart.inverted && !series.yAxis.reversed) {
3713 borderCrisp *= -1;
3714 }
3715
3716 if (seriesOptions.grouping !== false) {
3717 z = 0;
3718 }
3719
3720 z += (seriesOptions.groupZPadding || 1);
3721 each(series.data, function(point) {
3722 if (point.y !== null) {
3723 var shapeArgs = point.shapeArgs,
3724 tooltipPos = point.tooltipPos,
3725 // Array for final shapeArgs calculation.
3726 // We are checking two dimensions (x and y).
3727 dimensions = [
3728 ['x', 'width'],
3729 ['y', 'height']
3730 ],
3731 borderlessBase; // Crisped rects can have +/- 0.5 pixels offset.
3732
3733 // #3131 We need to check if column is inside plotArea.
3734 each(dimensions, function(d) {
3735 borderlessBase = shapeArgs[d[0]] - borderCrisp;
3736 if (borderlessBase < 0) {
3737 // If borderLessBase is smaller than 0, it is needed to set
3738 // its value to 0 or 0.5 depending on borderWidth
3739 // borderWidth may be even or odd.
3740 shapeArgs[d[1]] += shapeArgs[d[0]] + borderCrisp;
3741 shapeArgs[d[0]] = -borderCrisp;
3742 borderlessBase = 0;
3743 }
3744 if (
3745 borderlessBase + shapeArgs[d[1]] > series[d[0] + 'Axis'].len &&
3746 shapeArgs[d[1]] !== 0 // Do not change height/width of column if 0.
3747 // #6708
3748 ) {
3749 shapeArgs[d[1]] = series[d[0] + 'Axis'].len - shapeArgs[d[0]];
3750 }
3751 if (
3752 (shapeArgs[d[1]] !== 0) && // Do not remove columns with zero height/width.
3753 (
3754 shapeArgs[d[0]] >= series[d[0] + 'Axis'].len ||
3755 shapeArgs[d[0]] + shapeArgs[d[1]] <= borderCrisp
3756 )
3757 ) {
3758 for (var key in shapeArgs) { // Set args to 0 if column is outside the chart.
3759 shapeArgs[key] = 0;
3760 }
3761 }
3762 });
3763
3764 point.shapeType = 'cuboid';
3765 shapeArgs.z = z;
3766 shapeArgs.depth = depth;
3767 shapeArgs.insidePlotArea = true;
3768
3769 // Translate the tooltip position in 3d space
3770 tooltipPos = perspective([{
3771 x: tooltipPos[0],
3772 y: tooltipPos[1],
3773 z: z
3774 }], chart, true)[0];
3775 point.tooltipPos = [tooltipPos.x, tooltipPos.y];
3776 }
3777 });
3778 // store for later use #4067
3779 series.z = z;
3780 };
3781
3782 wrap(seriesTypes.column.prototype, 'animate', function(proceed) {
3783 if (!this.chart.is3d()) {
3784 proceed.apply(this, [].slice.call(arguments, 1));
3785 } else {
3786 var args = arguments,
3787 init = args[1],
3788 yAxis = this.yAxis,
3789 series = this,
3790 reversed = this.yAxis.reversed;
3791
3792 if (svg) { // VML is too slow anyway
3793 if (init) {
3794 each(series.data, function(point) {
3795 if (point.y !== null) {
3796 point.height = point.shapeArgs.height;
3797 point.shapey = point.shapeArgs.y; // #2968
3798 point.shapeArgs.height = 1;
3799 if (!reversed) {
3800 if (point.stackY) {
3801 point.shapeArgs.y = point.plotY + yAxis.translate(point.stackY);
3802 } else {
3803 point.shapeArgs.y = point.plotY + (point.negative ? -point.height : point.height);
3804 }
3805 }
3806 }
3807 });
3808
3809 } else { // run the animation
3810 each(series.data, function(point) {
3811 if (point.y !== null) {
3812 point.shapeArgs.height = point.height;
3813 point.shapeArgs.y = point.shapey; // #2968
3814 // null value do not have a graphic
3815 if (point.graphic) {
3816 point.graphic.animate(point.shapeArgs, series.options.animation);
3817 }
3818 }
3819 });
3820
3821 // redraw datalabels to the correct position
3822 this.drawDataLabels();
3823
3824 // delete this function to allow it only once
3825 series.animate = null;
3826 }
3827 }
3828 }
3829 });
3830
3831 /*
3832 * In case of 3d columns there is no sense to add this columns
3833 * to a specific series group - if series is added to a group
3834 * all columns will have the same zIndex in comparison with different series
3835 */
3836
3837 wrap(seriesTypes.column.prototype, 'plotGroup', function(proceed, prop, name, visibility, zIndex, parent) {
3838 if (this.chart.is3d() && parent && !this[prop]) {
3839 if (!this.chart.columnGroup) {
3840 this.chart.columnGroup = this.chart.renderer.g('columnGroup').add(parent);
3841 }
3842 this[prop] = this.chart.columnGroup;
3843 this.chart.columnGroup.attr(this.getPlotBox());
3844 this[prop].survive = true;
3845 }
3846 return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
3847 });
3848
3849 /*
3850 * When series is not added to group it is needed to change
3851 * setVisible method to allow correct Legend funcionality
3852 * This wrap is basing on pie chart series
3853 */
3854 wrap(seriesTypes.column.prototype, 'setVisible', function(proceed, vis) {
3855 var series = this,
3856 pointVis;
3857 if (series.chart.is3d()) {
3858 each(series.data, function(point) {
3859 point.visible = point.options.visible = vis = vis === undefined ? !point.visible : vis;
3860 pointVis = vis ? 'visible' : 'hidden';
3861 series.options.data[inArray(point, series.data)] = point.options;
3862 if (point.graphic) {
3863 point.graphic.attr({
3864 visibility: pointVis
3865 });
3866 }
3867 });
3868 }
3869 proceed.apply(this, Array.prototype.slice.call(arguments, 1));
3870 });
3871
3872 wrap(seriesTypes.column.prototype, 'init', function(proceed) {
3873 proceed.apply(this, [].slice.call(arguments, 1));
3874
3875 if (this.chart.is3d()) {
3876 var seriesOptions = this.options,
3877 grouping = seriesOptions.grouping,
3878 stacking = seriesOptions.stacking,
3879 reversedStacks = pick(this.yAxis.options.reversedStacks, true),
3880 z = 0;
3881
3882 if (!(grouping !== undefined && !grouping)) {
3883 var stacks = this.chart.retrieveStacks(stacking),
3884 stack = seriesOptions.stack || 0,
3885 i; // position within the stack
3886 for (i = 0; i < stacks[stack].series.length; i++) {
3887 if (stacks[stack].series[i] === this) {
3888 break;
3889 }
3890 }
3891 z = (10 * (stacks.totalStacks - stacks[stack].position)) + (reversedStacks ? i : -i); // #4369
3892
3893 // In case when axis is reversed, columns are also reversed inside the group (#3737)
3894 if (!this.xAxis.reversed) {
3895 z = (stacks.totalStacks * 10) - z;
3896 }
3897 }
3898
3899 seriesOptions.zIndex = z;
3900 }
3901 });
3902
3903
3904 function pointAttribs(proceed) {
3905 var attr = proceed.apply(this, [].slice.call(arguments, 1));
3906
3907 if (this.chart.is3d && this.chart.is3d()) {
3908 // Set the fill color to the fill color to provide a smooth edge
3909 attr.stroke = this.options.edgeColor || attr.fill;
3910 attr['stroke-width'] = pick(this.options.edgeWidth, 1); // #4055
3911 }
3912
3913 return attr;
3914 }
3915
3916 wrap(seriesTypes.column.prototype, 'pointAttribs', pointAttribs);
3917 if (seriesTypes.columnrange) {
3918 wrap(seriesTypes.columnrange.prototype, 'pointAttribs', pointAttribs);
3919 seriesTypes.columnrange.prototype.plotGroup = seriesTypes.column.prototype.plotGroup;
3920 seriesTypes.columnrange.prototype.setVisible = seriesTypes.column.prototype.setVisible;
3921 }
3922
3923
3924 wrap(Series.prototype, 'alignDataLabel', function(proceed) {
3925
3926 // Only do this for 3D columns and columnranges
3927 if (this.chart.is3d() && (this.type === 'column' || this.type === 'columnrange')) {
3928 var series = this,
3929 chart = series.chart;
3930
3931 var args = arguments,
3932 alignTo = args[4];
3933
3934 var pos = ({
3935 x: alignTo.x,
3936 y: alignTo.y,
3937 z: series.z
3938 });
3939 pos = perspective([pos], chart, true)[0];
3940 alignTo.x = pos.x;
3941 alignTo.y = pos.y;
3942 }
3943
3944 proceed.apply(this, [].slice.call(arguments, 1));
3945 });
3946
3947 // Added stackLabels position calculation for 3D charts.
3948 wrap(H.StackItem.prototype, 'getStackBox', function(proceed, chart) { // #3946
3949 var stackBox = proceed.apply(this, [].slice.call(arguments, 1));
3950
3951 // Only do this for 3D chart.
3952 if (chart.is3d()) {
3953 var pos = ({
3954 x: stackBox.x,
3955 y: stackBox.y,
3956 z: 0
3957 });
3958 pos = H.perspective([pos], chart, true)[0];
3959 stackBox.x = pos.x;
3960 stackBox.y = pos.y;
3961 }
3962
3963 return stackBox;
3964 });
3965
3966 /*
3967 EXTENSION FOR 3D CYLINDRICAL COLUMNS
3968 Not supported
3969 */
3970 /*
3971 var defaultOptions = H.getOptions();
3972 defaultOptions.plotOptions.cylinder = H.merge(defaultOptions.plotOptions.column);
3973 var CylinderSeries = H.extendClass(seriesTypes.column, {
3974 type: 'cylinder'
3975 });
3976 seriesTypes.cylinder = CylinderSeries;
3977
3978 wrap(seriesTypes.cylinder.prototype, 'translate', function (proceed) {
3979 proceed.apply(this, [].slice.call(arguments, 1));
3980
3981 // Do not do this if the chart is not 3D
3982 if (!this.chart.is3d()) {
3983 return;
3984 }
3985
3986 var series = this,
3987 chart = series.chart,
3988 options = chart.options,
3989 cylOptions = options.plotOptions.cylinder,
3990 options3d = options.chart.options3d,
3991 depth = cylOptions.depth || 0,
3992 alpha = chart.alpha3d;
3993
3994 var z = cylOptions.stacking ? (this.options.stack || 0) * depth : series._i * depth;
3995 z += depth / 2;
3996
3997 if (cylOptions.grouping !== false) { z = 0; }
3998
3999 each(series.data, function (point) {
4000 var shapeArgs = point.shapeArgs,
4001 deg2rad = H.deg2rad;
4002 point.shapeType = 'arc3d';
4003 shapeArgs.x += depth / 2;
4004 shapeArgs.z = z;
4005 shapeArgs.start = 0;
4006 shapeArgs.end = 2 * PI;
4007 shapeArgs.r = depth * 0.95;
4008 shapeArgs.innerR = 0;
4009 shapeArgs.depth = shapeArgs.height * (1 / sin((90 - alpha) * deg2rad)) - z;
4010 shapeArgs.alpha = 90 - alpha;
4011 shapeArgs.beta = 0;
4012 });
4013 });
4014 */
4015
4016 }(Highcharts));
4017 (function(H) {
4018 /**
4019 * (c) 2010-2017 Torstein Honsi
4020 *
4021 * 3D pie series
4022 *
4023 * License: www.highcharts.com/license
4024 */
4025 var deg2rad = H.deg2rad,
4026 each = H.each,
4027 pick = H.pick,
4028 seriesTypes = H.seriesTypes,
4029 svg = H.svg,
4030 wrap = H.wrap;
4031
4032
4033 /**
4034 * The thickness of a 3D pie. Requires `highcharts-3d.js`
4035 *
4036 * @type {Number}
4037 * @default 0
4038 * @since 4.0
4039 * @product highcharts
4040 * @apioption plotOptions.pie.depth
4041 */
4042
4043 wrap(seriesTypes.pie.prototype, 'translate', function(proceed) {
4044 proceed.apply(this, [].slice.call(arguments, 1));
4045
4046 // Do not do this if the chart is not 3D
4047 if (!this.chart.is3d()) {
4048 return;
4049 }
4050
4051 var series = this,
4052 seriesOptions = series.options,
4053 depth = seriesOptions.depth || 0,
4054 options3d = series.chart.options.chart.options3d,
4055 alpha = options3d.alpha,
4056 beta = options3d.beta,
4057 z = seriesOptions.stacking ? (seriesOptions.stack || 0) * depth : series._i * depth;
4058
4059 z += depth / 2;
4060
4061 if (seriesOptions.grouping !== false) {
4062 z = 0;
4063 }
4064
4065 each(series.data, function(point) {
4066
4067 var shapeArgs = point.shapeArgs,
4068 angle;
4069
4070 point.shapeType = 'arc3d';
4071
4072 shapeArgs.z = z;
4073 shapeArgs.depth = depth * 0.75;
4074 shapeArgs.alpha = alpha;
4075 shapeArgs.beta = beta;
4076 shapeArgs.center = series.center;
4077
4078 angle = (shapeArgs.end + shapeArgs.start) / 2;
4079
4080 point.slicedTranslation = {
4081 translateX: Math.round(Math.cos(angle) * seriesOptions.slicedOffset * Math.cos(alpha * deg2rad)),
4082 translateY: Math.round(Math.sin(angle) * seriesOptions.slicedOffset * Math.cos(alpha * deg2rad))
4083 };
4084 });
4085 });
4086
4087 wrap(seriesTypes.pie.prototype.pointClass.prototype, 'haloPath', function(proceed) {
4088 var args = arguments;
4089 return this.series.chart.is3d() ? [] : proceed.call(this, args[1]);
4090 });
4091
4092
4093 wrap(seriesTypes.pie.prototype, 'pointAttribs', function(proceed, point, state) {
4094 var attr = proceed.call(this, point, state),
4095 options = this.options;
4096
4097 if (this.chart.is3d()) {
4098 attr.stroke = options.edgeColor || point.color || this.color;
4099 attr['stroke-width'] = pick(options.edgeWidth, 1);
4100 }
4101
4102 return attr;
4103 });
4104
4105
4106 wrap(seriesTypes.pie.prototype, 'drawPoints', function(proceed) {
4107 proceed.apply(this, [].slice.call(arguments, 1));
4108
4109 if (this.chart.is3d()) {
4110 each(this.points, function(point) {
4111 var graphic = point.graphic;
4112
4113 // #4584 Check if has graphic - null points don't have it
4114 if (graphic) {
4115 // Hide null or 0 points (#3006, 3650)
4116 graphic[point.y && point.visible ? 'show' : 'hide']();
4117 }
4118 });
4119 }
4120 });
4121
4122 wrap(seriesTypes.pie.prototype, 'drawDataLabels', function(proceed) {
4123 if (this.chart.is3d()) {
4124 var series = this,
4125 chart = series.chart,
4126 options3d = chart.options.chart.options3d;
4127 each(series.data, function(point) {
4128 var shapeArgs = point.shapeArgs,
4129 r = shapeArgs.r,
4130 a1 = (shapeArgs.alpha || options3d.alpha) * deg2rad, // #3240 issue with datalabels for 0 and null values
4131 b1 = (shapeArgs.beta || options3d.beta) * deg2rad,
4132 a2 = (shapeArgs.start + shapeArgs.end) / 2,
4133 labelPos = point.labelPos,
4134 labelIndexes = [0, 2, 4], // [x1, y1, x2, y2, x3, y3]
4135 yOffset = (-r * (1 - Math.cos(a1)) * Math.sin(a2)), // + (sin(a2) > 0 ? sin(a1) * d : 0)
4136 xOffset = r * (Math.cos(b1) - 1) * Math.cos(a2);
4137
4138 // Apply perspective on label positions
4139 each(labelIndexes, function(index) {
4140 labelPos[index] += xOffset;
4141 labelPos[index + 1] += yOffset;
4142 });
4143 });
4144 }
4145
4146 proceed.apply(this, [].slice.call(arguments, 1));
4147 });
4148
4149 wrap(seriesTypes.pie.prototype, 'addPoint', function(proceed) {
4150 proceed.apply(this, [].slice.call(arguments, 1));
4151 if (this.chart.is3d()) {
4152 // destroy (and rebuild) everything!!!
4153 this.update(this.userOptions, true); // #3845 pass the old options
4154 }
4155 });
4156
4157 wrap(seriesTypes.pie.prototype, 'animate', function(proceed) {
4158 if (!this.chart.is3d()) {
4159 proceed.apply(this, [].slice.call(arguments, 1));
4160 } else {
4161 var args = arguments,
4162 init = args[1],
4163 animation = this.options.animation,
4164 attribs,
4165 center = this.center,
4166 group = this.group,
4167 markerGroup = this.markerGroup;
4168
4169 if (svg) { // VML is too slow anyway
4170
4171 if (animation === true) {
4172 animation = {};
4173 }
4174 // Initialize the animation
4175 if (init) {
4176
4177 // Scale down the group and place it in the center
4178 group.oldtranslateX = group.translateX;
4179 group.oldtranslateY = group.translateY;
4180 attribs = {
4181 translateX: center[0],
4182 translateY: center[1],
4183 scaleX: 0.001, // #1499
4184 scaleY: 0.001
4185 };
4186
4187 group.attr(attribs);
4188 if (markerGroup) {
4189 markerGroup.attrSetters = group.attrSetters;
4190 markerGroup.attr(attribs);
4191 }
4192
4193 // Run the animation
4194 } else {
4195 attribs = {
4196 translateX: group.oldtranslateX,
4197 translateY: group.oldtranslateY,
4198 scaleX: 1,
4199 scaleY: 1
4200 };
4201 group.animate(attribs, animation);
4202
4203 if (markerGroup) {
4204 markerGroup.animate(attribs, animation);
4205 }
4206
4207 // Delete this function to allow it only once
4208 this.animate = null;
4209 }
4210
4211 }
4212 }
4213 });
4214
4215 }(Highcharts));
4216 (function(H) {
4217 /**
4218 * (c) 2010-2017 Torstein Honsi
4219 *
4220 * Scatter 3D series.
4221 *
4222 * License: www.highcharts.com/license
4223 */
4224 var Point = H.Point,
4225 seriesType = H.seriesType,
4226 seriesTypes = H.seriesTypes;
4227
4228 /**
4229 * A 3D scatter plot uses x, y and z coordinates to display values for three
4230 * variables for a set of data.
4231 *
4232 * @sample {highcharts} highcharts/3d/scatter/
4233 * Simple 3D scatter
4234 * @sample {highcharts} highcharts/demo/3d-scatter-draggable
4235 * Draggable 3d scatter
4236 *
4237 * @extends {plotOptions.scatter}
4238 * @product highcharts
4239 * @optionparent plotOptions.scatter3d
4240 */
4241 seriesType('scatter3d', 'scatter', {
4242 tooltip: {
4243 pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>z: <b>{point.z}</b><br/>'
4244 }
4245
4246 // Series class
4247 }, {
4248 pointAttribs: function(point) {
4249 var attribs = seriesTypes.scatter.prototype.pointAttribs
4250 .apply(this, arguments);
4251
4252 if (this.chart.is3d() && point) {
4253 attribs.zIndex = H.pointCameraDistance(point, this.chart);
4254 }
4255
4256 return attribs;
4257 },
4258 axisTypes: ['xAxis', 'yAxis', 'zAxis'],
4259 pointArrayMap: ['x', 'y', 'z'],
4260 parallelArrays: ['x', 'y', 'z'],
4261
4262 // Require direct touch rather than using the k-d-tree, because the k-d-tree
4263 // currently doesn't take the xyz coordinate system into account (#4552)
4264 directTouch: true
4265
4266 // Point class
4267 }, {
4268 applyOptions: function() {
4269 Point.prototype.applyOptions.apply(this, arguments);
4270 if (this.z === undefined) {
4271 this.z = 0;
4272 }
4273
4274 return this;
4275 }
4276
4277 });
4278
4279
4280 /**
4281 * A `scatter3d` series. If the [type](#series.scatter3d.type) option is
4282 * not specified, it is inherited from [chart.type](#chart.type).
4283 *
4284 * For options that apply to multiple series, it is recommended to add
4285 * them to the [plotOptions.series](#plotOptions.series) options structure.
4286 * To apply to all series of this specific type, apply it to [plotOptions.
4287 * scatter3d](#plotOptions.scatter3d).
4288 *
4289 * @type {Object}
4290 * @extends series,plotOptions.scatter3d
4291 * @product highcharts
4292 * @apioption series.scatter3d
4293 */
4294
4295 /**
4296 * An array of data points for the series. For the `scatter3d` series
4297 * type, points can be given in the following ways:
4298 *
4299 * 1. An array of arrays with 3 values. In this case, the values correspond
4300 * to `x,y,z`. If the first value is a string, it is applied as the name
4301 * of the point, and the `x` value is inferred.
4302 *
4303 * ```js
4304 * data: [
4305 * [0, 0, 1],
4306 * [1, 8, 7],
4307 * [2, 9, 2]
4308 * ]
4309 * ```
4310 *
4311 * 3. An array of objects with named values. The objects are point
4312 * configuration objects as seen below. If the total number of data
4313 * points exceeds the series' [turboThreshold](#series.scatter3d.turboThreshold),
4314 * this option is not available.
4315 *
4316 * ```js
4317 * data: [{
4318 * x: 1,
4319 * y: 2,
4320 * z: 24,
4321 * name: "Point2",
4322 * color: "#00FF00"
4323 * }, {
4324 * x: 1,
4325 * y: 4,
4326 * z: 12,
4327 * name: "Point1",
4328 * color: "#FF00FF"
4329 * }]
4330 * ```
4331 *
4332 * @type {Array<Object|Array>}
4333 * @extends series.scatter.data
4334 * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
4335 * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
4336 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
4337 * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
4338 * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
4339 * @product highcharts
4340 * @apioption series.scatter3d.data
4341 */
4342
4343 /**
4344 * The z value for each data point.
4345 *
4346 * @type {Number}
4347 * @product highcharts
4348 * @apioption series.scatter3d.data.z
4349 */
4350
4351 }(Highcharts));
4352 (function(H) {
4353 /**
4354 * (c) 2010-2017 Torstein Honsi
4355 *
4356 * License: www.highcharts.com/license
4357 */
4358
4359 var Axis = H.Axis,
4360 SVGRenderer = H.SVGRenderer,
4361 VMLRenderer = H.VMLRenderer;
4362
4363 /**
4364 * Extension to the VML Renderer
4365 */
4366 if (VMLRenderer) {
4367
4368 H.setOptions({
4369 animate: false
4370 });
4371
4372 VMLRenderer.prototype.face3d = SVGRenderer.prototype.face3d;
4373 VMLRenderer.prototype.polyhedron = SVGRenderer.prototype.polyhedron;
4374 VMLRenderer.prototype.cuboid = SVGRenderer.prototype.cuboid;
4375 VMLRenderer.prototype.cuboidPath = SVGRenderer.prototype.cuboidPath;
4376
4377 VMLRenderer.prototype.toLinePath = SVGRenderer.prototype.toLinePath;
4378 VMLRenderer.prototype.toLineSegments = SVGRenderer.prototype.toLineSegments;
4379
4380 VMLRenderer.prototype.createElement3D =
4381 SVGRenderer.prototype.createElement3D;
4382
4383 VMLRenderer.prototype.arc3d = function(shapeArgs) {
4384 var result = SVGRenderer.prototype.arc3d.call(this, shapeArgs);
4385 result.css({
4386 zIndex: result.zIndex
4387 });
4388 return result;
4389 };
4390
4391 H.VMLRenderer.prototype.arc3dPath = H.SVGRenderer.prototype.arc3dPath;
4392
4393 H.wrap(Axis.prototype, 'render', function(proceed) {
4394 proceed.apply(this, [].slice.call(arguments, 1));
4395 // VML doesn't support a negative z-index
4396 if (this.sideFrame) {
4397 this.sideFrame.css({
4398 zIndex: 0
4399 });
4400 this.sideFrame.front.attr({
4401 fill: this.sideFrame.color
4402 });
4403 }
4404 if (this.bottomFrame) {
4405 this.bottomFrame.css({
4406 zIndex: 1
4407 });
4408 this.bottomFrame.front.attr({
4409 fill: this.bottomFrame.color
4410 });
4411 }
4412 if (this.backFrame) {
4413 this.backFrame.css({
4414 zIndex: 0
4415 });
4416 this.backFrame.front.attr({
4417 fill: this.backFrame.color
4418 });
4419 }
4420 });
4421
4422 }
4423
4424
4425 }(Highcharts));
4426}));