/* *
|
*
|
* (c) 2010-2019 Torstein Honsi
|
*
|
* Extensions to the SVGRenderer class to enable 3D shapes
|
*
|
* License: www.highcharts.com/license
|
*
|
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
*
|
* */
|
'use strict';
|
|
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
|
|
var _Globals = require('../parts/Globals.js');
|
|
var _Globals2 = _interopRequireDefault(_Globals);
|
|
var _Utilities = require('../parts/Utilities.js');
|
|
var _Utilities2 = _interopRequireDefault(_Utilities);
|
|
require('../parts/Color.js');
|
|
require('../parts/SvgRenderer.js');
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
var defined = _Utilities2.default.defined,
|
objectEach = _Utilities2.default.objectEach;
|
|
var cos = Math.cos,
|
PI = Math.PI,
|
sin = Math.sin;
|
var animObject = _Globals2.default.animObject,
|
charts = _Globals2.default.charts,
|
color = _Globals2.default.color,
|
deg2rad = _Globals2.default.deg2rad,
|
extend = _Globals2.default.extend,
|
merge = _Globals2.default.merge,
|
perspective = _Globals2.default.perspective,
|
pick = _Globals2.default.pick,
|
SVGElement = _Globals2.default.SVGElement,
|
SVGRenderer = _Globals2.default.SVGRenderer,
|
|
// internal:
|
dFactor,
|
element3dMethods,
|
cuboidMethods;
|
/*
|
EXTENSION TO THE SVG-RENDERER TO ENABLE 3D SHAPES
|
*/
|
// HELPER METHODS
|
dFactor = 4 * (Math.sqrt(2) - 1) / 3 / (PI / 2);
|
/* eslint-disable no-invalid-this, valid-jsdoc */
|
/**
|
* Method to construct a curved path. Can 'wrap' around more then 180 degrees.
|
* @private
|
*/
|
function curveTo(cx, cy, rx, ry, start, end, dx, dy) {
|
var result = [],
|
arcAngle = end - start;
|
if (end > start && end - start > Math.PI / 2 + 0.0001) {
|
result = result.concat(curveTo(cx, cy, rx, ry, start, start + Math.PI / 2, dx, dy));
|
result = result.concat(curveTo(cx, cy, rx, ry, start + Math.PI / 2, end, dx, dy));
|
return result;
|
}
|
if (end < start && start - end > Math.PI / 2 + 0.0001) {
|
result = result.concat(curveTo(cx, cy, rx, ry, start, start - Math.PI / 2, dx, dy));
|
result = result.concat(curveTo(cx, cy, rx, ry, start - Math.PI / 2, end, dx, dy));
|
return result;
|
}
|
return ['C', cx + rx * Math.cos(start) - rx * dFactor * arcAngle * Math.sin(start) + dx, cy + ry * Math.sin(start) + ry * dFactor * arcAngle * Math.cos(start) + dy, cx + rx * Math.cos(end) + rx * dFactor * arcAngle * Math.sin(end) + dx, cy + ry * Math.sin(end) - ry * dFactor * arcAngle * Math.cos(end) + dy, cx + rx * Math.cos(end) + dx, cy + ry * Math.sin(end) + dy];
|
}
|
SVGRenderer.prototype.toLinePath = function (points, closed) {
|
var result = [];
|
// Put "L x y" for each point
|
points.forEach(function (point) {
|
result.push('L', point.x, point.y);
|
});
|
if (points.length) {
|
// Set the first element to M
|
result[0] = 'M';
|
// If it is a closed line, add Z
|
if (closed) {
|
result.push('Z');
|
}
|
}
|
return result;
|
};
|
SVGRenderer.prototype.toLineSegments = function (points) {
|
var result = [],
|
m = true;
|
points.forEach(function (point) {
|
result.push(m ? 'M' : 'L', point.x, point.y);
|
m = !m;
|
});
|
return result;
|
};
|
// A 3-D Face is defined by it's 3D vertexes, and is only visible if it's
|
// vertexes are counter-clockwise (Back-face culling). It is used as a
|
// polyhedron Element
|
SVGRenderer.prototype.face3d = function (args) {
|
var renderer = this,
|
ret = this.createElement('path');
|
ret.vertexes = [];
|
ret.insidePlotArea = false;
|
ret.enabled = true;
|
ret.attr = function (hash) {
|
if ((typeof hash === 'undefined' ? 'undefined' : _typeof(hash)) === 'object' && (defined(hash.enabled) || defined(hash.vertexes) || defined(hash.insidePlotArea))) {
|
this.enabled = pick(hash.enabled, this.enabled);
|
this.vertexes = pick(hash.vertexes, this.vertexes);
|
this.insidePlotArea = pick(hash.insidePlotArea, this.insidePlotArea);
|
delete hash.enabled;
|
delete hash.vertexes;
|
delete hash.insidePlotArea;
|
var chart = charts[renderer.chartIndex],
|
vertexes2d = perspective(this.vertexes, chart, this.insidePlotArea),
|
path = renderer.toLinePath(vertexes2d, true),
|
area = _Globals2.default.shapeArea(vertexes2d),
|
visibility = this.enabled && area > 0 ? 'visible' : 'hidden';
|
hash.d = path;
|
hash.visibility = visibility;
|
}
|
return SVGElement.prototype.attr.apply(this, arguments);
|
};
|
ret.animate = function (params) {
|
if ((typeof params === 'undefined' ? 'undefined' : _typeof(params)) === 'object' && (defined(params.enabled) || defined(params.vertexes) || defined(params.insidePlotArea))) {
|
this.enabled = pick(params.enabled, this.enabled);
|
this.vertexes = pick(params.vertexes, this.vertexes);
|
this.insidePlotArea = pick(params.insidePlotArea, this.insidePlotArea);
|
delete params.enabled;
|
delete params.vertexes;
|
delete params.insidePlotArea;
|
var chart = charts[renderer.chartIndex],
|
vertexes2d = perspective(this.vertexes, chart, this.insidePlotArea),
|
path = renderer.toLinePath(vertexes2d, true),
|
area = _Globals2.default.shapeArea(vertexes2d),
|
visibility = this.enabled && area > 0 ? 'visible' : 'hidden';
|
params.d = path;
|
this.attr('visibility', visibility);
|
}
|
return SVGElement.prototype.animate.apply(this, arguments);
|
};
|
return ret.attr(args);
|
};
|
// A Polyhedron is a handy way of defining a group of 3-D faces. It's only
|
// attribute is `faces`, an array of attributes of each one of it's Face3D
|
// instances.
|
SVGRenderer.prototype.polyhedron = function (args) {
|
var renderer = this,
|
result = this.g(),
|
destroy = result.destroy;
|
if (!this.styledMode) {
|
result.attr({
|
'stroke-linejoin': 'round'
|
});
|
}
|
result.faces = [];
|
// destroy all children
|
result.destroy = function () {
|
for (var i = 0; i < result.faces.length; i++) {
|
result.faces[i].destroy();
|
}
|
return destroy.call(this);
|
};
|
result.attr = function (hash, val, complete, continueAnimation) {
|
if ((typeof hash === 'undefined' ? 'undefined' : _typeof(hash)) === 'object' && defined(hash.faces)) {
|
while (result.faces.length > hash.faces.length) {
|
result.faces.pop().destroy();
|
}
|
while (result.faces.length < hash.faces.length) {
|
result.faces.push(renderer.face3d().add(result));
|
}
|
for (var i = 0; i < hash.faces.length; i++) {
|
if (renderer.styledMode) {
|
delete hash.faces[i].fill;
|
}
|
result.faces[i].attr(hash.faces[i], null, complete, continueAnimation);
|
}
|
delete hash.faces;
|
}
|
return SVGElement.prototype.attr.apply(this, arguments);
|
};
|
result.animate = function (params, duration, complete) {
|
if (params && params.faces) {
|
while (result.faces.length > params.faces.length) {
|
result.faces.pop().destroy();
|
}
|
while (result.faces.length < params.faces.length) {
|
result.faces.push(renderer.face3d().add(result));
|
}
|
for (var i = 0; i < params.faces.length; i++) {
|
result.faces[i].animate(params.faces[i], duration, complete);
|
}
|
delete params.faces;
|
}
|
return SVGElement.prototype.animate.apply(this, arguments);
|
};
|
return result.attr(args);
|
};
|
// Base, abstract prototype member for 3D elements
|
element3dMethods = {
|
/**
|
* The init is used by base - renderer.Element
|
* @private
|
*/
|
initArgs: function initArgs(args) {
|
var elem3d = this,
|
renderer = elem3d.renderer,
|
paths = renderer[elem3d.pathType + 'Path'](args),
|
zIndexes = paths.zIndexes;
|
// build parts
|
elem3d.parts.forEach(function (part) {
|
elem3d[part] = renderer.path(paths[part]).attr({
|
'class': 'highcharts-3d-' + part,
|
zIndex: zIndexes[part] || 0
|
}).add(elem3d);
|
});
|
elem3d.attr({
|
'stroke-linejoin': 'round',
|
zIndex: zIndexes.group
|
});
|
// store original destroy
|
elem3d.originalDestroy = elem3d.destroy;
|
elem3d.destroy = elem3d.destroyParts;
|
},
|
/**
|
* Single property setter that applies options to each part
|
* @private
|
*/
|
singleSetterForParts: function singleSetterForParts(prop, val, values, verb, duration, complete) {
|
var elem3d = this,
|
newAttr = {},
|
optionsToApply = [null, null, verb || 'attr', duration, complete],
|
hasZIndexes = values && values.zIndexes;
|
if (!values) {
|
newAttr[prop] = val;
|
optionsToApply[0] = newAttr;
|
} else {
|
objectEach(values, function (partVal, part) {
|
newAttr[part] = {};
|
newAttr[part][prop] = partVal;
|
// include zIndexes if provided
|
if (hasZIndexes) {
|
newAttr[part].zIndex = values.zIndexes[part] || 0;
|
}
|
});
|
optionsToApply[1] = newAttr;
|
}
|
return elem3d.processParts.apply(elem3d, optionsToApply);
|
},
|
/**
|
* Calls function for each part. Used for attr, animate and destroy.
|
* @private
|
*/
|
processParts: function processParts(props, partsProps, verb, duration, complete) {
|
var elem3d = this;
|
elem3d.parts.forEach(function (part) {
|
// if different props for different parts
|
if (partsProps) {
|
props = _Globals2.default.pick(partsProps[part], false);
|
}
|
// only if something to set, but allow undefined
|
if (props !== false) {
|
elem3d[part][verb](props, duration, complete);
|
}
|
});
|
return elem3d;
|
},
|
/**
|
* Destroy all parts
|
* @private
|
*/
|
destroyParts: function destroyParts() {
|
this.processParts(null, null, 'destroy');
|
return this.originalDestroy();
|
}
|
};
|
// CUBOID
|
cuboidMethods = _Globals2.default.merge(element3dMethods, {
|
parts: ['front', 'top', 'side'],
|
pathType: 'cuboid',
|
attr: function attr(args, val, complete, continueAnimation) {
|
// Resolve setting attributes by string name
|
if (typeof args === 'string' && typeof val !== 'undefined') {
|
var key = args;
|
args = {};
|
args[key] = val;
|
}
|
if (args.shapeArgs || defined(args.x)) {
|
return this.singleSetterForParts('d', null, this.renderer[this.pathType + 'Path'](args.shapeArgs || args));
|
}
|
return SVGElement.prototype.attr.call(this, args, undefined, complete, continueAnimation);
|
},
|
animate: function animate(args, duration, complete) {
|
if (defined(args.x) && defined(args.y)) {
|
var paths = this.renderer[this.pathType + 'Path'](args);
|
this.singleSetterForParts('d', null, paths, 'animate', duration, complete);
|
this.attr({
|
zIndex: paths.zIndexes.group
|
});
|
} else {
|
SVGElement.prototype.animate.call(this, args, duration, complete);
|
}
|
return this;
|
},
|
fillSetter: function fillSetter(fill) {
|
this.singleSetterForParts('fill', null, {
|
front: fill,
|
top: color(fill).brighten(0.1).get(),
|
side: color(fill).brighten(-0.1).get()
|
});
|
// fill for animation getter (#6776)
|
this.color = this.fill = fill;
|
return this;
|
}
|
});
|
// set them up
|
SVGRenderer.prototype.elements3d = {
|
base: element3dMethods,
|
cuboid: cuboidMethods
|
};
|
/**
|
* return result, generalization
|
* @private
|
*/
|
SVGRenderer.prototype.element3d = function (type, shapeArgs) {
|
// base
|
var ret = this.g();
|
// extend
|
_Globals2.default.extend(ret, this.elements3d[type]);
|
// init
|
ret.initArgs(shapeArgs);
|
// return
|
return ret;
|
};
|
// generelized, so now use simply
|
SVGRenderer.prototype.cuboid = function (shapeArgs) {
|
return this.element3d('cuboid', shapeArgs);
|
};
|
// Generates a cuboid path and zIndexes
|
_Globals2.default.SVGRenderer.prototype.cuboidPath = function (shapeArgs) {
|
var x = shapeArgs.x,
|
y = shapeArgs.y,
|
z = shapeArgs.z,
|
h = shapeArgs.height,
|
w = shapeArgs.width,
|
d = shapeArgs.depth,
|
chart = charts[this.chartIndex],
|
front,
|
back,
|
top,
|
bottom,
|
left,
|
right,
|
shape,
|
path1,
|
path2,
|
path3,
|
isFront,
|
isTop,
|
isRight,
|
options3d = chart.options.chart.options3d,
|
alpha = options3d.alpha,
|
|
// Priority for x axis is the biggest,
|
// because of x direction has biggest influence on zIndex
|
incrementX = 10000,
|
|
// y axis has the smallest priority in case of our charts
|
// (needs to be set because of stacking)
|
incrementY = 10,
|
incrementZ = 100,
|
zIndex = 0,
|
|
// The 8 corners of the cube
|
pArr = [{
|
x: x,
|
y: y,
|
z: z
|
}, {
|
x: x + w,
|
y: y,
|
z: z
|
}, {
|
x: x + w,
|
y: y + h,
|
z: z
|
}, {
|
x: x,
|
y: y + h,
|
z: z
|
}, {
|
x: x,
|
y: y + h,
|
z: z + d
|
}, {
|
x: x + w,
|
y: y + h,
|
z: z + d
|
}, {
|
x: x + w,
|
y: y,
|
z: z + d
|
}, {
|
x: x,
|
y: y,
|
z: z + d
|
}],
|
pickShape;
|
// apply perspective
|
pArr = perspective(pArr, chart, shapeArgs.insidePlotArea);
|
/**
|
* helper method to decide which side is visible
|
* @private
|
*/
|
function mapPath(i) {
|
return pArr[i];
|
}
|
/**
|
* First value - path with specific side
|
* Second value - added information about side for later calculations.
|
* Possible second values are 0 for path1, 1 for path2 and -1 for no path
|
* chosen.
|
* @private
|
*/
|
pickShape = function pickShape(path1, path2) {
|
var ret = [[], -1];
|
path1 = path1.map(mapPath);
|
path2 = path2.map(mapPath);
|
if (_Globals2.default.shapeArea(path1) < 0) {
|
ret = [path1, 0];
|
} else if (_Globals2.default.shapeArea(path2) < 0) {
|
ret = [path2, 1];
|
}
|
return ret;
|
};
|
// front or back
|
front = [3, 2, 1, 0];
|
back = [7, 6, 5, 4];
|
shape = pickShape(front, back);
|
path1 = shape[0];
|
isFront = shape[1];
|
// top or bottom
|
top = [1, 6, 7, 0];
|
bottom = [4, 5, 2, 3];
|
shape = pickShape(top, bottom);
|
path2 = shape[0];
|
isTop = shape[1];
|
// side
|
right = [1, 2, 5, 6];
|
left = [0, 7, 4, 3];
|
shape = pickShape(right, left);
|
path3 = shape[0];
|
isRight = shape[1];
|
/* New block used for calculating zIndex. It is basing on X, Y and Z
|
position of specific columns. All zIndexes (for X, Y and Z values) are
|
added to the final zIndex, where every value has different priority. 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). Big
|
differences between priorities is made because we need to ensure that
|
even for big changes in Y and Z parameters all columns will be drawn
|
correctly. */
|
if (isRight === 1) {
|
zIndex += incrementX * (1000 - x);
|
} else if (!isRight) {
|
zIndex += incrementX * x;
|
}
|
zIndex += incrementY * (!isTop ||
|
// Numbers checked empirically
|
alpha >= 0 && alpha <= 180 || alpha < 360 && alpha > 357.5 ? chart.plotHeight - y : 10 + y);
|
if (isFront === 1) {
|
zIndex += incrementZ * z;
|
} else if (!isFront) {
|
zIndex += incrementZ * (1000 - z);
|
}
|
return {
|
front: this.toLinePath(path1, true),
|
top: this.toLinePath(path2, true),
|
side: this.toLinePath(path3, true),
|
zIndexes: {
|
group: Math.round(zIndex)
|
},
|
// additional info about zIndexes
|
isFront: isFront,
|
isTop: isTop
|
}; // #4774
|
};
|
// SECTORS //
|
_Globals2.default.SVGRenderer.prototype.arc3d = function (attribs) {
|
var wrapper = this.g(),
|
renderer = wrapper.renderer,
|
customAttribs = ['x', 'y', 'r', 'innerR', 'start', 'end'];
|
/**
|
* Get custom attributes. Don't mutate the original object and return an
|
* object with only custom attr.
|
* @private
|
*/
|
function suckOutCustom(params) {
|
var hasCA = false,
|
ca = {},
|
key;
|
params = merge(params); // Don't mutate the original object
|
for (key in params) {
|
if (customAttribs.indexOf(key) !== -1) {
|
ca[key] = params[key];
|
delete params[key];
|
hasCA = true;
|
}
|
}
|
return hasCA ? ca : false;
|
}
|
attribs = merge(attribs);
|
attribs.alpha = (attribs.alpha || 0) * deg2rad;
|
attribs.beta = (attribs.beta || 0) * deg2rad;
|
// Create the different sub sections of the shape
|
wrapper.top = renderer.path();
|
wrapper.side1 = renderer.path();
|
wrapper.side2 = renderer.path();
|
wrapper.inn = renderer.path();
|
wrapper.out = renderer.path();
|
// Add all faces
|
wrapper.onAdd = function () {
|
var parent = wrapper.parentGroup,
|
className = wrapper.attr('class');
|
wrapper.top.add(wrapper);
|
// These faces are added outside the wrapper group because the z index
|
// relates to neighbour elements as well
|
['out', 'inn', 'side1', 'side2'].forEach(function (face) {
|
wrapper[face].attr({
|
'class': className + ' highcharts-3d-side'
|
}).add(parent);
|
});
|
};
|
// Cascade to faces
|
['addClass', 'removeClass'].forEach(function (fn) {
|
wrapper[fn] = function () {
|
var args = arguments;
|
['top', 'out', 'inn', 'side1', 'side2'].forEach(function (face) {
|
wrapper[face][fn].apply(wrapper[face], args);
|
});
|
};
|
});
|
/**
|
* Compute the transformed paths and set them to the composite shapes
|
* @private
|
*/
|
wrapper.setPaths = function (attribs) {
|
var paths = wrapper.renderer.arc3dPath(attribs),
|
zIndex = paths.zTop * 100;
|
wrapper.attribs = attribs;
|
wrapper.top.attr({ d: paths.top, zIndex: paths.zTop });
|
wrapper.inn.attr({ d: paths.inn, zIndex: paths.zInn });
|
wrapper.out.attr({ d: paths.out, zIndex: paths.zOut });
|
wrapper.side1.attr({ d: paths.side1, zIndex: paths.zSide1 });
|
wrapper.side2.attr({ d: paths.side2, zIndex: paths.zSide2 });
|
// show all children
|
wrapper.zIndex = zIndex;
|
wrapper.attr({ zIndex: zIndex });
|
// Set the radial gradient center the first time
|
if (attribs.center) {
|
wrapper.top.setRadialReference(attribs.center);
|
delete attribs.center;
|
}
|
};
|
wrapper.setPaths(attribs);
|
/**
|
* Apply the fill to the top and a darker shade to the sides
|
* @private
|
*/
|
wrapper.fillSetter = function (value) {
|
var darker = color(value).brighten(-0.1).get();
|
this.fill = value;
|
this.side1.attr({ fill: darker });
|
this.side2.attr({ fill: darker });
|
this.inn.attr({ fill: darker });
|
this.out.attr({ fill: darker });
|
this.top.attr({ fill: value });
|
return this;
|
};
|
// Apply the same value to all. These properties cascade down to the
|
// children when set to the composite arc3d.
|
['opacity', 'translateX', 'translateY', 'visibility'].forEach(function (setter) {
|
wrapper[setter + 'Setter'] = function (value, key) {
|
wrapper[key] = value;
|
['out', 'inn', 'side1', 'side2', 'top'].forEach(function (el) {
|
wrapper[el].attr(key, value);
|
});
|
};
|
});
|
// Override attr to remove shape attributes and use those to set child paths
|
wrapper.attr = function (params) {
|
var ca;
|
if ((typeof params === 'undefined' ? 'undefined' : _typeof(params)) === 'object') {
|
ca = suckOutCustom(params);
|
if (ca) {
|
extend(wrapper.attribs, ca);
|
wrapper.setPaths(wrapper.attribs);
|
}
|
}
|
return SVGElement.prototype.attr.apply(wrapper, arguments);
|
};
|
// Override the animate function by sucking out custom parameters related to
|
// the shapes directly, and update the shapes from the animation step.
|
wrapper.animate = function (params, animation, complete) {
|
var ca,
|
from = this.attribs,
|
to,
|
anim,
|
randomProp = 'data-' + Math.random().toString(26).substring(2, 9);
|
// Attribute-line properties connected to 3D. These shouldn't have been
|
// in the attribs collection in the first place.
|
delete params.center;
|
delete params.z;
|
delete params.depth;
|
delete params.alpha;
|
delete params.beta;
|
anim = animObject(pick(animation, this.renderer.globalAnimation));
|
if (anim.duration) {
|
ca = suckOutCustom(params);
|
// Params need to have a property in order for the step to run
|
// (#5765, #7097, #7437)
|
wrapper[randomProp] = 0;
|
params[randomProp] = 1;
|
wrapper[randomProp + 'Setter'] = _Globals2.default.noop;
|
if (ca) {
|
to = ca;
|
anim.step = function (a, fx) {
|
/**
|
* @private
|
*/
|
function interpolate(key) {
|
return from[key] + (pick(to[key], from[key]) - from[key]) * fx.pos;
|
}
|
if (fx.prop === randomProp) {
|
fx.elem.setPaths(merge(from, {
|
x: interpolate('x'),
|
y: interpolate('y'),
|
r: interpolate('r'),
|
innerR: interpolate('innerR'),
|
start: interpolate('start'),
|
end: interpolate('end')
|
}));
|
}
|
};
|
}
|
animation = anim; // Only when duration (#5572)
|
}
|
return SVGElement.prototype.animate.call(this, params, animation, complete);
|
};
|
// destroy all children
|
wrapper.destroy = function () {
|
this.top.destroy();
|
this.out.destroy();
|
this.inn.destroy();
|
this.side1.destroy();
|
this.side2.destroy();
|
return SVGElement.prototype.destroy.call(this);
|
};
|
// hide all children
|
wrapper.hide = function () {
|
this.top.hide();
|
this.out.hide();
|
this.inn.hide();
|
this.side1.hide();
|
this.side2.hide();
|
};
|
wrapper.show = function (inherit) {
|
this.top.show(inherit);
|
this.out.show(inherit);
|
this.inn.show(inherit);
|
this.side1.show(inherit);
|
this.side2.show(inherit);
|
};
|
return wrapper;
|
};
|
// Generate the paths required to draw a 3D arc
|
SVGRenderer.prototype.arc3dPath = function (shapeArgs) {
|
var cx = shapeArgs.x,
|
// x coordinate of the center
|
cy = shapeArgs.y,
|
// y coordinate of the center
|
start = shapeArgs.start,
|
// start angle
|
end = shapeArgs.end - 0.00001,
|
// end angle
|
r = shapeArgs.r,
|
// radius
|
ir = shapeArgs.innerR || 0,
|
// inner radius
|
d = shapeArgs.depth || 0,
|
// depth
|
alpha = shapeArgs.alpha,
|
// alpha rotation of the chart
|
beta = shapeArgs.beta; // beta rotation of the chart
|
// Derived Variables
|
var cs = Math.cos(start),
|
// cosinus of the start angle
|
ss = Math.sin(start),
|
// sinus of the start angle
|
ce = Math.cos(end),
|
// cosinus of the end angle
|
se = Math.sin(end),
|
// sinus of the end angle
|
rx = r * Math.cos(beta),
|
// x-radius
|
ry = r * Math.cos(alpha),
|
// y-radius
|
irx = ir * Math.cos(beta),
|
// x-radius (inner)
|
iry = ir * Math.cos(alpha),
|
// y-radius (inner)
|
dx = d * Math.sin(beta),
|
// distance between top and bottom in x
|
dy = d * Math.sin(alpha); // distance between top and bottom in y
|
// TOP
|
var top = ['M', cx + rx * cs, cy + ry * ss];
|
top = top.concat(curveTo(cx, cy, rx, ry, start, end, 0, 0));
|
top = top.concat(['L', cx + irx * ce, cy + iry * se]);
|
top = top.concat(curveTo(cx, cy, irx, iry, end, start, 0, 0));
|
top = top.concat(['Z']);
|
// OUTSIDE
|
var b = beta > 0 ? Math.PI / 2 : 0,
|
a = alpha > 0 ? 0 : Math.PI / 2;
|
var start2 = start > -b ? start : end > -b ? -b : start,
|
end2 = end < PI - a ? end : start < PI - a ? PI - a : end,
|
midEnd = 2 * PI - a;
|
// When slice goes over bottom middle, need to add both, left and right
|
// outer side. Additionally, when we cross right hand edge, create sharp
|
// edge. Outer shape/wall:
|
//
|
// -------
|
// / ^ \
|
// 4) / / \ \ 1)
|
// / / \ \
|
// / / \ \
|
// (c)=> ==== ==== <=(d)
|
// \ \ / /
|
// \ \<=(a)/ /
|
// \ \ / / <=(b)
|
// 3) \ v / 2)
|
// -------
|
//
|
// (a) - inner side
|
// (b) - outer side
|
// (c) - left edge (sharp)
|
// (d) - right edge (sharp)
|
// 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
|
var out = ['M', cx + rx * cos(start2), cy + ry * sin(start2)];
|
out = out.concat(curveTo(cx, cy, rx, ry, start2, end2, 0, 0));
|
// When shape is wide, it can cross both, (c) and (d) edges, when using
|
// startAngle
|
if (end > midEnd && start < midEnd) {
|
// Go to outer side
|
out = out.concat(['L', cx + rx * cos(end2) + dx, cy + ry * sin(end2) + dy]);
|
// Curve to the right edge of the slice (d)
|
out = out.concat(curveTo(cx, cy, rx, ry, end2, midEnd, dx, dy));
|
// Go to the inner side
|
out = out.concat(['L', cx + rx * cos(midEnd), cy + ry * sin(midEnd)]);
|
// Curve to the true end of the slice
|
out = out.concat(curveTo(cx, cy, rx, ry, midEnd, end, 0, 0));
|
// Go to the outer side
|
out = out.concat(['L', cx + rx * cos(end) + dx, cy + ry * sin(end) + dy]);
|
// Go back to middle (d)
|
out = out.concat(curveTo(cx, cy, rx, ry, end, midEnd, dx, dy));
|
out = out.concat(['L', cx + rx * cos(midEnd), cy + ry * sin(midEnd)]);
|
// Go back to the left edge
|
out = out.concat(curveTo(cx, cy, rx, ry, midEnd, end2, 0, 0));
|
// But shape can cross also only (c) edge:
|
} else if (end > PI - a && start < PI - a) {
|
// Go to outer side
|
out = out.concat(['L', cx + rx * Math.cos(end2) + dx, cy + ry * Math.sin(end2) + dy]);
|
// Curve to the true end of the slice
|
out = out.concat(curveTo(cx, cy, rx, ry, end2, end, dx, dy));
|
// Go to the inner side
|
out = out.concat(['L', cx + rx * Math.cos(end), cy + ry * Math.sin(end)]);
|
// Go back to the artifical end2
|
out = out.concat(curveTo(cx, cy, rx, ry, end, end2, 0, 0));
|
}
|
out = out.concat(['L', cx + rx * Math.cos(end2) + dx, cy + ry * Math.sin(end2) + dy]);
|
out = out.concat(curveTo(cx, cy, rx, ry, end2, start2, dx, dy));
|
out = out.concat(['Z']);
|
// INSIDE
|
var inn = ['M', cx + irx * cs, cy + iry * ss];
|
inn = inn.concat(curveTo(cx, cy, irx, iry, start, end, 0, 0));
|
inn = inn.concat(['L', cx + irx * Math.cos(end) + dx, cy + iry * Math.sin(end) + dy]);
|
inn = inn.concat(curveTo(cx, cy, irx, iry, end, start, dx, dy));
|
inn = inn.concat(['Z']);
|
// SIDES
|
var side1 = ['M', cx + rx * cs, cy + ry * ss, 'L', cx + rx * cs + dx, cy + ry * ss + dy, 'L', cx + irx * cs + dx, cy + iry * ss + dy, 'L', cx + irx * cs, cy + iry * ss, 'Z'];
|
var side2 = ['M', cx + rx * ce, cy + ry * se, 'L', cx + rx * ce + dx, cy + ry * se + dy, 'L', cx + irx * ce + dx, cy + iry * se + dy, 'L', cx + irx * ce, cy + iry * se, 'Z'];
|
// correction for changed position of vanishing point caused by alpha and
|
// beta rotations
|
var angleCorr = Math.atan2(dy, -dx),
|
angleEnd = Math.abs(end + angleCorr),
|
angleStart = Math.abs(start + angleCorr),
|
angleMid = Math.abs((start + end) / 2 + angleCorr);
|
/**
|
* set to 0-PI range
|
* @private
|
*/
|
function toZeroPIRange(angle) {
|
angle = angle % (2 * Math.PI);
|
if (angle > Math.PI) {
|
angle = 2 * Math.PI - angle;
|
}
|
return angle;
|
}
|
angleEnd = toZeroPIRange(angleEnd);
|
angleStart = toZeroPIRange(angleStart);
|
angleMid = toZeroPIRange(angleMid);
|
// *1e5 is to compensate pInt in zIndexSetter
|
var incPrecision = 1e5,
|
a1 = angleMid * incPrecision,
|
a2 = angleStart * incPrecision,
|
a3 = angleEnd * incPrecision;
|
return {
|
top: top,
|
// max angle is PI, so this is always higher
|
zTop: Math.PI * incPrecision + 1,
|
out: out,
|
zOut: Math.max(a1, a2, a3),
|
inn: inn,
|
zInn: Math.max(a1, a2, a3),
|
side1: side1,
|
zSide1: a3 * 0.99,
|
side2: side2,
|
zSide2: a2 * 0.99
|
};
|
};
|