chore: version inicial del codigo fuente del LMS Estafeta

This commit is contained in:
2026-10-02 20:33:12 -06:00
commit c8bb6b97b9
1572 changed files with 457520 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+611
View File
@@ -0,0 +1,611 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* Extenstion for 3d axes
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../Globals.js';
import Math3D from '../../Extensions/Math3D.js';
var perspective = Math3D.perspective, perspective3D = Math3D.perspective3D, shapeArea = Math3D.shapeArea;
import Tick from './Tick.js';
import Tick3D from './Tick3D.js';
import U from '../Utilities.js';
var addEvent = U.addEvent, merge = U.merge, pick = U.pick, wrap = U.wrap;
var deg2rad = H.deg2rad;
/* eslint-disable valid-jsdoc */
/**
* Adds 3D support to axes.
* @private
* @class
*/
var Axis3DAdditions = /** @class */ (function () {
/* *
*
* Constructors
*
* */
/**
* @private
*/
function Axis3DAdditions(axis) {
this.axis = axis;
}
/* *
*
* Functions
*
* */
/**
* @private
* @param {Highcharts.Axis} axis
* Related axis.
* @param {Highcharts.Position3dObject} pos
* Position to fix.
* @param {boolean} [isTitle]
* Whether this is a title position.
* @return {Highcharts.Position3dObject}
* Fixed position.
*/
Axis3DAdditions.prototype.fix3dPosition = function (pos, isTitle) {
var axis3D = this;
var axis = axis3D.axis;
var chart = axis.chart;
// Do not do this if the chart is not 3D
if (axis.coll === 'colorAxis' ||
!chart.chart3d ||
!chart.is3d()) {
return pos;
}
var alpha = deg2rad * chart.options.chart.options3d.alpha, beta = deg2rad * chart.options.chart.options3d.beta, positionMode = pick(isTitle && axis.options.title.position3d, axis.options.labels.position3d), skew = pick(isTitle && axis.options.title.skew3d, axis.options.labels.skew3d), frame = chart.chart3d.frame3d, plotLeft = chart.plotLeft, plotRight = chart.plotWidth + plotLeft, plotTop = chart.plotTop, plotBottom = chart.plotHeight + plotTop,
// Indicates that we are labelling an X or Z axis on the "back" of
// the chart
reverseFlap = false, offsetX = 0, offsetY = 0, vecX, vecY = { x: 0, y: 1, z: 0 };
pos = axis.axis3D.swapZ({ x: pos.x, y: pos.y, z: 0 });
if (axis.isZAxis) { // Z Axis
if (axis.opposite) {
if (frame.axes.z.top === null) {
return {};
}
offsetY = pos.y - plotTop;
pos.x = frame.axes.z.top.x;
pos.y = frame.axes.z.top.y;
vecX = frame.axes.z.top.xDir;
reverseFlap = !frame.top.frontFacing;
}
else {
if (frame.axes.z.bottom === null) {
return {};
}
offsetY = pos.y - plotBottom;
pos.x = frame.axes.z.bottom.x;
pos.y = frame.axes.z.bottom.y;
vecX = frame.axes.z.bottom.xDir;
reverseFlap = !frame.bottom.frontFacing;
}
}
else if (axis.horiz) { // X Axis
if (axis.opposite) {
if (frame.axes.x.top === null) {
return {};
}
offsetY = pos.y - plotTop;
pos.y = frame.axes.x.top.y;
pos.z = frame.axes.x.top.z;
vecX = frame.axes.x.top.xDir;
reverseFlap = !frame.top.frontFacing;
}
else {
if (frame.axes.x.bottom === null) {
return {};
}
offsetY = pos.y - plotBottom;
pos.y = frame.axes.x.bottom.y;
pos.z = frame.axes.x.bottom.z;
vecX = frame.axes.x.bottom.xDir;
reverseFlap = !frame.bottom.frontFacing;
}
}
else { // Y Axis
if (axis.opposite) {
if (frame.axes.y.right === null) {
return {};
}
offsetX = pos.x - plotRight;
pos.x = frame.axes.y.right.x;
pos.z = frame.axes.y.right.z;
vecX = frame.axes.y.right.xDir;
// Rotate 90º on opposite edge
vecX = { x: vecX.z, y: vecX.y, z: -vecX.x };
}
else {
if (frame.axes.y.left === null) {
return {};
}
offsetX = pos.x - plotLeft;
pos.x = frame.axes.y.left.x;
pos.z = frame.axes.y.left.z;
vecX = frame.axes.y.left.xDir;
}
}
if (positionMode === 'chart') {
// Labels preserve their direction relative to the chart
// nothing to do
}
else if (positionMode === 'flap') {
// Labels are rotated around the axis direction to face the screen
if (!axis.horiz) { // Y Axis
vecX = { x: Math.cos(beta), y: 0, z: Math.sin(beta) };
}
else { // X and Z Axis
var sin = Math.sin(alpha);
var cos = Math.cos(alpha);
if (axis.opposite) {
sin = -sin;
}
if (reverseFlap) {
sin = -sin;
}
vecY = { x: vecX.z * sin, y: cos, z: -vecX.x * sin };
}
}
else if (positionMode === 'ortho') {
// Labels will be rotated to be ortogonal to the axis
if (!axis.horiz) { // Y Axis
vecX = { x: Math.cos(beta), y: 0, z: Math.sin(beta) };
}
else { // X and Z Axis
var sina = Math.sin(alpha);
var cosa = Math.cos(alpha);
var sinb = Math.sin(beta);
var cosb = Math.cos(beta);
var vecZ = { x: sinb * cosa, y: -sina, z: -cosa * cosb };
vecY = {
x: vecX.y * vecZ.z - vecX.z * vecZ.y,
y: vecX.z * vecZ.x - vecX.x * vecZ.z,
z: vecX.x * vecZ.y - vecX.y * vecZ.x
};
var scale = 1 / Math.sqrt(vecY.x * vecY.x + vecY.y * vecY.y + vecY.z * vecY.z);
if (reverseFlap) {
scale = -scale;
}
vecY = { x: scale * vecY.x, y: scale * vecY.y, z: scale * vecY.z };
}
}
else { // positionMode == 'offset'
// Labels will be skewd to maintain vertical / horizontal offsets
// from axis
if (!axis.horiz) { // Y Axis
vecX = { x: Math.cos(beta), y: 0, z: Math.sin(beta) };
}
else { // X and Z Axis
vecY = {
x: Math.sin(beta) * Math.sin(alpha),
y: Math.cos(alpha),
z: -Math.cos(beta) * Math.sin(alpha)
};
}
}
pos.x += offsetX * vecX.x + offsetY * vecY.x;
pos.y += offsetX * vecX.y + offsetY * vecY.y;
pos.z += offsetX * vecX.z + offsetY * vecY.z;
var projected = perspective([pos], axis.chart)[0];
if (skew) {
// Check if the label text would be mirrored
var isMirrored = shapeArea(perspective([
pos,
{ x: pos.x + vecX.x, y: pos.y + vecX.y, z: pos.z + vecX.z },
{ x: pos.x + vecY.x, y: pos.y + vecY.y, z: pos.z + vecY.z }
], axis.chart)) < 0;
if (isMirrored) {
vecX = { x: -vecX.x, y: -vecX.y, z: -vecX.z };
}
var pointsProjected = perspective([
{ x: pos.x, y: pos.y, z: pos.z },
{ x: pos.x + vecX.x, y: pos.y + vecX.y, z: pos.z + vecX.z },
{ x: pos.x + vecY.x, y: pos.y + vecY.y, z: pos.z + vecY.z }
], axis.chart);
projected.matrix = [
pointsProjected[1].x - pointsProjected[0].x,
pointsProjected[1].y - pointsProjected[0].y,
pointsProjected[2].x - pointsProjected[0].x,
pointsProjected[2].y - pointsProjected[0].y,
projected.x,
projected.y
];
projected.matrix[4] -= projected.x * projected.matrix[0] +
projected.y * projected.matrix[2];
projected.matrix[5] -= projected.x * projected.matrix[1] +
projected.y * projected.matrix[3];
}
return projected;
};
/**
* @private
*/
Axis3DAdditions.prototype.swapZ = function (p, insidePlotArea) {
var axis = this.axis;
if (axis.isZAxis) {
var plotLeft = insidePlotArea ? 0 : axis.chart.plotLeft;
return {
x: plotLeft + p.z,
y: p.y,
z: p.x - plotLeft
};
}
return p;
};
return Axis3DAdditions;
}());
/**
* Axis with 3D support.
* @private
* @class
*/
var Axis3D = /** @class */ (function () {
function Axis3D() {
}
/* *
*
* Static Functions
*
* */
/**
* Extends axis class with 3D support.
* @private
*/
Axis3D.compose = function (AxisClass) {
merge(true, AxisClass.defaultOptions, Axis3D.defaultOptions);
AxisClass.keepProps.push('axis3D');
addEvent(AxisClass, 'init', Axis3D.onInit);
addEvent(AxisClass, 'afterSetOptions', Axis3D.onAfterSetOptions);
addEvent(AxisClass, 'drawCrosshair', Axis3D.onDrawCrosshair);
addEvent(AxisClass, 'destroy', Axis3D.onDestroy);
var axisProto = AxisClass.prototype;
wrap(axisProto, 'getLinePath', Axis3D.wrapGetLinePath);
wrap(axisProto, 'getPlotBandPath', Axis3D.wrapGetPlotBandPath);
wrap(axisProto, 'getPlotLinePath', Axis3D.wrapGetPlotLinePath);
wrap(axisProto, 'getSlotWidth', Axis3D.wrapGetSlotWidth);
wrap(axisProto, 'getTitlePosition', Axis3D.wrapGetTitlePosition);
Tick3D.compose(Tick);
};
/**
* @private
*/
Axis3D.onAfterSetOptions = function () {
var axis = this;
var chart = axis.chart;
var options = axis.options;
if (chart.is3d && chart.is3d() && axis.coll !== 'colorAxis') {
options.tickWidth = pick(options.tickWidth, 0);
options.gridLineWidth = pick(options.gridLineWidth, 1);
}
};
/**
* @private
*/
Axis3D.onDestroy = function () {
['backFrame', 'bottomFrame', 'sideFrame'].forEach(function (prop) {
if (this[prop]) {
this[prop] = this[prop].destroy();
}
}, this);
};
/**
* @private
*/
Axis3D.onDrawCrosshair = function (e) {
var axis = this;
if (axis.chart.is3d() &&
axis.coll !== 'colorAxis') {
if (e.point) {
e.point.crosshairPos = axis.isXAxis ?
e.point.axisXpos :
axis.len - e.point.axisYpos;
}
}
};
/**
* @private
*/
Axis3D.onInit = function () {
var axis = this;
if (!axis.axis3D) {
axis.axis3D = new Axis3DAdditions(axis);
}
};
/**
* Do not draw axislines in 3D.
* @private
*/
Axis3D.wrapGetLinePath = function (proceed) {
var axis = this;
// Do not do this if the chart is not 3D
if (!axis.chart.is3d() || axis.coll === 'colorAxis') {
return proceed.apply(axis, [].slice.call(arguments, 1));
}
return [];
};
/**
* @private
*/
Axis3D.wrapGetPlotBandPath = function (proceed) {
// Do not do this if the chart is not 3D
if (!this.chart.is3d() || this.coll === 'colorAxis') {
return proceed.apply(this, [].slice.call(arguments, 1));
}
var args = arguments, from = args[1], to = args[2], path = [], fromPath = this.getPlotLinePath({ value: from }), toPath = this.getPlotLinePath({ value: to });
if (fromPath && toPath) {
for (var i = 0; i < fromPath.length; i += 2) {
var fromStartSeg = fromPath[i], fromEndSeg = fromPath[i + 1], toStartSeg = toPath[i], toEndSeg = toPath[i + 1];
if (fromStartSeg[0] === 'M' &&
fromEndSeg[0] === 'L' &&
toStartSeg[0] === 'M' &&
toEndSeg[0] === 'L') {
path.push(fromStartSeg, fromEndSeg, toEndSeg,
// lineTo instead of moveTo
['L', toStartSeg[1], toStartSeg[2]], ['Z']);
}
}
}
return path;
};
/**
* @private
*/
Axis3D.wrapGetPlotLinePath = function (proceed) {
var axis = this;
var axis3D = axis.axis3D;
var chart = axis.chart;
var path = proceed.apply(axis, [].slice.call(arguments, 1));
// Do not do this if the chart is not 3D
if (axis.coll === 'colorAxis' ||
!chart.chart3d ||
!chart.is3d()) {
return path;
}
if (path === null) {
return path;
}
var options3d = chart.options.chart.options3d, d = axis.isZAxis ? chart.plotWidth : options3d.depth, frame = chart.chart3d.frame3d, startSegment = path[0], endSegment = path[1], pArr, pathSegments = [];
if (startSegment[0] === 'M' && endSegment[0] === 'L') {
pArr = [
axis3D.swapZ({ x: startSegment[1], y: startSegment[2], z: 0 }),
axis3D.swapZ({ x: startSegment[1], y: startSegment[2], z: d }),
axis3D.swapZ({ x: endSegment[1], y: endSegment[2], z: 0 }),
axis3D.swapZ({ x: endSegment[1], y: endSegment[2], z: d })
];
if (!this.horiz) { // Y-Axis
if (frame.front.visible) {
pathSegments.push(pArr[0], pArr[2]);
}
if (frame.back.visible) {
pathSegments.push(pArr[1], pArr[3]);
}
if (frame.left.visible) {
pathSegments.push(pArr[0], pArr[1]);
}
if (frame.right.visible) {
pathSegments.push(pArr[2], pArr[3]);
}
}
else if (this.isZAxis) { // Z-Axis
if (frame.left.visible) {
pathSegments.push(pArr[0], pArr[2]);
}
if (frame.right.visible) {
pathSegments.push(pArr[1], pArr[3]);
}
if (frame.top.visible) {
pathSegments.push(pArr[0], pArr[1]);
}
if (frame.bottom.visible) {
pathSegments.push(pArr[2], pArr[3]);
}
}
else { // X-Axis
if (frame.front.visible) {
pathSegments.push(pArr[0], pArr[2]);
}
if (frame.back.visible) {
pathSegments.push(pArr[1], pArr[3]);
}
if (frame.top.visible) {
pathSegments.push(pArr[0], pArr[1]);
}
if (frame.bottom.visible) {
pathSegments.push(pArr[2], pArr[3]);
}
}
pathSegments = perspective(pathSegments, this.chart, false);
}
return chart.renderer.toLineSegments(pathSegments);
};
/**
* Wrap getSlotWidth function to calculate individual width value for each
* slot (#8042).
* @private
*/
Axis3D.wrapGetSlotWidth = function (proceed, tick) {
var axis = this;
var chart = axis.chart;
var ticks = axis.ticks;
var gridGroup = axis.gridGroup;
if (axis.categories &&
chart.frameShapes &&
chart.is3d() &&
gridGroup &&
tick &&
tick.label) {
var firstGridLine = gridGroup.element.childNodes[0].getBBox(), frame3DLeft = chart.frameShapes.left.getBBox(), options3d = chart.options.chart.options3d, origin = {
x: chart.plotWidth / 2,
y: chart.plotHeight / 2,
z: options3d.depth / 2,
vd: pick(options3d.depth, 1) * pick(options3d.viewDistance, 0)
}, labelPos, prevLabelPos, nextLabelPos, slotWidth, tickId = tick.pos, prevTick = ticks[tickId - 1], nextTick = ticks[tickId + 1];
// Check whether the tick is not the first one and previous tick
// exists, then calculate position of previous label.
if (tickId !== 0 && prevTick && prevTick.label.xy) {
prevLabelPos = perspective3D({
x: prevTick.label.xy.x,
y: prevTick.label.xy.y,
z: null
}, origin, origin.vd);
}
// If next label position is defined, then recalculate its position
// basing on the perspective.
if (nextTick && nextTick.label.xy) {
nextLabelPos = perspective3D({
x: nextTick.label.xy.x,
y: nextTick.label.xy.y,
z: null
}, origin, origin.vd);
}
labelPos = {
x: tick.label.xy.x,
y: tick.label.xy.y,
z: null
};
labelPos = perspective3D(labelPos, origin, origin.vd);
// If tick is first one, check whether next label position is
// already calculated, then return difference between the first and
// the second label. If there is no next label position calculated,
// return the difference between the first grid line and left 3d
// frame.
slotWidth = Math.abs(prevLabelPos ?
labelPos.x - prevLabelPos.x : nextLabelPos ?
nextLabelPos.x - labelPos.x :
firstGridLine.x - frame3DLeft.x);
return slotWidth;
}
return proceed.apply(axis, [].slice.call(arguments, 1));
};
/**
* @private
*/
Axis3D.wrapGetTitlePosition = function (proceed) {
var pos = proceed.apply(this, [].slice.call(arguments, 1));
return this.axis3D ?
this.axis3D.fix3dPosition(pos, true) :
pos;
};
/* *
*
* Static Properties
*
* */
/**
* @optionparent xAxis
*/
Axis3D.defaultOptions = {
labels: {
/**
* Defines how the labels are be repositioned according to the 3D
* chart orientation.
*
* - `'offset'`: Maintain a fixed horizontal/vertical distance from
* the tick marks, despite the chart orientation. This is the
* backwards compatible behavior, and causes skewing of X and Z
* axes.
*
* - `'chart'`: Preserve 3D position relative to the chart. This
* looks nice, but hard to read if the text isn't forward-facing.
*
* - `'flap'`: Rotated text along the axis to compensate for the
* chart orientation. This tries to maintain text as legible as
* possible on all orientations.
*
* - `'ortho'`: Rotated text along the axis direction so that the
* labels are orthogonal to the axis. This is very similar to
* `'flap'`, but prevents skewing the labels (X and Y scaling are
* still present).
*
* @sample highcharts/3d/skewed-labels/
* Skewed labels
*
* @since 5.0.15
* @validvalue ['offset', 'chart', 'flap', 'ortho']
* @product highcharts
* @requires highcharts-3d
*/
position3d: 'offset',
/**
* If enabled, the axis labels will skewed to follow the
* perspective.
*
* This will fix overlapping labels and titles, but texts become
* less legible due to the distortion.
*
* The final appearance depends heavily on `labels.position3d`.
*
* @sample highcharts/3d/skewed-labels/
* Skewed labels
*
* @since 5.0.15
* @product highcharts
* @requires highcharts-3d
*/
skew3d: false
},
title: {
/**
* Defines how the title is repositioned according to the 3D chart
* orientation.
*
* - `'offset'`: Maintain a fixed horizontal/vertical distance from
* the tick marks, despite the chart orientation. This is the
* backwards compatible behavior, and causes skewing of X and Z
* axes.
*
* - `'chart'`: Preserve 3D position relative to the chart. This
* looks nice, but hard to read if the text isn't forward-facing.
*
* - `'flap'`: Rotated text along the axis to compensate for the
* chart orientation. This tries to maintain text as legible as
* possible on all orientations.
*
* - `'ortho'`: Rotated text along the axis direction so that the
* labels are orthogonal to the axis. This is very similar to
* `'flap'`, but prevents skewing the labels (X and Y scaling are
* still present).
*
* - `undefined`: Will use the config from `labels.position3d`
*
* @sample highcharts/3d/skewed-labels/
* Skewed labels
*
* @type {"offset"|"chart"|"flap"|"ortho"|null}
* @since 5.0.15
* @product highcharts
* @requires highcharts-3d
*/
position3d: null,
/**
* If enabled, the axis title will skewed to follow the perspective.
*
* This will fix overlapping labels and titles, but texts become
* less legible due to the distortion.
*
* The final appearance depends heavily on `title.position3d`.
*
* A `null` value will use the config from `labels.skew3d`.
*
* @sample highcharts/3d/skewed-labels/
* Skewed labels
*
* @type {boolean|null}
* @since 5.0.15
* @product highcharts
* @requires highcharts-3d
*/
skew3d: null
}
};
return Axis3D;
}());
export default Axis3D;
@@ -0,0 +1,534 @@
/* *
*
* (c) 2009-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Axis from './Axis.js';
import H from '../Globals.js';
import U from '../Utilities.js';
var addEvent = U.addEvent, find = U.find, fireEvent = U.fireEvent, isArray = U.isArray, isNumber = U.isNumber, pick = U.pick;
import '../Series/Series.js';
import StackItem from '../../Extensions/Stacking.js';
var Series = H.Series;
/* eslint-disable valid-jsdoc */
/**
* Provides support for broken axes.
* @private
* @class
*/
var BrokenAxisAdditions = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function BrokenAxisAdditions(axis) {
this.hasBreaks = false;
this.axis = axis;
}
/* *
*
* Static Functions
*
* */
/**
* @private
*/
BrokenAxisAdditions.isInBreak = function (brk, val) {
var ret, repeat = brk.repeat || Infinity, from = brk.from, length = brk.to - brk.from, test = (val >= from ?
(val - from) % repeat :
repeat - ((from - val) % repeat));
if (!brk.inclusive) {
ret = test < length && test !== 0;
}
else {
ret = test <= length;
}
return ret;
};
/**
* @private
*/
BrokenAxisAdditions.lin2Val = function (val) {
var axis = this;
var brokenAxis = axis.brokenAxis;
var breakArray = brokenAxis && brokenAxis.breakArray;
if (!breakArray) {
return val;
}
var nval = val, brk, i;
for (i = 0; i < breakArray.length; i++) {
brk = breakArray[i];
if (brk.from >= nval) {
break;
}
else if (brk.to < nval) {
nval += brk.len;
}
else if (BrokenAxisAdditions.isInBreak(brk, nval)) {
nval += brk.len;
}
}
return nval;
};
/**
* @private
*/
BrokenAxisAdditions.val2Lin = function (val) {
var axis = this;
var brokenAxis = axis.brokenAxis;
var breakArray = brokenAxis && brokenAxis.breakArray;
if (!breakArray) {
return val;
}
var nval = val, brk, i;
for (i = 0; i < breakArray.length; i++) {
brk = breakArray[i];
if (brk.to <= val) {
nval -= brk.len;
}
else if (brk.from >= val) {
break;
}
else if (BrokenAxisAdditions.isInBreak(brk, val)) {
nval -= (val - brk.from);
break;
}
}
return nval;
};
/* *
*
* Functions
*
* */
/**
* Returns the first break found where the x is larger then break.from and
* smaller then break.to.
*
* @param {number} x
* The number which should be within a break.
*
* @param {Array<Highcharts.XAxisBreaksOptions>} breaks
* The array of breaks to search within.
*
* @return {Highcharts.XAxisBreaksOptions|undefined}
* Returns the first break found that matches, returns false if no break is
* found.
*/
BrokenAxisAdditions.prototype.findBreakAt = function (x, breaks) {
return find(breaks, function (b) {
return b.from < x && x < b.to;
});
};
/**
* @private
*/
BrokenAxisAdditions.prototype.isInAnyBreak = function (val, testKeep) {
var brokenAxis = this;
var axis = brokenAxis.axis;
var breaks = axis.options.breaks, i = breaks && breaks.length, inbrk, keep, ret;
if (i) {
while (i--) {
if (BrokenAxisAdditions.isInBreak(breaks[i], val)) {
inbrk = true;
if (!keep) {
keep = pick(breaks[i].showPoints, !axis.isXAxis);
}
}
}
if (inbrk && testKeep) {
ret = inbrk && !keep;
}
else {
ret = inbrk;
}
}
return ret;
};
/**
* Dynamically set or unset breaks in an axis. This function in lighter than
* usin Axis.update, and it also preserves animation.
*
* @private
* @function Highcharts.Axis#setBreaks
*
* @param {Array<Highcharts.XAxisBreaksOptions>} [breaks]
* The breaks to add. When `undefined` it removes existing breaks.
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart immediately.
*
* @return {void}
*/
BrokenAxisAdditions.prototype.setBreaks = function (breaks, redraw) {
var brokenAxis = this;
var axis = brokenAxis.axis;
var hasBreaks = (isArray(breaks) && !!breaks.length);
axis.isDirty = brokenAxis.hasBreaks !== hasBreaks;
brokenAxis.hasBreaks = hasBreaks;
axis.options.breaks = axis.userOptions.breaks = breaks;
axis.forceRedraw = true; // Force recalculation in setScale
// Recalculate series related to the axis.
axis.series.forEach(function (series) {
series.isDirty = true;
});
if (!hasBreaks && axis.val2lin === BrokenAxisAdditions.val2Lin) {
// Revert to prototype functions
delete axis.val2lin;
delete axis.lin2val;
}
if (hasBreaks) {
axis.userOptions.ordinal = false;
axis.lin2val = BrokenAxisAdditions.lin2Val;
axis.val2lin = BrokenAxisAdditions.val2Lin;
axis.setExtremes = function (newMin, newMax, redraw, animation, eventArguments) {
// If trying to set extremes inside a break, extend min to
// after, and max to before the break ( #3857 )
if (brokenAxis.hasBreaks) {
var axisBreak, breaks = this.options.breaks;
while ((axisBreak = brokenAxis.findBreakAt(newMin, breaks))) {
newMin = axisBreak.to;
}
while ((axisBreak = brokenAxis.findBreakAt(newMax, breaks))) {
newMax = axisBreak.from;
}
// If both min and max is within the same break.
if (newMax < newMin) {
newMax = newMin;
}
}
Axis.prototype.setExtremes.call(this, newMin, newMax, redraw, animation, eventArguments);
};
axis.setAxisTranslation = function (saveOld) {
Axis.prototype.setAxisTranslation.call(this, saveOld);
brokenAxis.unitLength = null;
if (brokenAxis.hasBreaks) {
var breaks = axis.options.breaks || [],
// Temporary one:
breakArrayT = [], breakArray = [], length = 0, inBrk, repeat, min = axis.userMin || axis.min, max = axis.userMax || axis.max, pointRangePadding = pick(axis.pointRangePadding, 0), start, i;
// Min & max check (#4247)
breaks.forEach(function (brk) {
repeat = brk.repeat || Infinity;
if (BrokenAxisAdditions.isInBreak(brk, min)) {
min +=
(brk.to % repeat) -
(min % repeat);
}
if (BrokenAxisAdditions.isInBreak(brk, max)) {
max -=
(max % repeat) -
(brk.from % repeat);
}
});
// Construct an array holding all breaks in the axis
breaks.forEach(function (brk) {
start = brk.from;
repeat = brk.repeat || Infinity;
while (start - repeat > min) {
start -= repeat;
}
while (start < min) {
start += repeat;
}
for (i = start; i < max; i += repeat) {
breakArrayT.push({
value: i,
move: 'in'
});
breakArrayT.push({
value: i + (brk.to - brk.from),
move: 'out',
size: brk.breakSize
});
}
});
breakArrayT.sort(function (a, b) {
return ((a.value === b.value) ?
((a.move === 'in' ? 0 : 1) -
(b.move === 'in' ? 0 : 1)) :
a.value - b.value);
});
// Simplify the breaks
inBrk = 0;
start = min;
breakArrayT.forEach(function (brk) {
inBrk += (brk.move === 'in' ? 1 : -1);
if (inBrk === 1 && brk.move === 'in') {
start = brk.value;
}
if (inBrk === 0) {
breakArray.push({
from: start,
to: brk.value,
len: brk.value - start - (brk.size || 0)
});
length += brk.value - start - (brk.size || 0);
}
});
/**
* HC <= 8 backwards compatibility, used by demo samples.
* @deprecated
* @private
* @requires modules/broken-axis
*/
axis.breakArray = brokenAxis.breakArray = breakArray;
// Used with staticScale, and below the actual axis length,
// when breaks are substracted.
brokenAxis.unitLength = max - min - length + pointRangePadding;
fireEvent(axis, 'afterBreaks');
if (axis.staticScale) {
axis.transA = axis.staticScale;
}
else if (brokenAxis.unitLength) {
axis.transA *=
(max - axis.min + pointRangePadding) /
brokenAxis.unitLength;
}
if (pointRangePadding) {
axis.minPixelPadding =
axis.transA * axis.minPointOffset;
}
axis.min = min;
axis.max = max;
}
};
}
if (pick(redraw, true)) {
axis.chart.redraw();
}
};
return BrokenAxisAdditions;
}());
/**
* Axis with support of broken data rows.
* @private
* @class
*/
var BrokenAxis = /** @class */ (function () {
function BrokenAxis() {
}
/**
* Adds support for broken axes.
* @private
*/
BrokenAxis.compose = function (AxisClass, SeriesClass) {
AxisClass.keepProps.push('brokenAxis');
var seriesProto = Series.prototype;
/**
* @private
*/
seriesProto.drawBreaks = function (axis, keys) {
var series = this, points = series.points, breaks, threshold, eventName, y;
if (axis && // #5950
axis.brokenAxis &&
axis.brokenAxis.hasBreaks) {
var brokenAxis_1 = axis.brokenAxis;
keys.forEach(function (key) {
breaks = brokenAxis_1 && brokenAxis_1.breakArray || [];
threshold = axis.isXAxis ?
axis.min :
pick(series.options.threshold, axis.min);
points.forEach(function (point) {
y = pick(point['stack' + key.toUpperCase()], point[key]);
breaks.forEach(function (brk) {
if (isNumber(threshold) && isNumber(y)) {
eventName = false;
if ((threshold < brk.from && y > brk.to) ||
(threshold > brk.from && y < brk.from)) {
eventName = 'pointBreak';
}
else if ((threshold < brk.from && y > brk.from && y < brk.to) ||
(threshold > brk.from && y > brk.to && y < brk.from)) {
eventName = 'pointInBreak';
}
if (eventName) {
fireEvent(axis, eventName, { point: point, brk: brk });
}
}
});
});
});
}
};
/**
* Extend getGraphPath by identifying gaps in the data so that we can
* draw a gap in the line or area. This was moved from ordinal axis
* module to broken axis module as of #5045.
*
* @private
* @function Highcharts.Series#gappedPath
*
* @return {Highcharts.SVGPathArray}
* Gapped path
*/
seriesProto.gappedPath = function () {
var currentDataGrouping = this.currentDataGrouping, groupingSize = currentDataGrouping && currentDataGrouping.gapSize, gapSize = this.options.gapSize, points = this.points.slice(), i = points.length - 1, yAxis = this.yAxis, stack;
/**
* Defines when to display a gap in the graph, together with the
* [gapUnit](plotOptions.series.gapUnit) option.
*
* In case when `dataGrouping` is enabled, points can be grouped
* into a larger time span. This can make the grouped points to have
* a greater distance than the absolute value of `gapSize` property,
* which will result in disappearing graph completely. To prevent
* this situation the mentioned distance between grouped points is
* used instead of previously defined `gapSize`.
*
* In practice, this option is most often used to visualize gaps in
* time series. In a stock chart, intraday data is available for
* daytime hours, while gaps will appear in nights and weekends.
*
* @see [gapUnit](plotOptions.series.gapUnit)
* @see [xAxis.breaks](#xAxis.breaks)
*
* @sample {highstock} stock/plotoptions/series-gapsize/
* Setting the gap size to 2 introduces gaps for weekends
* in daily datasets.
*
* @type {number}
* @default 0
* @product highstock
* @requires modules/broken-axis
* @apioption plotOptions.series.gapSize
*/
/**
* Together with [gapSize](plotOptions.series.gapSize), this option
* defines where to draw gaps in the graph.
*
* When the `gapUnit` is `"relative"` (default), a gap size of 5
* means that if the distance between two points is greater than
* 5 times that of the two closest points, the graph will be broken.
*
* When the `gapUnit` is `"value"`, the gap is based on absolute
* axis values, which on a datetime axis is milliseconds. This also
* applies to the navigator series that inherits gap options from
* the base series.
*
* @see [gapSize](plotOptions.series.gapSize)
*
* @type {string}
* @default relative
* @since 5.0.13
* @product highstock
* @validvalue ["relative", "value"]
* @requires modules/broken-axis
* @apioption plotOptions.series.gapUnit
*/
if (gapSize && i > 0) { // #5008
// Gap unit is relative
if (this.options.gapUnit !== 'value') {
gapSize *= this.basePointRange;
}
// Setting a new gapSize in case dataGrouping is enabled (#7686)
if (groupingSize &&
groupingSize > gapSize &&
// Except when DG is forced (e.g. from other series)
// and has lower granularity than actual points (#11351)
groupingSize >= this.basePointRange) {
gapSize = groupingSize;
}
// extension for ordinal breaks
var current = void 0, next = void 0;
while (i--) {
// Reassign next if it is not visible
if (!(next && next.visible !== false)) {
next = points[i + 1];
}
current = points[i];
// Skip iteration if one of the points is not visible
if (next.visible === false || current.visible === false) {
continue;
}
if (next.x - current.x > gapSize) {
var xRange = (current.x + next.x) / 2;
points.splice(// insert after this one
i + 1, 0, {
isNull: true,
x: xRange
});
// For stacked chart generate empty stack items, #6546
if (yAxis.stacking && this.options.stacking) {
stack = yAxis.stacking.stacks[this.stackKey][xRange] =
new StackItem(yAxis, yAxis.options
.stackLabels, false, xRange, this.stack);
stack.total = 0;
}
}
// Assign current to next for the upcoming iteration
next = current;
}
}
// Call base method
return this.getGraphPath(points);
};
/* eslint-disable no-invalid-this */
addEvent(AxisClass, 'init', function () {
var axis = this;
if (!axis.brokenAxis) {
axis.brokenAxis = new BrokenAxisAdditions(axis);
}
});
addEvent(AxisClass, 'afterInit', function () {
if (typeof this.brokenAxis !== 'undefined') {
this.brokenAxis.setBreaks(this.options.breaks, false);
}
});
addEvent(AxisClass, 'afterSetTickPositions', function () {
var axis = this;
var brokenAxis = axis.brokenAxis;
if (brokenAxis &&
brokenAxis.hasBreaks) {
var tickPositions = this.tickPositions, info = this.tickPositions.info, newPositions = [], i;
for (i = 0; i < tickPositions.length; i++) {
if (!brokenAxis.isInAnyBreak(tickPositions[i])) {
newPositions.push(tickPositions[i]);
}
}
this.tickPositions = newPositions;
this.tickPositions.info = info;
}
});
// Force Axis to be not-ordinal when breaks are defined
addEvent(AxisClass, 'afterSetOptions', function () {
if (this.brokenAxis && this.brokenAxis.hasBreaks) {
this.options.ordinal = false;
}
});
addEvent(SeriesClass, 'afterGeneratePoints', function () {
var _a = this, isDirty = _a.isDirty, connectNulls = _a.options.connectNulls, points = _a.points, xAxis = _a.xAxis, yAxis = _a.yAxis;
// Set, or reset visibility of the points. Axis.setBreaks marks the
// series as isDirty
if (isDirty) {
var i = points.length;
while (i--) {
var point = points[i];
// Respect nulls inside the break (#4275)
var nullGap = point.y === null && connectNulls === false;
var isPointInBreak = (!nullGap && ((xAxis &&
xAxis.brokenAxis &&
xAxis.brokenAxis.isInAnyBreak(point.x, true)) || (yAxis &&
yAxis.brokenAxis &&
yAxis.brokenAxis.isInAnyBreak(point.y, true))));
// Set point.visible if in any break.
// If not in break, reset visible to original value.
point.visible = isPointInBreak ?
false :
point.options.visible !== false;
}
}
});
addEvent(SeriesClass, 'afterRender', function drawPointsWrapped() {
this.drawBreaks(this.xAxis, ['x']);
this.drawBreaks(this.yAxis, pick(this.pointArrayMap, ['y']));
});
};
return BrokenAxis;
}());
BrokenAxis.compose(Axis, Series); // @todo remove automatism
export default BrokenAxis;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,177 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Axis from './Axis.js';
import U from '../Utilities.js';
var addEvent = U.addEvent, getMagnitude = U.getMagnitude, normalizeTickInterval = U.normalizeTickInterval, timeUnits = U.timeUnits;
/* eslint-disable valid-jsdoc */
var DateTimeAxisAdditions = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function DateTimeAxisAdditions(axis) {
this.axis = axis;
}
/* *
*
* Functions
*
* */
/**
* Get a normalized tick interval for dates. Returns a configuration object
* with unit range (interval), count and name. Used to prepare data for
* `getTimeTicks`. Previously this logic was part of getTimeTicks, but as
* `getTimeTicks` now runs of segments in stock charts, the normalizing
* logic was extracted in order to prevent it for running over again for
* each segment having the same interval. #662, #697.
* @private
*/
/**
* Get a normalized tick interval for dates. Returns a configuration object
* with unit range (interval), count and name. Used to prepare data for
* `getTimeTicks`. Previously this logic was part of getTimeTicks, but as
* `getTimeTicks` now runs of segments in stock charts, the normalizing
* logic was extracted in order to prevent it for running over again for
* each segment having the same interval. #662, #697.
* @private
*/
DateTimeAxisAdditions.prototype.normalizeTimeTickInterval = function (tickInterval, unitsOption) {
var units = unitsOption || [[
'millisecond',
[1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
], [
'second',
[1, 2, 5, 10, 15, 30]
], [
'minute',
[1, 2, 5, 10, 15, 30]
], [
'hour',
[1, 2, 3, 4, 6, 8, 12]
], [
'day',
[1, 2]
], [
'week',
[1, 2]
], [
'month',
[1, 2, 3, 4, 6]
], [
'year',
null
]], unit = units[units.length - 1], // default unit is years
interval = timeUnits[unit[0]], multiples = unit[1], count, i;
// loop through the units to find the one that best fits the
// tickInterval
for (i = 0; i < units.length; i++) {
unit = units[i];
interval = timeUnits[unit[0]];
multiples = unit[1];
if (units[i + 1]) {
// lessThan is in the middle between the highest multiple and
// the next unit.
var lessThan = (interval *
multiples[multiples.length - 1] +
timeUnits[units[i + 1][0]]) / 2;
// break and keep the current unit
if (tickInterval <= lessThan) {
break;
}
}
}
// prevent 2.5 years intervals, though 25, 250 etc. are allowed
if (interval === timeUnits.year && tickInterval < 5 * interval) {
multiples = [1, 2, 5];
}
// get the count
count = normalizeTickInterval(tickInterval / interval, multiples, unit[0] === 'year' ? // #1913, #2360
Math.max(getMagnitude(tickInterval / interval), 1) :
1);
return {
unitRange: interval,
count: count,
unitName: unit[0]
};
};
return DateTimeAxisAdditions;
}());
/**
* Date and time support for axes.
*
* @private
* @class
*/
var DateTimeAxis = /** @class */ (function () {
function DateTimeAxis() {
}
/* *
*
* Static Functions
*
* */
/**
* Extends axis class with date and time support.
* @private
*/
DateTimeAxis.compose = function (AxisClass) {
AxisClass.keepProps.push('dateTime');
var axisProto = AxisClass.prototype;
/**
* Set the tick positions to a time unit that makes sense, for example
* on the first of each month or on every Monday. Return an array with
* the time positions. Used in datetime axes as well as for grouping
* data on a datetime axis.
*
* @private
* @function Highcharts.Axis#getTimeTicks
*
* @param {Highcharts.TimeNormalizeObject} normalizedInterval
* The interval in axis values (ms) and thecount.
*
* @param {number} min
* The minimum in axis values.
*
* @param {number} max
* The maximum in axis values.
*
* @param {number} startOfWeek
*
* @return {Highcharts.AxisTickPositionsArray}
*/
axisProto.getTimeTicks = function () {
return this.chart.time.getTimeTicks.apply(this.chart.time, arguments);
};
/* eslint-disable no-invalid-this */
addEvent(AxisClass, 'init', function (e) {
var axis = this;
var options = e.userOptions;
if (options.type !== 'datetime') {
axis.dateTime = void 0;
return;
}
if (!axis.dateTime) {
axis.dateTime = new DateTimeAxisAdditions(axis);
}
});
/* eslint-enable no-invalid-this */
};
/* *
*
* Static Properties
*
* */
DateTimeAxis.AdditionsClass = DateTimeAxisAdditions;
return DateTimeAxis;
}());
DateTimeAxis.compose(Axis);
export default DateTimeAxis;
+884
View File
@@ -0,0 +1,884 @@
/* *
*
* (c) 2016 Highsoft AS
* Authors: Lars A. V. Cabrera
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Axis from './Axis.js';
import H from '../Globals.js';
import O from '../Options.js';
var dateFormat = O.dateFormat;
import Tick from './Tick.js';
import U from '../Utilities.js';
var addEvent = U.addEvent, defined = U.defined, erase = U.erase, find = U.find, isArray = U.isArray, isNumber = U.isNumber, merge = U.merge, pick = U.pick, timeUnits = U.timeUnits, wrap = U.wrap;
var argsToArray = function (args) {
return Array.prototype.slice.call(args, 1);
}, isObject = function (x) {
// Always use strict mode
return U.isObject(x, true);
}, Chart = H.Chart;
var applyGridOptions = function applyGridOptions(axis) {
var options = axis.options;
// Center-align by default
if (!options.labels) {
options.labels = {};
}
options.labels.align = pick(options.labels.align, 'center');
// @todo: Check against tickLabelPlacement between/on etc
/* Prevents adding the last tick label if the axis is not a category
axis.
Since numeric labels are normally placed at starts and ends of a
range of value, and this module makes the label point at the value,
an "extra" label would appear. */
if (!axis.categories) {
options.showLastLabel = false;
}
// Prevents rotation of labels when squished, as rotating them would not
// help.
axis.labelRotation = 0;
options.labels.rotation = 0;
};
/**
* For a datetime axis, the scale will automatically adjust to the
* appropriate unit. This member gives the default string
* representations used for each unit. For intermediate values,
* different units may be used, for example the `day` unit can be used
* on midnight and `hour` unit be used for intermediate values on the
* same axis.
* For grid axes (like in Gantt charts),
* it is possible to declare as a list to provide different
* formats depending on available space.
* For an overview of the replacement codes, see
* [dateFormat](/class-reference/Highcharts#dateFormat).
*
* Defaults to:
* ```js
* {
hour: {
list: ['%H:%M', '%H']
},
day: {
list: ['%A, %e. %B', '%a, %e. %b', '%E']
},
week: {
list: ['Week %W', 'W%W']
},
month: {
list: ['%B', '%b', '%o']
}
},
* ```
*
* @sample {gantt} gantt/demo/left-axis-table
* Gantt Chart with custom axis date format.
*
* @product gantt
* @apioption xAxis.dateTimeLabelFormats
*/
/**
* Set grid options for the axis labels. Requires Highcharts Gantt.
*
* @since 6.2.0
* @product gantt
* @apioption xAxis.grid
*/
/**
* Enable grid on the axis labels. Defaults to true for Gantt charts.
*
* @type {boolean}
* @default true
* @since 6.2.0
* @product gantt
* @apioption xAxis.grid.enabled
*/
/**
* Set specific options for each column (or row for horizontal axes) in the
* grid. Each extra column/row is its own axis, and the axis options can be set
* here.
*
* @sample gantt/demo/left-axis-table
* Left axis as a table
*
* @type {Array<Highcharts.XAxisOptions>}
* @apioption xAxis.grid.columns
*/
/**
* Set border color for the label grid lines.
*
* @type {Highcharts.ColorString}
* @apioption xAxis.grid.borderColor
*/
/**
* Set border width of the label grid lines.
*
* @type {number}
* @default 1
* @apioption xAxis.grid.borderWidth
*/
/**
* Set cell height for grid axis labels. By default this is calculated from font
* size. This option only applies to horizontal axes.
*
* @sample gantt/grid-axis/cellheight
* Gant chart with custom cell height
* @type {number}
* @apioption xAxis.grid.cellHeight
*/
''; // detach doclets above
/**
* Get the largest label width and height.
*
* @private
* @function Highcharts.Axis#getMaxLabelDimensions
*
* @param {Highcharts.Dictionary<Highcharts.Tick>} ticks
* All the ticks on one axis.
*
* @param {Array<number|string>} tickPositions
* All the tick positions on one axis.
*
* @return {Highcharts.SizeObject}
* Object containing the properties height and width.
*
* @todo Move this to the generic axis implementation, as it is used there.
*/
Axis.prototype.getMaxLabelDimensions = function (ticks, tickPositions) {
var dimensions = {
width: 0,
height: 0
};
tickPositions.forEach(function (pos) {
var tick = ticks[pos], tickHeight = 0, tickWidth = 0, label;
if (isObject(tick)) {
label = isObject(tick.label) ? tick.label : {};
// Find width and height of tick
tickHeight = label.getBBox ? label.getBBox().height : 0;
if (label.textStr) {
// Set the tickWidth same as the label width after ellipsis
// applied #10281
tickWidth = Math.round(label.getBBox().width);
}
// Update the result if width and/or height are larger
dimensions.height = Math.max(tickHeight, dimensions.height);
dimensions.width = Math.max(tickWidth, dimensions.width);
}
});
return dimensions;
};
// Adds week date format
H.dateFormats.W = function (timestamp) {
var d = new this.Date(timestamp);
var firstDay = (this.get('Day', d) + 6) % 7;
var thursday = new this.Date(d.valueOf());
this.set('Date', thursday, this.get('Date', d) - firstDay + 3);
var firstThursday = new this.Date(this.get('FullYear', thursday), 0, 1);
if (this.get('Day', firstThursday) !== 4) {
this.set('Month', d, 0);
this.set('Date', d, 1 + (11 - this.get('Day', firstThursday)) % 7);
}
return (1 +
Math.floor((thursday.valueOf() - firstThursday.valueOf()) / 604800000)).toString();
};
// First letter of the day of the week, e.g. 'M' for 'Monday'.
H.dateFormats.E = function (timestamp) {
return dateFormat('%a', timestamp, true).charAt(0);
};
/* eslint-disable no-invalid-this */
addEvent(Chart, 'afterSetChartSize', function () {
this.axes.forEach(function (axis) {
(axis.grid && axis.grid.columns || []).forEach(function (column) {
column.setAxisSize();
column.setAxisTranslation();
});
});
});
// Center tick labels in cells.
addEvent(Tick, 'afterGetLabelPosition', function (e) {
var tick = this, label = tick.label, axis = tick.axis, reversed = axis.reversed, chart = axis.chart, options = axis.options, gridOptions = options.grid || {}, labelOpts = axis.options.labels, align = labelOpts.align,
// verticalAlign is currently not supported for axis.labels.
verticalAlign = 'middle', // labelOpts.verticalAlign,
side = GridAxis.Side[axis.side], tickmarkOffset = e.tickmarkOffset, tickPositions = axis.tickPositions, tickPos = tick.pos - tickmarkOffset, nextTickPos = (isNumber(tickPositions[e.index + 1]) ?
tickPositions[e.index + 1] - tickmarkOffset :
axis.max + tickmarkOffset), tickSize = axis.tickSize('tick'), tickWidth = tickSize ? tickSize[0] : 0, crispCorr = tickSize ? tickSize[1] / 2 : 0, labelHeight, lblMetrics, lines, bottom, top, left, right;
// Only center tick labels in grid axes
if (gridOptions.enabled === true) {
// Calculate top and bottom positions of the cell.
if (side === 'top') {
bottom = axis.top + axis.offset;
top = bottom - tickWidth;
}
else if (side === 'bottom') {
top = chart.chartHeight - axis.bottom + axis.offset;
bottom = top + tickWidth;
}
else {
bottom = axis.top + axis.len - axis.translate(reversed ? nextTickPos : tickPos);
top = axis.top + axis.len - axis.translate(reversed ? tickPos : nextTickPos);
}
// Calculate left and right positions of the cell.
if (side === 'right') {
left = chart.chartWidth - axis.right + axis.offset;
right = left + tickWidth;
}
else if (side === 'left') {
right = axis.left + axis.offset;
left = right - tickWidth;
}
else {
left = Math.round(axis.left + axis.translate(reversed ? nextTickPos : tickPos)) - crispCorr;
right = Math.round(axis.left + axis.translate(reversed ? tickPos : nextTickPos)) - crispCorr;
}
tick.slotWidth = right - left;
// Calculate the positioning of the label based on
// alignment.
e.pos.x = (align === 'left' ?
left :
align === 'right' ?
right :
left + ((right - left) / 2) // default to center
);
e.pos.y = (verticalAlign === 'top' ?
top :
verticalAlign === 'bottom' ?
bottom :
top + ((bottom - top) / 2) // default to middle
);
lblMetrics = chart.renderer.fontMetrics(labelOpts.style.fontSize, label.element);
labelHeight = label.getBBox().height;
// Adjustment to y position to align the label correctly.
// Would be better to have a setter or similar for this.
if (!labelOpts.useHTML) {
lines = Math.round(labelHeight / lblMetrics.h);
e.pos.y += (
// Center the label
// TODO: why does this actually center the label?
((lblMetrics.b - (lblMetrics.h - lblMetrics.f)) / 2) +
// Adjust for height of additional lines.
-(((lines - 1) * lblMetrics.h) / 2));
}
else {
e.pos.y += (
// Readjust yCorr in htmlUpdateTransform
lblMetrics.b +
// Adjust for height of html label
-(labelHeight / 2));
}
e.pos.x += (axis.horiz && labelOpts.x || 0);
}
});
/* eslint-enable no-invalid-this */
/**
* Additions for grid axes.
* @private
* @class
*/
var GridAxisAdditions = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function GridAxisAdditions(axis) {
this.axis = axis;
}
/* *
*
* Functions
*
* */
/**
* Checks if an axis is the outer axis in its dimension. Since
* axes are placed outwards in order, the axis with the highest
* index is the outermost axis.
*
* Example: If there are multiple x-axes at the top of the chart,
* this function returns true if the axis supplied is the last
* of the x-axes.
*
* @private
*
* @return {boolean}
* True if the axis is the outermost axis in its dimension; false if
* not.
*/
GridAxisAdditions.prototype.isOuterAxis = function () {
var axis = this.axis;
var chart = axis.chart;
var columnIndex = axis.grid.columnIndex;
var columns = (axis.linkedParent && axis.linkedParent.grid.columns ||
axis.grid.columns);
var parentAxis = columnIndex ? axis.linkedParent : axis;
var thisIndex = -1, lastIndex = 0;
chart[axis.coll].forEach(function (otherAxis, index) {
if (otherAxis.side === axis.side && !otherAxis.options.isInternal) {
lastIndex = index;
if (otherAxis === parentAxis) {
// Get the index of the axis in question
thisIndex = index;
}
}
});
return (lastIndex === thisIndex &&
(isNumber(columnIndex) ? columns.length === columnIndex : true));
};
return GridAxisAdditions;
}());
/**
* Axis with grid support.
* @private
* @class
*/
var GridAxis = /** @class */ (function () {
function GridAxis() {
}
/* *
*
* Static Functions
*
* */
/* eslint-disable valid-jsdoc */
/**
* Extends axis class with grid support.
* @private
*/
GridAxis.compose = function (AxisClass) {
Axis.keepProps.push('grid');
wrap(AxisClass.prototype, 'unsquish', GridAxis.wrapUnsquish);
// Add event handlers
addEvent(AxisClass, 'init', GridAxis.onInit);
addEvent(AxisClass, 'afterGetOffset', GridAxis.onAfterGetOffset);
addEvent(AxisClass, 'afterGetTitlePosition', GridAxis.onAfterGetTitlePosition);
addEvent(AxisClass, 'afterInit', GridAxis.onAfterInit);
addEvent(AxisClass, 'afterRender', GridAxis.onAfterRender);
addEvent(AxisClass, 'afterSetAxisTranslation', GridAxis.onAfterSetAxisTranslation);
addEvent(AxisClass, 'afterSetOptions', GridAxis.onAfterSetOptions);
addEvent(AxisClass, 'afterSetOptions', GridAxis.onAfterSetOptions2);
addEvent(AxisClass, 'afterSetScale', GridAxis.onAfterSetScale);
addEvent(AxisClass, 'afterTickSize', GridAxis.onAfterTickSize);
addEvent(AxisClass, 'trimTicks', GridAxis.onTrimTicks);
addEvent(AxisClass, 'destroy', GridAxis.onDestroy);
};
/**
* Handle columns and getOffset.
* @private
*/
GridAxis.onAfterGetOffset = function () {
var grid = this.grid;
(grid && grid.columns || []).forEach(function (column) {
column.getOffset();
});
};
/**
* @private
*/
GridAxis.onAfterGetTitlePosition = function (e) {
var axis = this;
var options = axis.options;
var gridOptions = options.grid || {};
if (gridOptions.enabled === true) {
// compute anchor points for each of the title align options
var title = axis.axisTitle, axisHeight = axis.height, horiz = axis.horiz, axisLeft = axis.left, offset = axis.offset, opposite = axis.opposite, _a = axis.options.title, axisTitleOptions = _a === void 0 ? {} : _a, axisTop = axis.top, axisWidth = axis.width;
var tickSize = axis.tickSize();
var titleWidth = title && title.getBBox().width;
var xOption = axisTitleOptions.x || 0;
var yOption = axisTitleOptions.y || 0;
var titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);
var titleFontSize = axis.chart.renderer.fontMetrics(axisTitleOptions.style &&
axisTitleOptions.style.fontSize, title).f;
var crispCorr = tickSize ? tickSize[0] / 2 : 0;
// TODO account for alignment
// the position in the perpendicular direction of the axis
var offAxis = ((horiz ? axisTop + axisHeight : axisLeft) +
(horiz ? 1 : -1) * // horizontal axis reverses the margin
(opposite ? -1 : 1) * // so does opposite axes
crispCorr +
(axis.side === GridAxis.Side.bottom ? titleFontSize : 0));
e.titlePosition.x = horiz ?
axisLeft - titleWidth / 2 - titleMargin + xOption :
offAxis + (opposite ? axisWidth : 0) + offset + xOption;
e.titlePosition.y = horiz ?
(offAxis -
(opposite ? axisHeight : 0) +
(opposite ? titleFontSize : -titleFontSize) / 2 +
offset +
yOption) :
axisTop - titleMargin + yOption;
}
};
/**
* @private
*/
GridAxis.onAfterInit = function () {
var axis = this;
var chart = axis.chart, _a = axis.options.grid, gridOptions = _a === void 0 ? {} : _a, userOptions = axis.userOptions;
if (gridOptions.enabled) {
applyGridOptions(axis);
/* eslint-disable no-invalid-this */
// TODO: wrap the axis instead
wrap(axis, 'labelFormatter', function (proceed) {
var _a = this, axis = _a.axis, value = _a.value;
var tickPos = axis.tickPositions;
var series = (axis.isLinked ?
axis.linkedParent :
axis).series[0];
var isFirst = value === tickPos[0];
var isLast = value === tickPos[tickPos.length - 1];
var point = series && find(series.options.data, function (p) {
return p[axis.isXAxis ? 'x' : 'y'] === value;
});
// Make additional properties available for the
// formatter
this.isFirst = isFirst;
this.isLast = isLast;
this.point = point;
// Call original labelFormatter
return proceed.call(this);
});
/* eslint-enable no-invalid-this */
}
if (gridOptions.columns) {
var columns = axis.grid.columns = [], columnIndex = axis.grid.columnIndex = 0;
// Handle columns, each column is a grid axis
while (++columnIndex < gridOptions.columns.length) {
var columnOptions = merge(userOptions, gridOptions.columns[gridOptions.columns.length - columnIndex - 1], {
linkedTo: 0,
// Force to behave like category axis
type: 'category',
// Disable by default the scrollbar on the grid axis
scrollbar: {
enabled: false
}
});
delete columnOptions.grid.columns; // Prevent recursion
var column = new Axis(axis.chart, columnOptions);
column.grid.isColumn = true;
column.grid.columnIndex = columnIndex;
// Remove column axis from chart axes array, and place it
// in the columns array.
erase(chart.axes, column);
erase(chart[axis.coll], column);
columns.push(column);
}
}
};
/**
* Draw an extra line on the far side of the outermost axis,
* creating floor/roof/wall of a grid. And some padding.
* ```
* Make this:
* (axis.min) __________________________ (axis.max)
* | | | | |
* Into this:
* (axis.min) __________________________ (axis.max)
* ___|____|____|____|____|__
* ```
* @private
*/
GridAxis.onAfterRender = function () {
var axis = this;
var grid = axis.grid;
var options = axis.options;
var renderer = axis.chart.renderer;
var gridOptions = options.grid || {};
var yStartIndex, yEndIndex, xStartIndex, xEndIndex;
if (gridOptions.enabled === true) {
// @todo acutual label padding (top, bottom, left, right)
axis.maxLabelDimensions = axis.getMaxLabelDimensions(axis.ticks, axis.tickPositions);
// Remove right wall before rendering if updating
if (axis.rightWall) {
axis.rightWall.destroy();
}
/*
Draw an extra axis line on outer axes
>
Make this: |______|______|______|___
> _________________________
Into this: |______|______|______|__|
*/
if (axis.grid && axis.grid.isOuterAxis() && axis.axisLine) {
var lineWidth = options.lineWidth;
if (lineWidth) {
var linePath = axis.getLinePath(lineWidth);
var startPoint = linePath[0];
var endPoint = linePath[1];
// Negate distance if top or left axis
// Subtract 1px to draw the line at the end of the tick
var tickLength = (axis.tickSize('tick') || [1])[0];
var distance = (tickLength - 1) * ((axis.side === GridAxis.Side.top ||
axis.side === GridAxis.Side.left) ? -1 : 1);
// If axis is horizontal, reposition line path vertically
if (startPoint[0] === 'M' && endPoint[0] === 'L') {
if (axis.horiz) {
startPoint[2] += distance;
endPoint[2] += distance;
}
else {
// If axis is vertical, reposition line path
// horizontally
startPoint[1] += distance;
endPoint[1] += distance;
}
}
if (!axis.grid.axisLineExtra) {
axis.grid.axisLineExtra = renderer
.path(linePath)
.attr({
zIndex: 7
})
.addClass('highcharts-axis-line')
.add(axis.axisGroup);
if (!renderer.styledMode) {
axis.grid.axisLineExtra.attr({
stroke: options.lineColor,
'stroke-width': lineWidth
});
}
}
else {
axis.grid.axisLineExtra.animate({
d: linePath
});
}
// show or hide the line depending on
// options.showEmpty
axis.axisLine[axis.showAxis ? 'show' : 'hide'](true);
}
}
(grid && grid.columns || []).forEach(function (column) {
column.render();
});
}
};
/**
* @private
*/
GridAxis.onAfterSetAxisTranslation = function () {
var axis = this;
var tickInfo = axis.tickPositions && axis.tickPositions.info;
var options = axis.options;
var gridOptions = options.grid || {};
var userLabels = axis.userOptions.labels || {};
if (axis.horiz) {
if (gridOptions.enabled === true) {
axis.series.forEach(function (series) {
series.options.pointRange = 0;
});
}
// Lower level time ticks, like hours or minutes, represent
// points in time and not ranges. These should be aligned
// left in the grid cell by default. The same applies to
// years of higher order.
if (tickInfo &&
options.dateTimeLabelFormats &&
options.labels &&
!defined(userLabels.align) &&
(options.dateTimeLabelFormats[tickInfo.unitName].range === false ||
tickInfo.count > 1 // years
)) {
options.labels.align = 'left';
if (!defined(userLabels.x)) {
options.labels.x = 3;
}
}
}
};
/**
* Creates a left and right wall on horizontal axes:
* - Places leftmost tick at the start of the axis, to create a left
* wall
* - Ensures that the rightmost tick is at the end of the axis, to
* create a right wall.
* @private
*/
GridAxis.onAfterSetOptions = function (e) {
var options = this.options, userOptions = e.userOptions, gridAxisOptions, gridOptions = ((options && isObject(options.grid)) ? options.grid : {});
if (gridOptions.enabled === true) {
// Merge the user options into default grid axis options so
// that when a user option is set, it takes presedence.
gridAxisOptions = merge(true, {
className: ('highcharts-grid-axis ' + (userOptions.className || '')),
dateTimeLabelFormats: {
hour: {
list: ['%H:%M', '%H']
},
day: {
list: ['%A, %e. %B', '%a, %e. %b', '%E']
},
week: {
list: ['Week %W', 'W%W']
},
month: {
list: ['%B', '%b', '%o']
}
},
grid: {
borderWidth: 1
},
labels: {
padding: 2,
style: {
fontSize: '13px'
}
},
margin: 0,
title: {
text: null,
reserveSpace: false,
rotation: 0
},
// In a grid axis, only allow one unit of certain types,
// for example we shouln't have one grid cell spanning
// two days.
units: [[
'millisecond',
[1, 10, 100]
], [
'second',
[1, 10]
], [
'minute',
[1, 5, 15]
], [
'hour',
[1, 6]
], [
'day',
[1]
], [
'week',
[1]
], [
'month',
[1]
], [
'year',
null
]]
}, userOptions);
// X-axis specific options
if (this.coll === 'xAxis') {
// For linked axes, tickPixelInterval is used only if
// the tickPositioner below doesn't run or returns
// undefined (like multiple years)
if (defined(userOptions.linkedTo) &&
!defined(userOptions.tickPixelInterval)) {
gridAxisOptions.tickPixelInterval = 350;
}
// For the secondary grid axis, use the primary axis'
// tick intervals and return ticks one level higher.
if (
// Check for tick pixel interval in options
!defined(userOptions.tickPixelInterval) &&
// Only for linked axes
defined(userOptions.linkedTo) &&
!defined(userOptions.tickPositioner) &&
!defined(userOptions.tickInterval)) {
gridAxisOptions.tickPositioner = function (min, max) {
var parentInfo = (this.linkedParent &&
this.linkedParent.tickPositions &&
this.linkedParent.tickPositions.info);
if (parentInfo) {
var unitIdx, count, unitName, i, units = gridAxisOptions.units, unitRange;
for (i = 0; i < units.length; i++) {
if (units[i][0] ===
parentInfo.unitName) {
unitIdx = i;
break;
}
}
// Get the first allowed count on the next
// unit.
if (units[unitIdx + 1]) {
unitName = units[unitIdx + 1][0];
count =
(units[unitIdx + 1][1] || [1])[0];
// In case the base X axis shows years, make
// the secondary axis show ten times the
// years (#11427)
}
else if (parentInfo.unitName === 'year') {
unitName = 'year';
count = parentInfo.count * 10;
}
unitRange = timeUnits[unitName];
this.tickInterval = unitRange * count;
return this.getTimeTicks({
unitRange: unitRange,
count: count,
unitName: unitName
}, min, max, this.options.startOfWeek);
}
};
}
}
// Now merge the combined options into the axis options
merge(true, this.options, gridAxisOptions);
if (this.horiz) {
/* _________________________
Make this: ___|_____|_____|_____|__|
^ ^
_________________________
Into this: |_____|_____|_____|_____|
^ ^ */
options.minPadding = pick(userOptions.minPadding, 0);
options.maxPadding = pick(userOptions.maxPadding, 0);
}
// If borderWidth is set, then use its value for tick and
// line width.
if (isNumber(options.grid.borderWidth)) {
options.tickWidth = options.lineWidth = gridOptions.borderWidth;
}
}
};
/**
* @private
*/
GridAxis.onAfterSetOptions2 = function (e) {
var axis = this;
var userOptions = e.userOptions;
var gridOptions = userOptions && userOptions.grid || {};
var columns = gridOptions.columns;
// Add column options to the parent axis. Children has their column
// options set on init in onGridAxisAfterInit.
if (gridOptions.enabled && columns) {
merge(true, axis.options, columns[columns.length - 1]);
}
};
/**
* Handle columns and setScale.
* @private
*/
GridAxis.onAfterSetScale = function () {
var axis = this;
(axis.grid.columns || []).forEach(function (column) {
column.setScale();
});
};
/**
* Draw vertical axis ticks extra long to create cell floors and roofs.
* Overrides the tickLength for vertical axes.
* @private
*/
GridAxis.onAfterTickSize = function (e) {
var defaultLeftAxisOptions = Axis.defaultLeftAxisOptions;
var _a = this, horiz = _a.horiz, maxLabelDimensions = _a.maxLabelDimensions, _b = _a.options.grid, gridOptions = _b === void 0 ? {} : _b;
if (gridOptions.enabled && maxLabelDimensions) {
var labelPadding = (Math.abs(defaultLeftAxisOptions.labels.x) * 2);
var distance = horiz ?
gridOptions.cellHeight || labelPadding + maxLabelDimensions.height :
labelPadding + maxLabelDimensions.width;
if (isArray(e.tickSize)) {
e.tickSize[0] = distance;
}
else {
e.tickSize = [distance, 0];
}
}
};
/**
* @private
*/
GridAxis.onDestroy = function (e) {
var grid = this.grid;
(grid.columns || []).forEach(function (column) {
column.destroy(e.keepEvents);
});
grid.columns = void 0;
};
/**
* Wraps axis init to draw cell walls on vertical axes.
* @private
*/
GridAxis.onInit = function (e) {
var axis = this;
var userOptions = e.userOptions || {};
var gridOptions = userOptions.grid || {};
if (gridOptions.enabled && defined(gridOptions.borderColor)) {
userOptions.tickColor = userOptions.lineColor = gridOptions.borderColor;
}
if (!axis.grid) {
axis.grid = new GridAxisAdditions(axis);
}
};
/**
* Makes tick labels which are usually ignored in a linked axis
* displayed if they are within range of linkedParent.min.
* ```
* _____________________________
* | | | | |
* Make this: | | 2 | 3 | 4 |
* |___|_______|_______|_______|
* ^
* _____________________________
* | | | | |
* Into this: | 1 | 2 | 3 | 4 |
* |___|_______|_______|_______|
* ^
* ```
* @private
* @todo Does this function do what the drawing says? Seems to affect
* ticks and not the labels directly?
*/
GridAxis.onTrimTicks = function () {
var axis = this;
var options = axis.options;
var gridOptions = options.grid || {};
var categoryAxis = axis.categories;
var tickPositions = axis.tickPositions;
var firstPos = tickPositions[0];
var lastPos = tickPositions[tickPositions.length - 1];
var linkedMin = axis.linkedParent && axis.linkedParent.min;
var linkedMax = axis.linkedParent && axis.linkedParent.max;
var min = linkedMin || axis.min;
var max = linkedMax || axis.max;
var tickInterval = axis.tickInterval;
var endMoreThanMin = (firstPos < min &&
firstPos + tickInterval > min);
var startLessThanMax = (lastPos > max &&
lastPos - tickInterval < max);
if (gridOptions.enabled === true &&
!categoryAxis &&
(axis.horiz || axis.isLinked)) {
if (endMoreThanMin && !options.startOnTick) {
tickPositions[0] = min;
}
if (startLessThanMax && !options.endOnTick) {
tickPositions[tickPositions.length - 1] = max;
}
}
};
/**
* Avoid altering tickInterval when reserving space.
* @private
*/
GridAxis.wrapUnsquish = function (proceed) {
var axis = this;
var _a = axis.options.grid, gridOptions = _a === void 0 ? {} : _a;
if (gridOptions.enabled === true && axis.categories) {
return axis.tickInterval;
}
return proceed.apply(axis, argsToArray(arguments));
};
return GridAxis;
}());
(function (GridAxis) {
/**
* Enum for which side the axis is on. Maps to axis.side.
* @private
*/
var Side;
(function (Side) {
Side[Side["top"] = 0] = "top";
Side[Side["right"] = 1] = "right";
Side[Side["bottom"] = 2] = "bottom";
Side[Side["left"] = 3] = "left";
})(Side = GridAxis.Side || (GridAxis.Side = {}));
})(GridAxis || (GridAxis = {}));
GridAxis.compose(Axis);
export default GridAxis;
@@ -0,0 +1,46 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
/**
* @private
* @class
*/
var HiddenAxis = /** @class */ (function () {
function HiddenAxis() {
}
/**
* Augments methods for the x axis in order to hide it completely. Used for
* the X axis in gauges
*
* @private
*
* @param {Highcharts.Axis} axis
* Radial axis to augment.
*/
HiddenAxis.init = function (axis) {
axis.getOffset = function () { };
axis.redraw = function () {
this.isDirty = false; // prevent setting Y axis dirty
};
axis.render = function () {
this.isDirty = false; // prevent setting Y axis dirty
};
axis.createLabelCollector = function () {
return function () {
return;
};
};
axis.setScale = function () { };
axis.setCategories = function () { };
axis.setTitle = function () { };
axis.isHidden = true;
};
return HiddenAxis;
}());
export default HiddenAxis;
@@ -0,0 +1,176 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Axis from './Axis.js';
import U from '../Utilities.js';
var addEvent = U.addEvent, getMagnitude = U.getMagnitude, normalizeTickInterval = U.normalizeTickInterval, pick = U.pick;
/* eslint-disable valid-jsdoc */
/**
* Provides logarithmic support for axes.
*
* @private
* @class
*/
var LogarithmicAxisAdditions = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function LogarithmicAxisAdditions(axis) {
this.axis = axis;
}
/* *
*
* Functions
*
* */
/**
* Set the tick positions of a logarithmic axis.
*/
LogarithmicAxisAdditions.prototype.getLogTickPositions = function (interval, min, max, minor) {
var log = this;
var axis = log.axis;
var axisLength = axis.len;
var options = axis.options;
// Since we use this method for both major and minor ticks,
// use a local variable and return the result
var positions = [];
// Reset
if (!minor) {
log.minorAutoInterval = void 0;
}
// First case: All ticks fall on whole logarithms: 1, 10, 100 etc.
if (interval >= 0.5) {
interval = Math.round(interval);
positions = axis.getLinearTickPositions(interval, min, max);
// Second case: We need intermediary ticks. For example
// 1, 2, 4, 6, 8, 10, 20, 40 etc.
}
else if (interval >= 0.08) {
var roundedMin = Math.floor(min), intermediate, i, j, len, pos, lastPos, break2;
if (interval > 0.3) {
intermediate = [1, 2, 4];
// 0.2 equals five minor ticks per 1, 10, 100 etc
}
else if (interval > 0.15) {
intermediate = [1, 2, 4, 6, 8];
}
else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
}
for (i = roundedMin; i < max + 1 && !break2; i++) {
len = intermediate.length;
for (j = 0; j < len && !break2; j++) {
pos = log.log2lin(log.lin2log(i) * intermediate[j]);
// #1670, lastPos is #3113
if (pos > min &&
(!minor || lastPos <= max) &&
typeof lastPos !== 'undefined') {
positions.push(lastPos);
}
if (lastPos > max) {
break2 = true;
}
lastPos = pos;
}
}
// Third case: We are so deep in between whole logarithmic values that
// we might as well handle the tick positions like a linear axis. For
// example 1.01, 1.02, 1.03, 1.04.
}
else {
var realMin = log.lin2log(min), realMax = log.lin2log(max), tickIntervalOption = minor ?
axis.getMinorTickInterval() :
options.tickInterval, filteredTickIntervalOption = tickIntervalOption === 'auto' ?
null :
tickIntervalOption, tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1), totalPixelLength = minor ?
axisLength / axis.tickPositions.length :
axisLength;
interval = pick(filteredTickIntervalOption, log.minorAutoInterval, (realMax - realMin) *
tickPixelIntervalOption / (totalPixelLength || 1));
interval = normalizeTickInterval(interval, void 0, getMagnitude(interval));
positions = axis.getLinearTickPositions(interval, realMin, realMax).map(log.log2lin);
if (!minor) {
log.minorAutoInterval = interval / 5;
}
}
// Set the axis-level tickInterval variable
if (!minor) {
axis.tickInterval = interval;
}
return positions;
};
LogarithmicAxisAdditions.prototype.lin2log = function (num) {
return Math.pow(10, num);
};
LogarithmicAxisAdditions.prototype.log2lin = function (num) {
return Math.log(num) / Math.LN10;
};
return LogarithmicAxisAdditions;
}());
var LogarithmicAxis = /** @class */ (function () {
function LogarithmicAxis() {
}
/**
* Provides logarithmic support for axes.
*
* @private
*/
LogarithmicAxis.compose = function (AxisClass) {
AxisClass.keepProps.push('logarithmic');
// HC <= 8 backwards compatibility, allow wrapping
// Axis.prototype.lin2log and log2lin
// @todo Remove this in next major
var axisProto = AxisClass.prototype;
var logAxisProto = LogarithmicAxisAdditions.prototype;
axisProto.log2lin = logAxisProto.log2lin;
axisProto.lin2log = logAxisProto.lin2log;
/* eslint-disable no-invalid-this */
addEvent(AxisClass, 'init', function (e) {
var axis = this;
var options = e.userOptions;
var logarithmic = axis.logarithmic;
if (options.type !== 'logarithmic') {
axis.logarithmic = void 0;
}
else {
if (!logarithmic) {
logarithmic = axis.logarithmic = new LogarithmicAxisAdditions(axis);
}
// HC <= 8 backwards compatibility, allow wrapping
// Axis.prototype.lin2log and log2lin
// @todo Remove this in next major
if (axis.log2lin !== logarithmic.log2lin) {
logarithmic.log2lin = axis.log2lin.bind(axis);
}
if (axis.lin2log !== logarithmic.lin2log) {
logarithmic.lin2log = axis.lin2log.bind(axis);
}
}
});
addEvent(AxisClass, 'afterInit', function () {
var axis = this;
var log = axis.logarithmic;
// extend logarithmic axis
if (log) {
axis.lin2val = function (num) {
return log.lin2log(num);
};
axis.val2lin = function (num) {
return log.log2lin(num);
};
}
});
};
return LogarithmicAxis;
}());
LogarithmicAxis.compose(Axis); // @todo move to factory functions
export default LogarithmicAxis;
+157
View File
@@ -0,0 +1,157 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Axis from './Axis.js';
import U from '../Utilities.js';
var addEvent = U.addEvent, pick = U.pick;
/**
* Map support for axes.
* @private
* @class
*/
var MapAxisAdditions = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function MapAxisAdditions(axis) {
this.axis = axis;
}
return MapAxisAdditions;
}());
/**
* Axis with map support.
* @private
* @class
*/
var MapAxis = /** @class */ (function () {
function MapAxis() {
}
/**
* Extends axes with map support.
* @private
*
* @param {Highcharts.Axis} AxisClass
* Axis class to extend.
*/
MapAxis.compose = function (AxisClass) {
AxisClass.keepProps.push('mapAxis');
/* eslint-disable no-invalid-this */
addEvent(AxisClass, 'init', function () {
var axis = this;
if (!axis.mapAxis) {
axis.mapAxis = new MapAxisAdditions(axis);
}
});
// Override to use the extreme coordinates from the SVG shape, not the
// data values
addEvent(AxisClass, 'getSeriesExtremes', function () {
if (!this.mapAxis) {
return;
}
var axis = this;
var xData = [];
// Remove the xData array and cache it locally so that the proceed
// method doesn't use it
if (axis.isXAxis) {
axis.series.forEach(function (series, i) {
if (series.useMapGeometry) {
xData[i] = series.xData;
series.xData = [];
}
});
axis.mapAxis.seriesXData = xData;
}
});
addEvent(AxisClass, 'afterGetSeriesExtremes', function () {
if (!this.mapAxis) {
return;
}
var axis = this;
var xData = axis.mapAxis.seriesXData || [];
var dataMin, dataMax, useMapGeometry;
// Run extremes logic for map and mapline
if (axis.isXAxis) {
dataMin = pick(axis.dataMin, Number.MAX_VALUE);
dataMax = pick(axis.dataMax, -Number.MAX_VALUE);
axis.series.forEach(function (series, i) {
if (series.useMapGeometry) {
dataMin = Math.min(dataMin, pick(series.minX, dataMin));
dataMax = Math.max(dataMax, pick(series.maxX, dataMax));
series.xData = xData[i]; // Reset xData array
useMapGeometry = true;
}
});
if (useMapGeometry) {
axis.dataMin = dataMin;
axis.dataMax = dataMax;
}
axis.mapAxis.seriesXData = void 0;
}
});
// Override axis translation to make sure the aspect ratio is always
// kept
addEvent(AxisClass, 'afterSetAxisTranslation', function () {
if (!this.mapAxis) {
return;
}
var axis = this;
var chart = axis.chart;
var plotRatio = chart.plotWidth / chart.plotHeight;
var xAxis = chart.xAxis[0];
var mapRatio, adjustedAxisLength, padAxis, fixTo, fixDiff, preserveAspectRatio;
// Check for map-like series
if (axis.coll === 'yAxis' && typeof xAxis.transA !== 'undefined') {
axis.series.forEach(function (series) {
if (series.preserveAspectRatio) {
preserveAspectRatio = true;
}
});
}
// On Y axis, handle both
if (preserveAspectRatio) {
// Use the same translation for both axes
axis.transA = xAxis.transA = Math.min(axis.transA, xAxis.transA);
mapRatio = plotRatio / ((xAxis.max - xAxis.min) /
(axis.max - axis.min));
// What axis to pad to put the map in the middle
padAxis = mapRatio < 1 ? axis : xAxis;
// Pad it
adjustedAxisLength =
(padAxis.max - padAxis.min) * padAxis.transA;
padAxis.mapAxis.pixelPadding = padAxis.len - adjustedAxisLength;
padAxis.minPixelPadding = padAxis.mapAxis.pixelPadding / 2;
fixTo = padAxis.mapAxis.fixTo;
if (fixTo) {
fixDiff = fixTo[1] - padAxis.toValue(fixTo[0], true);
fixDiff *= padAxis.transA;
if (Math.abs(fixDiff) > padAxis.minPixelPadding ||
(padAxis.min === padAxis.dataMin &&
padAxis.max === padAxis.dataMax)) { // zooming out again, keep within restricted area
fixDiff = 0;
}
padAxis.minPixelPadding -= fixDiff;
}
}
});
// Override Axis.render in order to delete the fixTo prop
addEvent(AxisClass, 'render', function () {
var axis = this;
if (axis.mapAxis) {
axis.mapAxis.fixTo = void 0;
}
});
/* eslint-enable no-invalid-this */
};
return MapAxis;
}());
MapAxis.compose(Axis); // @todo move to factory functions
export default MapAxis;
@@ -0,0 +1,164 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../Globals.js';
var isTouchDevice = H.isTouchDevice;
import U from '../Utilities.js';
var addEvent = U.addEvent, correctFloat = U.correctFloat, defined = U.defined, isNumber = U.isNumber, pick = U.pick;
/* eslint-disable valid-jsdoc */
/**
* @private
* @class
*/
var NavigatorAxisAdditions = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function NavigatorAxisAdditions(axis) {
this.axis = axis;
}
/* *
*
* Functions
*
* */
/**
* @private
*/
NavigatorAxisAdditions.prototype.destroy = function () {
this.axis = void 0;
};
/**
* Add logic to normalize the zoomed range in order to preserve the pressed
* state of range selector buttons
*
* @private
* @function Highcharts.Axis#toFixedRange
* @param {number} [pxMin]
* @param {number} [pxMax]
* @param {number} [fixedMin]
* @param {number} [fixedMax]
* @return {*}
*/
NavigatorAxisAdditions.prototype.toFixedRange = function (pxMin, pxMax, fixedMin, fixedMax) {
var navigator = this;
var axis = navigator.axis;
var chart = axis.chart;
var fixedRange = chart && chart.fixedRange, halfPointRange = (axis.pointRange || 0) / 2, newMin = pick(fixedMin, axis.translate(pxMin, true, !axis.horiz)), newMax = pick(fixedMax, axis.translate(pxMax, true, !axis.horiz)), changeRatio = fixedRange && (newMax - newMin) / fixedRange;
// Add/remove half point range to/from the extremes (#1172)
if (!defined(fixedMin)) {
newMin = correctFloat(newMin + halfPointRange);
}
if (!defined(fixedMax)) {
newMax = correctFloat(newMax - halfPointRange);
}
// If the difference between the fixed range and the actual requested
// range is too great, the user is dragging across an ordinal gap, and
// we need to release the range selector button.
if (changeRatio > 0.7 && changeRatio < 1.3) {
if (fixedMax) {
newMin = newMax - fixedRange;
}
else {
newMax = newMin + fixedRange;
}
}
if (!isNumber(newMin) || !isNumber(newMax)) { // #1195, #7411
newMin = newMax = void 0;
}
return {
min: newMin,
max: newMax
};
};
return NavigatorAxisAdditions;
}());
/**
* @private
* @class
*/
var NavigatorAxis = /** @class */ (function () {
function NavigatorAxis() {
}
/* *
*
* Static Functions
*
* */
/**
* @private
*/
NavigatorAxis.compose = function (AxisClass) {
AxisClass.keepProps.push('navigatorAxis');
/* eslint-disable no-invalid-this */
addEvent(AxisClass, 'init', function () {
var axis = this;
if (!axis.navigatorAxis) {
axis.navigatorAxis = new NavigatorAxisAdditions(axis);
}
});
// For Stock charts, override selection zooming with some special
// features because X axis zooming is already allowed by the Navigator
// and Range selector.
addEvent(AxisClass, 'zoom', function (e) {
var axis = this;
var chart = axis.chart;
var chartOptions = chart.options;
var navigator = chartOptions.navigator;
var navigatorAxis = axis.navigatorAxis;
var pinchType = chartOptions.chart.pinchType;
var rangeSelector = chartOptions.rangeSelector;
var zoomType = chartOptions.chart.zoomType;
var previousZoom;
if (axis.isXAxis && ((navigator && navigator.enabled) ||
(rangeSelector && rangeSelector.enabled))) {
// For y only zooming, ignore the X axis completely
if (zoomType === 'y') {
e.zoomed = false;
// For xy zooming, record the state of the zoom before zoom
// selection, then when the reset button is pressed, revert to
// this state. This should apply only if the chart is
// initialized with a range (#6612), otherwise zoom all the way
// out.
}
else if (((!isTouchDevice && zoomType === 'xy') ||
(isTouchDevice && pinchType === 'xy')) &&
axis.options.range) {
previousZoom = navigatorAxis.previousZoom;
if (defined(e.newMin)) {
navigatorAxis.previousZoom = [axis.min, axis.max];
}
else if (previousZoom) {
e.newMin = previousZoom[0];
e.newMax = previousZoom[1];
navigatorAxis.previousZoom = void 0;
}
}
}
if (typeof e.zoomed !== 'undefined') {
e.preventDefault();
}
});
/* eslint-enable no-invalid-this */
};
/* *
*
* Static Properties
*
* */
/**
* @private
*/
NavigatorAxis.AdditionsClass = NavigatorAxisAdditions;
return NavigatorAxis;
}());
export default NavigatorAxis;
@@ -0,0 +1,777 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Axis from './Axis.js';
import H from '../Globals.js';
import U from '../Utilities.js';
var addEvent = U.addEvent, css = U.css, defined = U.defined, pick = U.pick, timeUnits = U.timeUnits;
import '../Chart/Chart.js';
// Has a dependency on Navigator due to the use of Axis.toFixedRange
import '../Navigator.js';
import '../Series/Series.js';
var Chart = H.Chart, Series = H.Series;
/**
* Extends the axis with ordinal support.
* @private
*/
var OrdinalAxis;
(function (OrdinalAxis) {
/* *
*
* Classes
*
* */
/**
* @private
*/
var Composition = /** @class */ (function () {
/* *
*
* Constructors
*
* */
/**
* @private
*/
function Composition(axis) {
this.index = {};
this.axis = axis;
}
/* *
*
* Functions
*
* */
/**
* Calculate the ordinal positions before tick positions are calculated.
*
* @private
*/
Composition.prototype.beforeSetTickPositions = function () {
var axis = this.axis, ordinal = axis.ordinal, len, ordinalPositions = [], uniqueOrdinalPositions, useOrdinal = false, dist, extremes = axis.getExtremes(), min = extremes.min, max = extremes.max, minIndex, maxIndex, slope, hasBreaks = axis.isXAxis && !!axis.options.breaks, isOrdinal = axis.options.ordinal, overscrollPointsRange = Number.MAX_VALUE, ignoreHiddenSeries = axis.chart.options.chart.ignoreHiddenSeries, i, hasBoostedSeries;
// Apply the ordinal logic
if (isOrdinal || hasBreaks) { // #4167 YAxis is never ordinal ?
axis.series.forEach(function (series, i) {
uniqueOrdinalPositions = [];
if ((!ignoreHiddenSeries || series.visible !== false) &&
(series.takeOrdinalPosition !== false || hasBreaks)) {
// concatenate the processed X data into the existing
// positions, or the empty array
ordinalPositions = ordinalPositions.concat(series.processedXData);
len = ordinalPositions.length;
// remove duplicates (#1588)
ordinalPositions.sort(function (a, b) {
// without a custom function it is sorted as strings
return a - b;
});
overscrollPointsRange = Math.min(overscrollPointsRange, pick(
// Check for a single-point series:
series.closestPointRange, overscrollPointsRange));
if (len) {
i = 0;
while (i < len - 1) {
if (ordinalPositions[i] !== ordinalPositions[i + 1]) {
uniqueOrdinalPositions.push(ordinalPositions[i + 1]);
}
i++;
}
// Check first item:
if (uniqueOrdinalPositions[0] !== ordinalPositions[0]) {
uniqueOrdinalPositions.unshift(ordinalPositions[0]);
}
ordinalPositions = uniqueOrdinalPositions;
}
}
if (series.isSeriesBoosting) {
hasBoostedSeries = true;
}
});
if (hasBoostedSeries) {
ordinalPositions.length = 0;
}
// cache the length
len = ordinalPositions.length;
// Check if we really need the overhead of mapping axis data
// against the ordinal positions. If the series consist of
// evenly spaced data any way, we don't need any ordinal logic.
if (len > 2) { // two points have equal distance by default
dist = ordinalPositions[1] - ordinalPositions[0];
i = len - 1;
while (i-- && !useOrdinal) {
if (ordinalPositions[i + 1] - ordinalPositions[i] !== dist) {
useOrdinal = true;
}
}
// When zooming in on a week, prevent axis padding for
// weekends even though the data within the week is evenly
// spaced.
if (!axis.options.keepOrdinalPadding &&
(ordinalPositions[0] - min > dist ||
max - ordinalPositions[ordinalPositions.length - 1] >
dist)) {
useOrdinal = true;
}
}
else if (axis.options.overscroll) {
if (len === 2) {
// Exactly two points, distance for overscroll is fixed:
overscrollPointsRange =
ordinalPositions[1] - ordinalPositions[0];
}
else if (len === 1) {
// We have just one point, closest distance is unknown.
// Assume then it is last point and overscrolled range:
overscrollPointsRange = axis.options.overscroll;
ordinalPositions = [
ordinalPositions[0],
ordinalPositions[0] + overscrollPointsRange
];
}
else {
// In case of zooming in on overscrolled range, stick to
// the old range:
overscrollPointsRange = ordinal.overscrollPointsRange;
}
}
// Record the slope and offset to compute the linear values from
// the array index. Since the ordinal positions may exceed the
// current range, get the start and end positions within it
// (#719, #665b)
if (useOrdinal) {
if (axis.options.overscroll) {
ordinal.overscrollPointsRange = overscrollPointsRange;
ordinalPositions = ordinalPositions.concat(ordinal.getOverscrollPositions());
}
// Register
ordinal.positions = ordinalPositions;
// This relies on the ordinalPositions being set. Use
// Math.max and Math.min to prevent padding on either sides
// of the data.
minIndex = axis.ordinal2lin(// #5979
Math.max(min, ordinalPositions[0]), true);
maxIndex = Math.max(axis.ordinal2lin(Math.min(max, ordinalPositions[ordinalPositions.length - 1]), true), 1); // #3339
// Set the slope and offset of the values compared to the
// indices in the ordinal positions
ordinal.slope = slope = (max - min) / (maxIndex - minIndex);
ordinal.offset = min - (minIndex * slope);
}
else {
ordinal.overscrollPointsRange = pick(axis.closestPointRange, ordinal.overscrollPointsRange);
ordinal.positions = axis.ordinal.slope = ordinal.offset =
void 0;
}
}
axis.isOrdinal = isOrdinal && useOrdinal; // #3818, #4196, #4926
ordinal.groupIntervalFactor = null; // reset for next run
};
/**
* Get the ordinal positions for the entire data set. This is necessary
* in chart panning because we need to find out what points or data
* groups are available outside the visible range. When a panning
* operation starts, if an index for the given grouping does not exists,
* it is created and cached. This index is deleted on updated data, so
* it will be regenerated the next time a panning operation starts.
*
* @private
*/
Composition.prototype.getExtendedPositions = function () {
var ordinal = this, axis = ordinal.axis, axisProto = axis.constructor.prototype, chart = axis.chart, grouping = axis.series[0].currentDataGrouping, ordinalIndex = ordinal.index, key = grouping ?
grouping.count + grouping.unitName :
'raw', overscroll = axis.options.overscroll, extremes = axis.getExtremes(), fakeAxis, fakeSeries;
// If this is the first time, or the ordinal index is deleted by
// updatedData,
// create it.
if (!ordinalIndex) {
ordinalIndex = ordinal.index = {};
}
if (!ordinalIndex[key]) {
// Create a fake axis object where the extended ordinal
// positions are emulated
fakeAxis = {
series: [],
chart: chart,
getExtremes: function () {
return {
min: extremes.dataMin,
max: extremes.dataMax + overscroll
};
},
options: {
ordinal: true
},
ordinal: {},
ordinal2lin: axisProto.ordinal2lin,
val2lin: axisProto.val2lin // #2590
};
fakeAxis.ordinal.axis = fakeAxis;
// Add the fake series to hold the full data, then apply
// processData to it
axis.series.forEach(function (series) {
fakeSeries = {
xAxis: fakeAxis,
xData: series.xData.slice(),
chart: chart,
destroyGroupedData: H.noop,
getProcessedData: H.Series.prototype.getProcessedData
};
fakeSeries.xData = fakeSeries.xData.concat(ordinal.getOverscrollPositions());
fakeSeries.options = {
dataGrouping: grouping ? {
enabled: true,
forced: true,
// doesn't matter which, use the fastest
approximation: 'open',
units: [[
grouping.unitName,
[grouping.count]
]]
} : {
enabled: false
}
};
series.processData.apply(fakeSeries);
fakeAxis.series.push(fakeSeries);
});
// Run beforeSetTickPositions to compute the ordinalPositions
axis.ordinal.beforeSetTickPositions.apply({ axis: fakeAxis });
// Cache it
ordinalIndex[key] = fakeAxis.ordinal.positions;
}
return ordinalIndex[key];
};
/**
* Find the factor to estimate how wide the plot area would have been if
* ordinal gaps were included. This value is used to compute an imagined
* plot width in order to establish the data grouping interval.
*
* A real world case is the intraday-candlestick example. Without this
* logic, it would show the correct data grouping when viewing a range
* within each day, but once moving the range to include the gap between
* two days, the interval would include the cut-away night hours and the
* data grouping would be wrong. So the below method tries to compensate
* by identifying the most common point interval, in this case days.
*
* An opposite case is presented in issue #718. We have a long array of
* daily data, then one point is appended one hour after the last point.
* We expect the data grouping not to change.
*
* In the future, if we find cases where this estimation doesn't work
* optimally, we might need to add a second pass to the data grouping
* logic, where we do another run with a greater interval if the number
* of data groups is more than a certain fraction of the desired group
* count.
*
* @private
*/
Composition.prototype.getGroupIntervalFactor = function (xMin, xMax, series) {
var ordinal = this, axis = ordinal.axis, i, processedXData = series.processedXData, len = processedXData.length, distances = [], median, groupIntervalFactor = ordinal.groupIntervalFactor;
// Only do this computation for the first series, let the other
// inherit it (#2416)
if (!groupIntervalFactor) {
// Register all the distances in an array
for (i = 0; i < len - 1; i++) {
distances[i] =
processedXData[i + 1] - processedXData[i];
}
// Sort them and find the median
distances.sort(function (a, b) {
return a - b;
});
median = distances[Math.floor(len / 2)];
// Compensate for series that don't extend through the entire
// axis extent. #1675.
xMin = Math.max(xMin, processedXData[0]);
xMax = Math.min(xMax, processedXData[len - 1]);
ordinal.groupIntervalFactor = groupIntervalFactor =
(len * median) / (xMax - xMin);
}
// Return the factor needed for data grouping
return groupIntervalFactor;
};
/**
* Get ticks for an ordinal axis within a range where points don't
* exist. It is required when overscroll is enabled. We can't base on
* points, because we may not have any, so we use approximated
* pointRange and generate these ticks between Axis.dataMax,
* Axis.dataMax + Axis.overscroll evenly spaced. Used in panning and
* navigator scrolling.
*
* @private
*/
Composition.prototype.getOverscrollPositions = function () {
var ordinal = this, axis = ordinal.axis, extraRange = axis.options.overscroll, distance = ordinal.overscrollPointsRange, positions = [], max = axis.dataMax;
if (defined(distance)) {
// Max + pointRange because we need to scroll to the last
positions.push(max);
while (max <= axis.dataMax + extraRange) {
max += distance;
positions.push(max);
}
}
return positions;
};
/**
* Make the tick intervals closer because the ordinal gaps make the
* ticks spread out or cluster.
*
* @private
*/
Composition.prototype.postProcessTickInterval = function (tickInterval) {
// Problem: https://jsfiddle.net/highcharts/FQm4E/1/
// This is a case where this algorithm doesn't work optimally. In
// this case, the tick labels are spread out per week, but all the
// gaps reside within weeks. So we have a situation where the labels
// are courser than the ordinal gaps, and thus the tick interval
// should not be altered.
var ordinal = this, axis = ordinal.axis, ordinalSlope = ordinal.slope, ret;
if (ordinalSlope) {
if (!axis.options.breaks) {
ret = tickInterval / (ordinalSlope / axis.closestPointRange);
}
else {
ret = axis.closestPointRange || tickInterval; // #7275
}
}
else {
ret = tickInterval;
}
return ret;
};
return Composition;
}());
OrdinalAxis.Composition = Composition;
/* *
*
* Functions
*
* */
/**
* Extends the axis with ordinal support.
*
* @private
*
* @param AxisClass
* Axis class to extend.
*
* @param ChartClass
* Chart class to use.
*
* @param SeriesClass
* Series class to use.
*/
function compose(AxisClass, ChartClass, SeriesClass) {
AxisClass.keepProps.push('ordinal');
var axisProto = AxisClass.prototype;
/**
* In an ordinal axis, there might be areas with dense consentrations of
* points, then large gaps between some. Creating equally distributed
* ticks over this entire range may lead to a huge number of ticks that
* will later be removed. So instead, break the positions up in
* segments, find the tick positions for each segment then concatenize
* them. This method is used from both data grouping logic and X axis
* tick position logic.
*
* @private
*/
AxisClass.prototype.getTimeTicks = function (normalizedInterval, min, max, startOfWeek, positions, closestDistance, findHigherRanks) {
if (positions === void 0) { positions = []; }
if (closestDistance === void 0) { closestDistance = 0; }
var start = 0, end, segmentPositions, higherRanks = {}, hasCrossedHigherRank, info, posLength, outsideMax, groupPositions = [], lastGroupPosition = -Number.MAX_VALUE, tickPixelIntervalOption = this.options.tickPixelInterval, time = this.chart.time,
// Record all the start positions of a segment, to use when
// deciding what's a gap in the data.
segmentStarts = [];
// The positions are not always defined, for example for ordinal
// positions when data has regular interval (#1557, #2090)
if ((!this.options.ordinal && !this.options.breaks) ||
!positions ||
positions.length < 3 ||
typeof min === 'undefined') {
return time.getTimeTicks.apply(time, arguments);
}
// Analyze the positions array to split it into segments on gaps
// larger than 5 times the closest distance. The closest distance is
// already found at this point, so we reuse that instead of
// computing it again.
posLength = positions.length;
for (end = 0; end < posLength; end++) {
outsideMax = end && positions[end - 1] > max;
if (positions[end] < min) { // Set the last position before min
start = end;
}
if (end === posLength - 1 ||
positions[end + 1] - positions[end] > closestDistance * 5 ||
outsideMax) {
// For each segment, calculate the tick positions from the
// getTimeTicks utility function. The interval will be the
// same regardless of how long the segment is.
if (positions[end] > lastGroupPosition) { // #1475
segmentPositions = time.getTimeTicks(normalizedInterval, positions[start], positions[end], startOfWeek);
// Prevent duplicate groups, for example for multiple
// segments within one larger time frame (#1475)
while (segmentPositions.length &&
segmentPositions[0] <= lastGroupPosition) {
segmentPositions.shift();
}
if (segmentPositions.length) {
lastGroupPosition =
segmentPositions[segmentPositions.length - 1];
}
segmentStarts.push(groupPositions.length);
groupPositions = groupPositions.concat(segmentPositions);
}
// Set start of next segment
start = end + 1;
}
if (outsideMax) {
break;
}
}
// Get the grouping info from the last of the segments. The info is
// the same for all segments.
info = segmentPositions.info;
// Optionally identify ticks with higher rank, for example when the
// ticks have crossed midnight.
if (findHigherRanks && info.unitRange <= timeUnits.hour) {
end = groupPositions.length - 1;
// Compare points two by two
for (start = 1; start < end; start++) {
if (time.dateFormat('%d', groupPositions[start]) !==
time.dateFormat('%d', groupPositions[start - 1])) {
higherRanks[groupPositions[start]] = 'day';
hasCrossedHigherRank = true;
}
}
// If the complete array has crossed midnight, we want to mark
// the first positions also as higher rank
if (hasCrossedHigherRank) {
higherRanks[groupPositions[0]] = 'day';
}
info.higherRanks = higherRanks;
}
// Save the info
info.segmentStarts = segmentStarts;
groupPositions.info = info;
// Don't show ticks within a gap in the ordinal axis, where the
// space between two points is greater than a portion of the tick
// pixel interval
if (findHigherRanks && defined(tickPixelIntervalOption)) {
var length = groupPositions.length, i = length, itemToRemove, translated, translatedArr = [], lastTranslated, medianDistance, distance, distances = [];
// Find median pixel distance in order to keep a reasonably even
// distance between ticks (#748)
while (i--) {
translated = this.translate(groupPositions[i]);
if (lastTranslated) {
distances[i] = lastTranslated - translated;
}
translatedArr[i] = lastTranslated = translated;
}
distances.sort();
medianDistance = distances[Math.floor(distances.length / 2)];
if (medianDistance < tickPixelIntervalOption * 0.6) {
medianDistance = null;
}
// Now loop over again and remove ticks where needed
i = groupPositions[length - 1] > max ? length - 1 : length; // #817
lastTranslated = void 0;
while (i--) {
translated = translatedArr[i];
distance = Math.abs(lastTranslated - translated);
// #4175 - when axis is reversed, the distance, is negative
// but tickPixelIntervalOption positive, so we need to
// compare the same values
// Remove ticks that are closer than 0.6 times the pixel
// interval from the one to the right, but not if it is
// close to the median distance (#748).
if (lastTranslated &&
distance < tickPixelIntervalOption * 0.8 &&
(medianDistance === null || distance < medianDistance * 0.8)) {
// Is this a higher ranked position with a normal
// position to the right?
if (higherRanks[groupPositions[i]] &&
!higherRanks[groupPositions[i + 1]]) {
// Yes: remove the lower ranked neighbour to the
// right
itemToRemove = i + 1;
lastTranslated = translated; // #709
}
else {
// No: remove this one
itemToRemove = i;
}
groupPositions.splice(itemToRemove, 1);
}
else {
lastTranslated = translated;
}
}
}
return groupPositions;
};
/**
* Translate from linear (internal) to axis value.
*
* @private
* @function Highcharts.Axis#lin2val
*
* @param {number} val
* The linear abstracted value.
*
* @param {boolean} [fromIndex]
* Translate from an index in the ordinal positions rather than a
* value.
*
* @return {number}
*/
axisProto.lin2val = function (val, fromIndex) {
var axis = this, ordinal = axis.ordinal, ordinalPositions = ordinal.positions, ret;
// the visible range contains only equally spaced values
if (!ordinalPositions) {
ret = val;
}
else {
var ordinalSlope = ordinal.slope, ordinalOffset = ordinal.offset, i = ordinalPositions.length - 1, linearEquivalentLeft, linearEquivalentRight, distance;
// Handle the case where we translate from the index directly,
// used only when panning an ordinal axis
if (fromIndex) {
if (val < 0) { // out of range, in effect panning to the left
val = ordinalPositions[0];
}
else if (val > i) { // out of range, panning to the right
val = ordinalPositions[i];
}
else { // split it up
i = Math.floor(val);
distance = val - i; // the decimal
}
// Loop down along the ordinal positions. When the linear
// equivalent of i matches an ordinal position, interpolate
// between the left and right values.
}
else {
while (i--) {
linearEquivalentLeft =
(ordinalSlope * i) + ordinalOffset;
if (val >= linearEquivalentLeft) {
linearEquivalentRight =
(ordinalSlope *
(i + 1)) +
ordinalOffset;
// something between 0 and 1
distance = (val - linearEquivalentLeft) /
(linearEquivalentRight - linearEquivalentLeft);
break;
}
}
}
// If the index is within the range of the ordinal positions,
// return the associated or interpolated value. If not, just
// return the value.
return (typeof distance !== 'undefined' &&
typeof ordinalPositions[i] !== 'undefined' ?
ordinalPositions[i] + (distance ?
distance *
(ordinalPositions[i + 1] - ordinalPositions[i]) :
0) :
val);
}
return ret;
};
/**
* Translate from a linear axis value to the corresponding ordinal axis
* position. If there are no gaps in the ordinal axis this will be the
* same. The translated value is the value that the point would have if
* the axis were linear, using the same min and max.
*
* @private
* @function Highcharts.Axis#val2lin
*
* @param {number} val
* The axis value.
*
* @param {boolean} [toIndex]
* Whether to return the index in the ordinalPositions or the new value.
*
* @return {number}
*/
axisProto.val2lin = function (val, toIndex) {
var axis = this, ordinal = axis.ordinal, ordinalPositions = ordinal.positions, ret;
if (!ordinalPositions) {
ret = val;
}
else {
var ordinalLength = ordinalPositions.length, i, distance, ordinalIndex;
// first look for an exact match in the ordinalpositions array
i = ordinalLength;
while (i--) {
if (ordinalPositions[i] === val) {
ordinalIndex = i;
break;
}
}
// if that failed, find the intermediate position between the
// two nearest values
i = ordinalLength - 1;
while (i--) {
if (val > ordinalPositions[i] || i === 0) { // interpolate
// something between 0 and 1
distance = (val - ordinalPositions[i]) /
(ordinalPositions[i + 1] - ordinalPositions[i]);
ordinalIndex = i + distance;
break;
}
}
ret = toIndex ?
ordinalIndex :
ordinal.slope *
(ordinalIndex || 0) +
ordinal.offset;
}
return ret;
};
// Record this to prevent overwriting by broken-axis module (#5979)
axisProto.ordinal2lin = axisProto.val2lin;
/* eslint-disable no-invalid-this */
addEvent(AxisClass, 'afterInit', function () {
var axis = this;
if (!axis.ordinal) {
axis.ordinal = new OrdinalAxis.Composition(axis);
}
});
addEvent(AxisClass, 'foundExtremes', function () {
var axis = this;
if (axis.isXAxis &&
defined(axis.options.overscroll) &&
axis.max === axis.dataMax &&
(
// Panning is an execption. We don't want to apply
// overscroll when panning over the dataMax
!axis.chart.mouseIsDown ||
axis.isInternal) && (
// Scrollbar buttons are the other execption:
!axis.eventArgs ||
axis.eventArgs && axis.eventArgs.trigger !== 'navigator')) {
axis.max += axis.options.overscroll;
// Live data and buttons require translation for the min:
if (!axis.isInternal && defined(axis.userMin)) {
axis.min += axis.options.overscroll;
}
}
});
// For ordinal axis, that loads data async, redraw axis after data is
// loaded. If we don't do that, axis will have the same extremes as
// previously, but ordinal positions won't be calculated. See #10290
addEvent(AxisClass, 'afterSetScale', function () {
var axis = this;
if (axis.horiz && !axis.isDirty) {
axis.isDirty = axis.isOrdinal &&
axis.chart.navigator &&
!axis.chart.navigator.adaptToUpdatedData;
}
});
addEvent(AxisClass, 'initialAxisTranslation', function () {
var axis = this;
if (axis.ordinal) {
axis.ordinal.beforeSetTickPositions();
axis.tickInterval = axis.ordinal.postProcessTickInterval(axis.tickInterval);
}
});
// Extending the Chart.pan method for ordinal axes
addEvent(ChartClass, 'pan', function (e) {
var chart = this, xAxis = chart.xAxis[0], overscroll = xAxis.options.overscroll, chartX = e.originalEvent.chartX, panning = chart.options.chart &&
chart.options.chart.panning, runBase = false;
if (panning &&
panning.type !== 'y' &&
xAxis.options.ordinal &&
xAxis.series.length) {
var mouseDownX = chart.mouseDownX, extremes = xAxis.getExtremes(), dataMax = extremes.dataMax, min = extremes.min, max = extremes.max, trimmedRange, hoverPoints = chart.hoverPoints, closestPointRange = (xAxis.closestPointRange ||
(xAxis.ordinal && xAxis.ordinal.overscrollPointsRange)), pointPixelWidth = (xAxis.translationSlope *
(xAxis.ordinal.slope || closestPointRange)),
// how many ordinal units did we move?
movedUnits = (mouseDownX - chartX) / pointPixelWidth,
// get index of all the chart's points
extendedAxis = { ordinal: { positions: xAxis.ordinal.getExtendedPositions() } }, ordinalPositions, searchAxisLeft, lin2val = xAxis.lin2val, val2lin = xAxis.val2lin, searchAxisRight;
// we have an ordinal axis, but the data is equally spaced
if (!extendedAxis.ordinal.positions) {
runBase = true;
}
else if (Math.abs(movedUnits) > 1) {
// Remove active points for shared tooltip
if (hoverPoints) {
hoverPoints.forEach(function (point) {
point.setState();
});
}
if (movedUnits < 0) {
searchAxisLeft = extendedAxis;
searchAxisRight = xAxis.ordinal.positions ? xAxis : extendedAxis;
}
else {
searchAxisLeft = xAxis.ordinal.positions ? xAxis : extendedAxis;
searchAxisRight = extendedAxis;
}
// In grouped data series, the last ordinal position
// represents the grouped data, which is to the left of the
// real data max. If we don't compensate for this, we will
// be allowed to pan grouped data series passed the right of
// the plot area.
ordinalPositions = searchAxisRight.ordinal.positions;
if (dataMax >
ordinalPositions[ordinalPositions.length - 1]) {
ordinalPositions.push(dataMax);
}
// Get the new min and max values by getting the ordinal
// index for the current extreme, then add the moved units
// and translate back to values. This happens on the
// extended ordinal positions if the new position is out of
// range, else it happens on the current x axis which is
// smaller and faster.
chart.fixedRange = max - min;
trimmedRange = xAxis.navigatorAxis.toFixedRange(null, null, lin2val.apply(searchAxisLeft, [
val2lin.apply(searchAxisLeft, [min, true]) + movedUnits,
true // translate from index
]), lin2val.apply(searchAxisRight, [
val2lin.apply(searchAxisRight, [max, true]) + movedUnits,
true // translate from index
]));
// Apply it if it is within the available data range
if (trimmedRange.min >= Math.min(extremes.dataMin, min) &&
trimmedRange.max <= Math.max(dataMax, max) + overscroll) {
xAxis.setExtremes(trimmedRange.min, trimmedRange.max, true, false, { trigger: 'pan' });
}
chart.mouseDownX = chartX; // set new reference for next run
css(chart.container, { cursor: 'move' });
}
}
else {
runBase = true;
}
// revert to the linear chart.pan version
if (runBase || (panning && /y/.test(panning.type))) {
if (overscroll) {
xAxis.max = xAxis.dataMax + overscroll;
}
}
else {
e.preventDefault();
}
});
addEvent(SeriesClass, 'updatedData', function () {
var xAxis = this.xAxis;
// Destroy the extended ordinal index on updated data
if (xAxis && xAxis.options.ordinal) {
delete xAxis.ordinal.index;
}
});
/* eslint-enable no-invalid-this */
}
OrdinalAxis.compose = compose;
})(OrdinalAxis || (OrdinalAxis = {}));
OrdinalAxis.compose(Axis, Chart, Series); // @todo move to StockChart, remove from master
export default OrdinalAxis;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,863 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Axis from './Axis.js';
import Tick from './Tick.js';
import HiddenAxis from './HiddenAxis.js';
import U from '../Utilities.js';
var addEvent = U.addEvent, correctFloat = U.correctFloat, defined = U.defined, extend = U.extend, fireEvent = U.fireEvent, isNumber = U.isNumber, merge = U.merge, pick = U.pick, pInt = U.pInt, relativeLength = U.relativeLength, wrap = U.wrap;
/**
* @private
* @class
*/
var RadialAxis = /** @class */ (function () {
function RadialAxis() {
}
/* *
*
* Static Functions
*
* */
RadialAxis.init = function (axis) {
var axisProto = Axis.prototype;
// Merge and set options.
axis.setOptions = function (userOptions) {
var options = this.options = merge(axis.constructor.defaultOptions, this.defaultPolarOptions, userOptions);
// Make sure the plotBands array is instanciated for each Axis
// (#2649)
if (!options.plotBands) {
options.plotBands = [];
}
fireEvent(this, 'afterSetOptions');
};
// Wrap the getOffset method to return zero offset for title or labels
// in a radial axis.
axis.getOffset = function () {
// Call the Axis prototype method (the method we're in now is on the
// instance)
axisProto.getOffset.call(this);
// Title or label offsets are not counted
this.chart.axisOffset[this.side] = 0;
};
/**
* Get the path for the axis line. This method is also referenced in the
* getPlotLinePath method.
*
* @private
*
* @param {number} _lineWidth
* Line width is not used.
*
* @param {number} [radius]
* Radius of radial path.
*
* @param {number} [innerRadius]
* Inner radius of radial path.
*
* @return {RadialAxisPath}
*/
axis.getLinePath = function (_lineWidth, radius, innerRadius) {
var center = this.pane.center, end, chart = this.chart, r = pick(radius, center[2] / 2 - this.offset), path;
if (typeof innerRadius === 'undefined') {
innerRadius = this.horiz ? 0 : this.center && -this.center[3] / 2;
}
// In case when innerSize of pane is set, it must be included
if (innerRadius) {
r += innerRadius;
}
if (this.isCircular || typeof radius !== 'undefined') {
path = this.chart.renderer.symbols.arc(this.left + center[0], this.top + center[1], r, r, {
start: this.startAngleRad,
end: this.endAngleRad,
open: true,
innerR: 0
});
// Bounds used to position the plotLine label next to the line
// (#7117)
path.xBounds = [this.left + center[0]];
path.yBounds = [this.top + center[1] - r];
}
else {
end = this.postTranslate(this.angleRad, r);
path = [
['M', this.center[0] + chart.plotLeft, this.center[1] + chart.plotTop],
['L', end.x, end.y]
];
}
return path;
};
/**
* Override setAxisTranslation by setting the translation to the
* difference in rotation. This allows the translate method to return
* angle for any given value.
*
* @private
*/
axis.setAxisTranslation = function () {
// Call uber method
axisProto.setAxisTranslation.call(this);
// Set transA and minPixelPadding
if (this.center) { // it's not defined the first time
if (this.isCircular) {
this.transA = (this.endAngleRad - this.startAngleRad) /
((this.max - this.min) || 1);
}
else {
// The transA here is the length of the axis, so in case
// of inner radius, the length must be decreased by it
this.transA = ((this.center[2] - this.center[3]) / 2) /
((this.max - this.min) || 1);
}
if (this.isXAxis) {
this.minPixelPadding = this.transA * this.minPointOffset;
}
else {
// This is a workaround for regression #2593, but categories
// still don't position correctly.
this.minPixelPadding = 0;
}
}
};
/**
* In case of auto connect, add one closestPointRange to the max value
* right before tickPositions are computed, so that ticks will extend
* passed the real max.
* @private
*/
axis.beforeSetTickPositions = function () {
// If autoConnect is true, polygonal grid lines are connected, and
// one closestPointRange is added to the X axis to prevent the last
// point from overlapping the first.
this.autoConnect = (this.isCircular &&
typeof pick(this.userMax, this.options.max) === 'undefined' &&
correctFloat(this.endAngleRad - this.startAngleRad) ===
correctFloat(2 * Math.PI));
// This will lead to add an extra tick to xAxis in order to display
// a correct range on inverted polar
if (!this.isCircular && this.chart.inverted) {
this.max++;
}
if (this.autoConnect) {
this.max += ((this.categories && 1) ||
this.pointRange ||
this.closestPointRange ||
0); // #1197, #2260
}
};
/**
* Override the setAxisSize method to use the arc's circumference as
* length. This allows tickPixelInterval to apply to pixel lengths along
* the perimeter.
* @private
*/
axis.setAxisSize = function () {
var center, start;
axisProto.setAxisSize.call(this);
if (this.isRadial) {
// Set the center array
this.pane.updateCenter(this);
// In case when the innerSize is set in a polar chart, the axis'
// center cannot be a reference to pane's center
center = this.center = extend([], this.pane.center);
// The sector is used in Axis.translate to compute the
// translation of reversed axis points (#2570)
if (this.isCircular) {
this.sector = this.endAngleRad - this.startAngleRad;
}
else {
// When the pane's startAngle or the axis' angle is set then
// new x and y values for vertical axis' center must be
// calulated
start = this.postTranslate(this.angleRad, center[3] / 2);
center[0] = start.x - this.chart.plotLeft;
center[1] = start.y - this.chart.plotTop;
}
// Axis len is used to lay out the ticks
this.len = this.width = this.height =
(center[2] - center[3]) * pick(this.sector, 1) / 2;
}
};
/**
* Returns the x, y coordinate of a point given by a value and a pixel
* distance from center.
*
* @private
*
* @param {number} value
* Point value.
*
* @param {number} [length]
* Distance from center.
*
* @return {Highcharts.PositionObject}
*/
axis.getPosition = function (value, length) {
var translatedVal = this.translate(value);
return this.postTranslate(this.isCircular ? translatedVal : this.angleRad, // #2848
// In case when translatedVal is negative, the 0 value must be
// used instead, in order to deal with lines and labels that
// fall out of the visible range near the center of a pane
pick(this.isCircular ?
length :
(translatedVal < 0 ? 0 : translatedVal), this.center[2] / 2) - this.offset);
};
/**
* Translate from intermediate plotX (angle), plotY (axis.len - radius)
* to final chart coordinates.
*
* @private
*
* @param {number} angle
* Translation angle.
*
* @param {number} radius
* Translation radius.
*
* @return {Highcharts.PositionObject}
*/
axis.postTranslate = function (angle, radius) {
var chart = this.chart, center = this.center;
angle = this.startAngleRad + angle;
return {
x: chart.plotLeft + center[0] + Math.cos(angle) * radius,
y: chart.plotTop + center[1] + Math.sin(angle) * radius
};
};
/**
* Find the path for plot bands along the radial axis.
*
* @private
*
* @param {number} from
* From value.
*
* @param {number} to
* To value.
*
* @param {Highcharts.AxisPlotBandsOptions} options
* Band options.
*
* @return {RadialAxisPath}
*/
axis.getPlotBandPath = function (from, to, options) {
var radiusToPixels = function (radius) {
if (typeof radius === 'string') {
var r = parseInt(radius, 10);
if (percentRegex.test(radius)) {
r = (r * fullRadius) / 100;
}
return r;
}
return radius;
};
var center = this.center, startAngleRad = this.startAngleRad, fullRadius = center[2] / 2, offset = Math.min(this.offset, 0), percentRegex = /%$/, start, end, angle, xOnPerimeter, open, isCircular = this.isCircular, // X axis in a polar chart
path, outerRadius = pick(radiusToPixels(options.outerRadius), fullRadius), innerRadius = radiusToPixels(options.innerRadius), thickness = pick(radiusToPixels(options.thickness), 10);
// Polygonal plot bands
if (this.options.gridLineInterpolation === 'polygon') {
path = this.getPlotLinePath({ value: from }).concat(this.getPlotLinePath({ value: to, reverse: true }));
// Circular grid bands
}
else {
// Keep within bounds
from = Math.max(from, this.min);
to = Math.min(to, this.max);
var transFrom = this.translate(from);
var transTo = this.translate(to);
// Plot bands on Y axis (radial axis) - inner and outer
// radius depend on to and from
if (!isCircular) {
outerRadius = transFrom || 0;
innerRadius = transTo || 0;
}
// Handle full circle
if (options.shape === 'circle' || !isCircular) {
start = -Math.PI / 2;
end = Math.PI * 1.5;
open = true;
}
else {
start = startAngleRad + (transFrom || 0);
end = startAngleRad + (transTo || 0);
}
outerRadius -= offset; // #5283
thickness -= offset; // #5283
path = this.chart.renderer.symbols.arc(this.left + center[0], this.top + center[1], outerRadius, outerRadius, {
// Math is for reversed yAxis (#3606)
start: Math.min(start, end),
end: Math.max(start, end),
innerR: pick(innerRadius, outerRadius - thickness),
open: open
});
// Provide positioning boxes for the label (#6406)
if (isCircular) {
angle = (end + start) / 2;
xOnPerimeter = (this.left +
center[0] +
(center[2] / 2) * Math.cos(angle));
path.xBounds = angle > -Math.PI / 2 && angle < Math.PI / 2 ?
// Right hemisphere
[xOnPerimeter, this.chart.plotWidth] :
// Left hemisphere
[0, xOnPerimeter];
path.yBounds = [
this.top + center[1] + (center[2] / 2) * Math.sin(angle)
];
// Shift up or down to get the label clear of the perimeter
path.yBounds[0] += ((angle > -Math.PI && angle < 0) ||
(angle > Math.PI)) ? -10 : 10;
}
}
return path;
};
// Find the correct end values of crosshair in polar.
axis.getCrosshairPosition = function (options, x1, y1) {
var axis = this, value = options.value, center = axis.pane.center, shapeArgs, end, x2, y2;
if (axis.isCircular) {
if (!defined(value)) {
// When the snap is set to false
x2 = options.chartX || 0;
y2 = options.chartY || 0;
value = axis.translate(Math.atan2(y2 - y1, x2 - x1) - axis.startAngleRad, true);
}
else if (options.point) {
// When the snap is set to true
shapeArgs = options.point.shapeArgs || {};
if (shapeArgs.start) {
// Find a true value of the point based on the
// angle
value = axis.chart.inverted ?
axis.translate(options.point.rectPlotY, true) :
options.point.x;
}
}
end = axis.getPosition(value);
x2 = end.x;
y2 = end.y;
}
else {
if (!defined(value)) {
x2 = options.chartX;
y2 = options.chartY;
}
if (defined(x2) && defined(y2)) {
// Calculate radius of non-circular axis' crosshair
y1 = center[1] + axis.chart.plotTop;
value = axis.translate(Math.min(Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)), center[2] / 2) - center[3] / 2, true);
}
}
return [value, x2 || 0, y2 || 0];
};
// Find the path for plot lines perpendicular to the radial axis.
axis.getPlotLinePath = function (options) {
var axis = this, center = axis.pane.center, chart = axis.chart, inverted = chart.inverted, value = options.value, reverse = options.reverse, end = axis.getPosition(value), background = axis.pane.options.background ?
(axis.pane.options.background[0] ||
axis.pane.options.background) :
{}, innerRadius = background.innerRadius || '0%', outerRadius = background.outerRadius || '100%', x1 = center[0] + chart.plotLeft, y1 = center[1] + chart.plotTop, x2 = end.x, y2 = end.y, height = axis.height, isCrosshair = options.isCrosshair, paneInnerR = center[3] / 2, innerRatio, distance, a, b, otherAxis, xy, tickPositions, crossPos, path;
// Crosshair logic
if (isCrosshair) {
// Find crosshair's position and perform destructuring
// assignment
crossPos = this.getCrosshairPosition(options, x1, y1);
value = crossPos[0];
x2 = crossPos[1];
y2 = crossPos[2];
}
// Spokes
if (axis.isCircular) {
distance =
Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
a = (typeof innerRadius === 'string') ?
relativeLength(innerRadius, 1) : (innerRadius / distance);
b = (typeof outerRadius === 'string') ?
relativeLength(outerRadius, 1) : (outerRadius / distance);
// To ensure that gridlines won't be displayed in area
// defined by innerSize in case of custom radiuses of pane's
// background
if (center && paneInnerR) {
innerRatio = paneInnerR / distance;
if (a < innerRatio) {
a = innerRatio;
}
if (b < innerRatio) {
b = innerRatio;
}
}
path = [
['M', x1 + a * (x2 - x1), y1 - a * (y1 - y2)],
['L', x2 - (1 - b) * (x2 - x1), y2 + (1 - b) * (y1 - y2)]
];
// Concentric circles
}
else {
// Pick the right values depending if it is grid line or
// crosshair
value = axis.translate(value);
// This is required in case when xAxis is non-circular to
// prevent grid lines (or crosshairs, if enabled) from
// rendering above the center after they supposed to be
// displayed below the center point
if (value) {
if (value < 0 || value > height) {
value = 0;
}
}
if (axis.options.gridLineInterpolation === 'circle') {
// A value of 0 is in the center, so it won't be
// visible, but draw it anyway for update and animation
// (#2366)
path = axis.getLinePath(0, value, paneInnerR);
// Concentric polygons
}
else {
path = [];
// Find the other axis (a circular one) in the same pane
chart[inverted ? 'yAxis' : 'xAxis'].forEach(function (a) {
if (a.pane === axis.pane) {
otherAxis = a;
}
});
if (otherAxis) {
tickPositions = otherAxis.tickPositions;
if (otherAxis.autoConnect) {
tickPositions =
tickPositions.concat([tickPositions[0]]);
}
// Reverse the positions for concatenation of polygonal
// plot bands
if (reverse) {
tickPositions = tickPositions.slice().reverse();
}
if (value) {
value += paneInnerR;
}
for (var i = 0; i < tickPositions.length; i++) {
xy = otherAxis.getPosition(tickPositions[i], value);
path.push(i ? ['L', xy.x, xy.y] : ['M', xy.x, xy.y]);
}
}
}
}
return path;
};
// Find the position for the axis title, by default inside the gauge.
axis.getTitlePosition = function () {
var center = this.center, chart = this.chart, titleOptions = this.options.title;
return {
x: chart.plotLeft + center[0] + (titleOptions.x || 0),
y: (chart.plotTop +
center[1] -
({
high: 0.5,
middle: 0.25,
low: 0
}[titleOptions.align] *
center[2]) +
(titleOptions.y || 0))
};
};
/**
* Attach and return collecting function for labels in radial axis for
* anti-collision.
*
* @private
*
* @return {Highcharts.ChartLabelCollectorFunction}
*/
axis.createLabelCollector = function () {
var axis = this;
return function () {
if (axis.isRadial &&
axis.tickPositions &&
// undocumented option for now, but working
axis.options.labels.allowOverlap !== true) {
return axis.tickPositions
.map(function (pos) {
return axis.ticks[pos] && axis.ticks[pos].label;
})
.filter(function (label) {
return Boolean(label);
});
}
};
};
};
/**
* Augments methods for the value axis.
*
* @private
*
* @param {Highcharts.Axis} AxisClass
* Axis class to extend.
*
* @param {Highcharts.Tick} TickClass
* Tick class to use.
*/
RadialAxis.compose = function (AxisClass, TickClass) {
/* eslint-disable no-invalid-this */
// Actions before axis init.
addEvent(AxisClass, 'init', function (e) {
var axis = this;
var chart = axis.chart;
var inverted = chart.inverted, angular = chart.angular, polar = chart.polar, isX = axis.isXAxis, coll = axis.coll, isHidden = angular && isX, isCircular, chartOptions = chart.options, paneIndex = e.userOptions.pane || 0, pane = this.pane =
chart.pane && chart.pane[paneIndex];
// Prevent changes for colorAxis
if (coll === 'colorAxis') {
this.isRadial = false;
return;
}
// Before prototype.init
if (angular) {
if (isHidden) {
HiddenAxis.init(axis);
}
else {
RadialAxis.init(axis);
}
isCircular = !isX;
if (isCircular) {
axis.defaultPolarOptions = RadialAxis.defaultRadialGaugeOptions;
}
}
else if (polar) {
RadialAxis.init(axis);
// Check which axis is circular
isCircular = axis.horiz;
axis.defaultPolarOptions = isCircular ?
RadialAxis.defaultCircularOptions :
merge(coll === 'xAxis' ?
AxisClass.defaultOptions :
AxisClass.defaultYAxisOptions, RadialAxis.defaultRadialOptions);
// Apply the stack labels for yAxis in case of inverted chart
if (inverted && coll === 'yAxis') {
axis.defaultPolarOptions.stackLabels = AxisClass.defaultYAxisOptions.stackLabels;
}
}
// Disable certain features on angular and polar axes
if (angular || polar) {
axis.isRadial = true;
chartOptions.chart.zoomType = null;
if (!axis.labelCollector) {
axis.labelCollector = axis.createLabelCollector();
}
if (axis.labelCollector) {
// Prevent overlapping axis labels (#9761)
chart.labelCollectors.push(axis.labelCollector);
}
}
else {
this.isRadial = false;
}
// A pointer back to this axis to borrow geometry
if (pane && isCircular) {
pane.axis = axis;
}
axis.isCircular = isCircular;
});
addEvent(AxisClass, 'afterInit', function () {
var axis = this;
var chart = axis.chart, options = axis.options, isHidden = chart.angular && axis.isXAxis, pane = axis.pane, paneOptions = pane && pane.options;
if (!isHidden && pane && (chart.angular || chart.polar)) {
// Start and end angle options are given in degrees relative to
// top, while internal computations are in radians relative to
// right (like SVG).
// Y axis in polar charts
axis.angleRad = (options.angle || 0) * Math.PI / 180;
// Gauges
axis.startAngleRad =
(paneOptions.startAngle - 90) * Math.PI / 180;
axis.endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360) - 90) * Math.PI / 180; // Gauges
axis.offset = options.offset || 0;
}
});
// Wrap auto label align to avoid setting axis-wide rotation on radial
// axes. (#4920)
addEvent(AxisClass, 'autoLabelAlign', function (e) {
if (this.isRadial) {
e.align = void 0;
e.preventDefault();
}
});
// Remove label collector function on axis remove/update
addEvent(AxisClass, 'destroy', function () {
var axis = this;
if (axis.chart &&
axis.chart.labelCollectors) {
var index = (axis.labelCollector ?
axis.chart.labelCollectors.indexOf(axis.labelCollector) :
-1);
if (index >= 0) {
axis.chart.labelCollectors.splice(index, 1);
}
}
});
addEvent(AxisClass, 'initialAxisTranslation', function () {
var axis = this;
if (axis.isRadial) {
axis.beforeSetTickPositions();
}
});
// Add special cases within the Tick class' methods for radial axes.
addEvent(TickClass, 'afterGetPosition', function (e) {
var tick = this;
if (tick.axis.getPosition) {
extend(e.pos, tick.axis.getPosition(this.pos));
}
});
// Find the center position of the label based on the distance option.
addEvent(TickClass, 'afterGetLabelPosition', function (e) {
var tick = this;
var axis = tick.axis;
var label = tick.label;
if (!label) {
return;
}
var labelBBox = label.getBBox(), labelOptions = axis.options.labels, optionsY = labelOptions.y, ret, centerSlot = 20, // 20 degrees to each side at the top and bottom
align = labelOptions.align, angle = ((axis.translate(this.pos) + axis.startAngleRad +
Math.PI / 2) / Math.PI * 180) % 360, correctAngle = Math.round(angle), labelDir = 'end', // Direction of the label 'start' or 'end'
reducedAngle1 = correctAngle < 0 ?
correctAngle + 360 : correctAngle, reducedAngle2 = reducedAngle1, translateY = 0, translateX = 0, labelYPosCorrection = labelOptions.y === null ? -labelBBox.height * 0.3 : 0;
if (axis.isRadial) { // Both X and Y axes in a polar chart
ret = axis.getPosition(this.pos, (axis.center[2] / 2) +
relativeLength(pick(labelOptions.distance, -25), axis.center[2] / 2, -axis.center[2] / 2));
// Automatically rotated
if (labelOptions.rotation === 'auto') {
label.attr({
rotation: angle
});
// Vertically centered
}
else if (optionsY === null) {
optionsY = (axis.chart.renderer
.fontMetrics(label.styles && label.styles.fontSize).b -
labelBBox.height / 2);
}
// Automatic alignment
if (align === null) {
if (axis.isCircular) { // Y axis
if (labelBBox.width >
axis.len * axis.tickInterval / (axis.max - axis.min)) { // #3506
centerSlot = 0;
}
if (angle > centerSlot && angle < 180 - centerSlot) {
align = 'left'; // right hemisphere
}
else if (angle > 180 + centerSlot &&
angle < 360 - centerSlot) {
align = 'right'; // left hemisphere
}
else {
align = 'center'; // top or bottom
}
}
else {
align = 'center';
}
label.attr({
align: align
});
}
// Auto alignment for solid-gauges with two labels (#10635)
if (align === 'auto' &&
axis.tickPositions.length === 2 &&
axis.isCircular) {
// Angles reduced to 0 - 90 or 180 - 270
if (reducedAngle1 > 90 && reducedAngle1 < 180) {
reducedAngle1 = 180 - reducedAngle1;
}
else if (reducedAngle1 > 270 && reducedAngle1 <= 360) {
reducedAngle1 = 540 - reducedAngle1;
}
// Angles reduced to 0 - 180
if (reducedAngle2 > 180 && reducedAngle2 <= 360) {
reducedAngle2 = 360 - reducedAngle2;
}
if ((axis.pane.options.startAngle === correctAngle) ||
(axis.pane.options.startAngle === correctAngle + 360) ||
(axis.pane.options.startAngle === correctAngle - 360)) {
labelDir = 'start';
}
if ((correctAngle >= -90 && correctAngle <= 90) ||
(correctAngle >= -360 && correctAngle <= -270) ||
(correctAngle >= 270 && correctAngle <= 360)) {
align = (labelDir === 'start') ? 'right' : 'left';
}
else {
align = (labelDir === 'start') ? 'left' : 'right';
}
// For angles beetwen (90 + n * 180) +- 20
if (reducedAngle2 > 70 && reducedAngle2 < 110) {
align = 'center';
}
// auto Y translation
if (reducedAngle1 < 15 ||
(reducedAngle1 >= 180 && reducedAngle1 < 195)) {
translateY = labelBBox.height * 0.3;
}
else if (reducedAngle1 >= 15 && reducedAngle1 <= 35) {
translateY = labelDir === 'start' ?
0 : labelBBox.height * 0.75;
}
else if (reducedAngle1 >= 195 && reducedAngle1 <= 215) {
translateY = labelDir === 'start' ?
labelBBox.height * 0.75 : 0;
}
else if (reducedAngle1 > 35 && reducedAngle1 <= 90) {
translateY = labelDir === 'start' ?
-labelBBox.height * 0.25 : labelBBox.height;
}
else if (reducedAngle1 > 215 && reducedAngle1 <= 270) {
translateY = labelDir === 'start' ?
labelBBox.height : -labelBBox.height * 0.25;
}
// auto X translation
if (reducedAngle2 < 15) {
translateX = labelDir === 'start' ?
-labelBBox.height * 0.15 : labelBBox.height * 0.15;
}
else if (reducedAngle2 > 165 && reducedAngle2 <= 180) {
translateX = labelDir === 'start' ?
labelBBox.height * 0.15 : -labelBBox.height * 0.15;
}
label.attr({ align: align });
label.translate(translateX, translateY + labelYPosCorrection);
}
e.pos.x = ret.x + labelOptions.x;
e.pos.y = ret.y + optionsY;
}
});
// Wrap the getMarkPath function to return the path of the radial marker
wrap(TickClass.prototype, 'getMarkPath', function (proceed, x, y, tickLength, tickWidth, horiz, renderer) {
var tick = this;
var axis = tick.axis;
var endPoint, ret;
if (axis.isRadial) {
endPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength);
ret = [
'M',
x,
y,
'L',
endPoint.x,
endPoint.y
];
}
else {
ret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer);
}
return ret;
});
};
/* *
*
* Static Properties
*
* */
/**
* Circular axis around the perimeter of a polar chart.
* @private
*/
RadialAxis.defaultCircularOptions = {
gridLineWidth: 1,
labels: {
align: null,
distance: 15,
x: 0,
y: null,
style: {
textOverflow: 'none' // wrap lines by default (#7248)
}
},
maxPadding: 0,
minPadding: 0,
showLastLabel: false,
tickLength: 0
};
/**
* The default options extend defaultYAxisOptions.
* @private
*/
RadialAxis.defaultRadialGaugeOptions = {
labels: {
align: 'center',
x: 0,
y: null // auto
},
minorGridLineWidth: 0,
minorTickInterval: 'auto',
minorTickLength: 10,
minorTickPosition: 'inside',
minorTickWidth: 1,
tickLength: 10,
tickPosition: 'inside',
tickWidth: 2,
title: {
rotation: 0
},
zIndex: 2 // behind dials, points in the series group
};
/**
* Radial axis, like a spoke in a polar chart.
* @private
*/
RadialAxis.defaultRadialOptions = {
/**
* In a polar chart, this is the angle of the Y axis in degrees, where
* 0 is up and 90 is right. The angle determines the position of the
* axis line and the labels, though the coordinate system is unaffected.
* Since v8.0.0 this option is also applicable for X axis (inverted
* polar).
*
* @sample {highcharts} highcharts/xaxis/angle/
* Custom X axis' angle on inverted polar chart
* @sample {highcharts} highcharts/yaxis/angle/
* Dual axis polar chart
*
* @type {number}
* @default 0
* @since 4.2.7
* @product highcharts
* @apioption xAxis.angle
*/
/**
* Polar charts only. Whether the grid lines should draw as a polygon
* with straight lines between categories, or as circles. Can be either
* `circle` or `polygon`. Since v8.0.0 this option is also applicable
* for X axis (inverted polar).
*
* @sample {highcharts} highcharts/demo/polar-spider/
* Polygon grid lines
* @sample {highcharts} highcharts/xaxis/gridlineinterpolation/
* Circle and polygon on inverted polar
* @sample {highcharts} highcharts/yaxis/gridlineinterpolation/
* Circle and polygon
*
* @type {string}
* @product highcharts
* @validvalue ["circle", "polygon"]
* @apioption xAxis.gridLineInterpolation
*/
gridLineInterpolation: 'circle',
gridLineWidth: 1,
labels: {
align: 'right',
x: -3,
y: -2
},
showLastLabel: false,
title: {
x: 4,
text: null,
rotation: 90
}
};
return RadialAxis;
}());
RadialAxis.compose(Axis, Tick); // @todo move outside
export default RadialAxis;
@@ -0,0 +1,151 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
import H from '../Globals.js';
import U from '../Utilities.js';
var addEvent = U.addEvent, defined = U.defined, pick = U.pick;
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* Creates scrollbars if enabled.
*
* @private
*/
var ScrollbarAxis = /** @class */ (function () {
function ScrollbarAxis() {
}
/**
* Attaches to axis events to create scrollbars if enabled.
*
* @private
*
* @param AxisClass
* Axis class to extend.
*
* @param ScrollbarClass
* Scrollbar class to use.
*/
ScrollbarAxis.compose = function (AxisClass, ScrollbarClass) {
// Wrap axis initialization and create scrollbar if enabled:
addEvent(AxisClass, 'afterInit', function () {
var axis = this;
if (axis.options &&
axis.options.scrollbar &&
axis.options.scrollbar.enabled) {
// Predefined options:
axis.options.scrollbar.vertical = !axis.horiz;
axis.options.startOnTick = axis.options.endOnTick = false;
axis.scrollbar = new ScrollbarClass(axis.chart.renderer, axis.options.scrollbar, axis.chart);
addEvent(axis.scrollbar, 'changed', function (e) {
var axisMin = pick(axis.options && axis.options.min, axis.min), axisMax = pick(axis.options && axis.options.max, axis.max), unitedMin = defined(axis.dataMin) ?
Math.min(axisMin, axis.min, axis.dataMin) : axisMin, unitedMax = defined(axis.dataMax) ?
Math.max(axisMax, axis.max, axis.dataMax) : axisMax, range = unitedMax - unitedMin, to, from;
// #12834, scroll when show/hide series, wrong extremes
if (!defined(axisMin) || !defined(axisMax)) {
return;
}
if ((axis.horiz && !axis.reversed) ||
(!axis.horiz && axis.reversed)) {
to = unitedMin + range * this.to;
from = unitedMin + range * this.from;
}
else {
// y-values in browser are reversed, but this also
// applies for reversed horizontal axis:
to = unitedMin + range * (1 - this.from);
from = unitedMin + range * (1 - this.to);
}
if (pick(this.options.liveRedraw, H.svg && !H.isTouchDevice && !this.chart.isBoosting) ||
// Mouseup always should change extremes
e.DOMType === 'mouseup' ||
// Internal events
!defined(e.DOMType)) {
axis.setExtremes(from, to, true, e.DOMType !== 'mousemove', e);
}
else {
// When live redraw is disabled, don't change extremes
// Only change the position of the scollbar thumb
this.setRange(this.from, this.to);
}
});
}
});
// Wrap rendering axis, and update scrollbar if one is created:
addEvent(AxisClass, 'afterRender', function () {
var axis = this, scrollMin = Math.min(pick(axis.options.min, axis.min), axis.min, pick(axis.dataMin, axis.min) // #6930
), scrollMax = Math.max(pick(axis.options.max, axis.max), axis.max, pick(axis.dataMax, axis.max) // #6930
), scrollbar = axis.scrollbar, offset = axis.axisTitleMargin + (axis.titleOffset || 0), scrollbarsOffsets = axis.chart.scrollbarsOffsets, axisMargin = axis.options.margin || 0, offsetsIndex, from, to;
if (scrollbar) {
if (axis.horiz) {
// Reserve space for labels/title
if (!axis.opposite) {
scrollbarsOffsets[1] += offset;
}
scrollbar.position(axis.left, axis.top + axis.height + 2 + scrollbarsOffsets[1] -
(axis.opposite ? axisMargin : 0), axis.width, axis.height);
// Next scrollbar should reserve space for margin (if set)
if (!axis.opposite) {
scrollbarsOffsets[1] += axisMargin;
}
offsetsIndex = 1;
}
else {
// Reserve space for labels/title
if (axis.opposite) {
scrollbarsOffsets[0] += offset;
}
scrollbar.position(axis.left + axis.width + 2 + scrollbarsOffsets[0] -
(axis.opposite ? 0 : axisMargin), axis.top, axis.width, axis.height);
// Next scrollbar should reserve space for margin (if set)
if (axis.opposite) {
scrollbarsOffsets[0] += axisMargin;
}
offsetsIndex = 0;
}
scrollbarsOffsets[offsetsIndex] += scrollbar.size +
scrollbar.options.margin;
if (isNaN(scrollMin) ||
isNaN(scrollMax) ||
!defined(axis.min) ||
!defined(axis.max) ||
axis.min === axis.max // #10733
) {
// default action: when extremes are the same or there is
// not extremes on the axis, but scrollbar exists, make it
// full size
scrollbar.setRange(0, 1);
}
else {
from =
(axis.min - scrollMin) / (scrollMax - scrollMin);
to =
(axis.max - scrollMin) / (scrollMax - scrollMin);
if ((axis.horiz && !axis.reversed) ||
(!axis.horiz && axis.reversed)) {
scrollbar.setRange(from, to);
}
else {
// inverse vertical axis
scrollbar.setRange(1 - to, 1 - from);
}
}
}
});
// Make space for a scrollbar:
addEvent(AxisClass, 'afterGetOffset', function () {
var axis = this, index = axis.horiz ? 2 : 1, scrollbar = axis.scrollbar;
if (scrollbar) {
axis.chart.scrollbarsOffsets = [0, 0]; // reset scrollbars offsets
axis.chart.axisOffset[index] +=
scrollbar.size + scrollbar.options.margin;
}
});
};
return ScrollbarAxis;
}());
export default ScrollbarAxis;
@@ -0,0 +1,193 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
import U from '../Utilities.js';
var addEvent = U.addEvent, destroyObjectProperties = U.destroyObjectProperties, fireEvent = U.fireEvent, getDeferredAnimation = U.getDeferredAnimation, objectEach = U.objectEach, pick = U.pick;
/* eslint-disable valid-jsdoc */
/**
* Adds stacking support to axes.
* @private
* @class
*/
var StackingAxisAdditions = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function StackingAxisAdditions(axis) {
this.oldStacks = {};
this.stacks = {};
this.stacksTouched = 0;
this.axis = axis;
}
/* *
*
* Functions
*
* */
/**
* Build the stacks from top down
* @private
*/
StackingAxisAdditions.prototype.buildStacks = function () {
var stacking = this;
var axis = stacking.axis;
var axisSeries = axis.series;
var reversedStacks = pick(axis.options.reversedStacks, true);
var len = axisSeries.length;
var actualSeries, i;
if (!axis.isXAxis) {
stacking.usePercentage = false;
i = len;
while (i--) {
actualSeries = axisSeries[reversedStacks ? i : len - i - 1];
actualSeries.setStackedPoints();
actualSeries.setGroupedPoints();
}
// Loop up again to compute percent and stream stack
for (i = 0; i < len; i++) {
axisSeries[i].modifyStacks();
}
fireEvent(axis, 'afterBuildStacks');
}
};
/**
* @private
*/
StackingAxisAdditions.prototype.cleanStacks = function () {
var stacking = this;
var axis = stacking.axis;
var stacks;
if (!axis.isXAxis) {
if (stacking.oldStacks) {
stacks = stacking.stacks = stacking.oldStacks;
}
// reset stacks
objectEach(stacks, function (type) {
objectEach(type, function (stack) {
stack.cumulative = stack.total;
});
});
}
};
/**
* Set all the stacks to initial states and destroy unused ones.
* @private
*/
StackingAxisAdditions.prototype.resetStacks = function () {
var stacking = this;
var axis = stacking.axis;
var stacks = stacking.stacks;
if (!axis.isXAxis) {
objectEach(stacks, function (type) {
objectEach(type, function (stack, key) {
// Clean up memory after point deletion (#1044, #4320)
if (stack.touched < stacking.stacksTouched) {
stack.destroy();
delete type[key];
// Reset stacks
}
else {
stack.total = null;
stack.cumulative = null;
}
});
});
}
};
/**
* @private
*/
StackingAxisAdditions.prototype.renderStackTotals = function () {
var stacking = this;
var axis = stacking.axis;
var chart = axis.chart;
var renderer = chart.renderer;
var stacks = stacking.stacks;
var stackLabelsAnim = axis.options.stackLabels.animation;
var animationConfig = getDeferredAnimation(chart, stackLabelsAnim);
var stackTotalGroup = stacking.stackTotalGroup = (stacking.stackTotalGroup ||
renderer
.g('stack-labels')
.attr({
visibility: 'visible',
zIndex: 6,
opacity: 0
})
.add());
// plotLeft/Top will change when y axis gets wider so we need to
// translate the stackTotalGroup at every render call. See bug #506
// and #516
stackTotalGroup.translate(chart.plotLeft, chart.plotTop);
// Render each stack total
objectEach(stacks, function (type) {
objectEach(type, function (stack) {
stack.render(stackTotalGroup);
});
});
stackTotalGroup.animate({
opacity: 1
}, animationConfig);
};
return StackingAxisAdditions;
}());
/**
* Axis with stacking support.
* @private
* @class
*/
var StackingAxis = /** @class */ (function () {
function StackingAxis() {
}
/* *
*
* Static Functions
*
* */
/**
* Extends axis with stacking support.
* @private
*/
StackingAxis.compose = function (AxisClass) {
var axisProto = AxisClass.prototype;
addEvent(AxisClass, 'init', StackingAxis.onInit);
addEvent(AxisClass, 'destroy', StackingAxis.onDestroy);
};
/**
* @private
*/
StackingAxis.onDestroy = function () {
var stacking = this.stacking;
if (!stacking) {
return;
}
var stacks = stacking.stacks;
// Destroy each stack total
objectEach(stacks, function (stack, stackKey) {
destroyObjectProperties(stack);
stacks[stackKey] = null;
});
if (stacking &&
stacking.stackTotalGroup) {
stacking.stackTotalGroup.destroy();
}
};
/**
* @private
*/
StackingAxis.onInit = function () {
var axis = this;
if (!axis.stacking) {
axis.stacking = new StackingAxisAdditions(axis);
}
};
return StackingAxis;
}());
export default StackingAxis;
+667
View File
@@ -0,0 +1,667 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../Globals.js';
/**
* Optional parameters for the tick.
* @private
* @interface Highcharts.TickParametersObject
*/ /**
* Set category for the tick.
* @name Highcharts.TickParametersObject#category
* @type {string|undefined}
*/ /**
* @name Highcharts.TickParametersObject#options
* @type {Highcharts.Dictionary<any>|undefined}
*/ /**
* Set tickmarkOffset for the tick.
* @name Highcharts.TickParametersObject#tickmarkOffset
* @type {number|undefined}
*/
import U from '../Utilities.js';
var clamp = U.clamp, correctFloat = U.correctFloat, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, extend = U.extend, fireEvent = U.fireEvent, isNumber = U.isNumber, merge = U.merge, objectEach = U.objectEach, pick = U.pick;
var deg2rad = H.deg2rad;
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* The Tick class.
*
* @class
* @name Highcharts.Tick
*
* @param {Highcharts.Axis} axis
* The axis of the tick.
*
* @param {number} pos
* The position of the tick on the axis in terms of axis values.
*
* @param {string} [type]
* The type of tick, either 'minor' or an empty string
*
* @param {boolean} [noLabel=false]
* Whether to disable the label or not. Defaults to false.
*
* @param {object} [parameters]
* Optional parameters for the tick.
*/
var Tick = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function Tick(axis, pos, type, noLabel, parameters) {
this.isNew = true;
this.isNewLabel = true;
/**
* The related axis of the tick.
* @name Highcharts.Tick#axis
* @type {Highcharts.Axis}
*/
this.axis = axis;
/**
* The logical position of the tick on the axis in terms of axis values.
* @name Highcharts.Tick#pos
* @type {number}
*/
this.pos = pos;
/**
* The tick type, which can be `"minor"`, or an empty string.
* @name Highcharts.Tick#type
* @type {string}
*/
this.type = type || '';
this.parameters = parameters || {};
/**
* The mark offset of the tick on the axis. Usually `undefined`, numeric
* for grid axes.
* @name Highcharts.Tick#tickmarkOffset
* @type {number|undefined}
*/
this.tickmarkOffset = this.parameters.tickmarkOffset;
this.options = this.parameters.options;
fireEvent(this, 'init');
if (!type && !noLabel) {
this.addLabel();
}
}
/* *
*
* Functions
*
* */
/**
* Write the tick label.
*
* @private
* @function Highcharts.Tick#addLabel
* @return {void}
*/
Tick.prototype.addLabel = function () {
var tick = this, axis = tick.axis, options = axis.options, chart = axis.chart, categories = axis.categories, log = axis.logarithmic, names = axis.names, pos = tick.pos, labelOptions = pick(tick.options && tick.options.labels, options.labels), str, tickPositions = axis.tickPositions, isFirst = pos === tickPositions[0], isLast = pos === tickPositions[tickPositions.length - 1], value = this.parameters.category || (categories ?
pick(categories[pos], names[pos], pos) :
pos), label = tick.label, animateLabels = (!labelOptions.step || labelOptions.step === 1) &&
axis.tickInterval === 1, tickPositionInfo = tickPositions.info, dateTimeLabelFormat, dateTimeLabelFormats, i, list;
// Set the datetime label format. If a higher rank is set for this
// position, use that. If not, use the general format.
if (axis.dateTime && tickPositionInfo) {
dateTimeLabelFormats = chart.time.resolveDTLFormat(options.dateTimeLabelFormats[(!options.grid &&
tickPositionInfo.higherRanks[pos]) ||
tickPositionInfo.unitName]);
dateTimeLabelFormat = dateTimeLabelFormats.main;
}
// set properties for access in render method
/**
* True if the tick is the first one on the axis.
* @name Highcharts.Tick#isFirst
* @readonly
* @type {boolean|undefined}
*/
tick.isFirst = isFirst;
/**
* True if the tick is the last one on the axis.
* @name Highcharts.Tick#isLast
* @readonly
* @type {boolean|undefined}
*/
tick.isLast = isLast;
// Get the string
tick.formatCtx = {
axis: axis,
chart: chart,
isFirst: isFirst,
isLast: isLast,
dateTimeLabelFormat: dateTimeLabelFormat,
tickPositionInfo: tickPositionInfo,
value: log ? correctFloat(log.lin2log(value)) : value,
pos: pos
};
str = axis.labelFormatter.call(tick.formatCtx, this.formatCtx);
// Set up conditional formatting based on the format list if existing.
list = dateTimeLabelFormats && dateTimeLabelFormats.list;
if (list) {
tick.shortenLabel = function () {
for (i = 0; i < list.length; i++) {
label.attr({
text: axis.labelFormatter.call(extend(tick.formatCtx, { dateTimeLabelFormat: list[i] }))
});
if (label.getBBox().width <
axis.getSlotWidth(tick) - 2 *
pick(labelOptions.padding, 5)) {
return;
}
}
label.attr({
text: ''
});
};
}
// Call only after first render
if (animateLabels && axis._addedPlotLB) {
tick.moveLabel(str, labelOptions);
}
// First call
if (!defined(label) && !tick.movedLabel) {
/**
* The rendered text label of the tick.
* @name Highcharts.Tick#label
* @type {Highcharts.SVGElement|undefined}
*/
tick.label = label = tick.createLabel({ x: 0, y: 0 }, str, labelOptions);
// Base value to detect change for new calls to getBBox
tick.rotation = 0;
// update
}
else if (label && label.textStr !== str && !animateLabels) {
// When resetting text, also reset the width if dynamically set
// (#8809)
if (label.textWidth &&
!(labelOptions.style && labelOptions.style.width) &&
!label.styles.width) {
label.css({ width: null });
}
label.attr({ text: str });
label.textPxLength = label.getBBox().width;
}
};
/**
* Render and return the label of the tick.
*
* @private
* @function Highcharts.Tick#createLabel
* @param {Highcharts.PositionObject} xy
* @param {string} str
* @param {Highcharts.XAxisLabelsOptions} labelOptions
* @return {Highcharts.SVGElement|undefined}
*/
Tick.prototype.createLabel = function (xy, str, labelOptions) {
var axis = this.axis, chart = axis.chart, label = defined(str) && labelOptions.enabled ?
chart.renderer
.text(str, xy.x, xy.y, labelOptions.useHTML)
.add(axis.labelGroup) :
null;
// Un-rotated length
if (label) {
// Without position absolute, IE export sometimes is wrong
if (!chart.styledMode) {
label.css(merge(labelOptions.style));
}
label.textPxLength = label.getBBox().width;
}
return label;
};
/**
* Destructor for the tick prototype
*
* @private
* @function Highcharts.Tick#destroy
* @return {void}
*/
Tick.prototype.destroy = function () {
destroyObjectProperties(this, this.axis);
};
/**
* Gets the x and y positions for ticks in terms of pixels.
*
* @private
* @function Highcharts.Tick#getPosition
*
* @param {boolean} horiz
* Whether the tick is on an horizontal axis or not.
*
* @param {number} tickPos
* Position of the tick.
*
* @param {number} tickmarkOffset
* Tickmark offset for all ticks.
*
* @param {boolean} [old]
* Whether the axis has changed or not.
*
* @return {Highcharts.PositionObject}
* The tick position.
*
* @fires Highcharts.Tick#event:afterGetPosition
*/
Tick.prototype.getPosition = function (horiz, tickPos, tickmarkOffset, old) {
var axis = this.axis, chart = axis.chart, cHeight = (old && chart.oldChartHeight) || chart.chartHeight, pos;
pos = {
x: horiz ?
correctFloat(axis.translate(tickPos + tickmarkOffset, null, null, old) +
axis.transB) :
(axis.left +
axis.offset +
(axis.opposite ?
(((old && chart.oldChartWidth) ||
chart.chartWidth) -
axis.right -
axis.left) :
0)),
y: horiz ?
(cHeight -
axis.bottom +
axis.offset -
(axis.opposite ? axis.height : 0)) :
correctFloat(cHeight -
axis.translate(tickPos + tickmarkOffset, null, null, old) -
axis.transB)
};
// Chrome workaround for #10516
pos.y = clamp(pos.y, -1e5, 1e5);
fireEvent(this, 'afterGetPosition', { pos: pos });
return pos;
};
/**
* Get the x, y position of the tick label
*
* @private
* @return {Highcharts.PositionObject}
*/
Tick.prototype.getLabelPosition = function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
var axis = this.axis, transA = axis.transA, reversed = ( // #7911
axis.isLinked && axis.linkedParent ?
axis.linkedParent.reversed :
axis.reversed), staggerLines = axis.staggerLines, rotCorr = axis.tickRotCorr || { x: 0, y: 0 }, yOffset = labelOptions.y,
// Adjust for label alignment if we use reserveSpace: true (#5286)
labelOffsetCorrection = (!horiz && !axis.reserveSpaceDefault ?
-axis.labelOffset * (axis.labelAlign === 'center' ? 0.5 : 1) :
0), line, pos = {};
if (!defined(yOffset)) {
if (axis.side === 0) {
yOffset = label.rotation ? -8 : -label.getBBox().height;
}
else if (axis.side === 2) {
yOffset = rotCorr.y + 8;
}
else {
// #3140, #3140
yOffset = Math.cos(label.rotation * deg2rad) *
(rotCorr.y - label.getBBox(false, 0).height / 2);
}
}
x = x +
labelOptions.x +
labelOffsetCorrection +
rotCorr.x -
(tickmarkOffset && horiz ?
tickmarkOffset * transA * (reversed ? -1 : 1) :
0);
y = y + yOffset - (tickmarkOffset && !horiz ?
tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
// Correct for staggered labels
if (staggerLines) {
line = (index / (step || 1) % staggerLines);
if (axis.opposite) {
line = staggerLines - line - 1;
}
y += line * (axis.labelOffset / staggerLines);
}
pos.x = x;
pos.y = Math.round(y);
fireEvent(this, 'afterGetLabelPosition', { pos: pos, tickmarkOffset: tickmarkOffset, index: index });
return pos;
};
/**
* Get the offset height or width of the label
*
* @private
* @function Highcharts.Tick#getLabelSize
* @return {number}
*/
Tick.prototype.getLabelSize = function () {
return this.label ?
this.label.getBBox()[this.axis.horiz ? 'height' : 'width'] :
0;
};
/**
* Extendible method to return the path of the marker
*
* @private
*
*/
Tick.prototype.getMarkPath = function (x, y, tickLength, tickWidth, horiz, renderer) {
return renderer.crispLine([[
'M',
x,
y
], [
'L',
x + (horiz ? 0 : -tickLength),
y + (horiz ? tickLength : 0)
]], tickWidth);
};
/**
* Handle the label overflow by adjusting the labels to the left and right
* edge, or hide them if they collide into the neighbour label.
*
* @private
* @function Highcharts.Tick#handleOverflow
* @param {Highcharts.PositionObject} xy
* @return {void}
*/
Tick.prototype.handleOverflow = function (xy) {
var tick = this, axis = this.axis, labelOptions = axis.options.labels, pxPos = xy.x, chartWidth = axis.chart.chartWidth, spacing = axis.chart.spacing, leftBound = pick(axis.labelLeft, Math.min(axis.pos, spacing[3])), rightBound = pick(axis.labelRight, Math.max(!axis.isRadial ? axis.pos + axis.len : 0, chartWidth - spacing[1])), label = this.label, rotation = this.rotation, factor = {
left: 0,
center: 0.5,
right: 1
}[axis.labelAlign || label.attr('align')], labelWidth = label.getBBox().width, slotWidth = axis.getSlotWidth(tick), modifiedSlotWidth = slotWidth, xCorrection = factor, goRight = 1, leftPos, rightPos, textWidth, css = {};
// Check if the label overshoots the chart spacing box. If it does, move
// it. If it now overshoots the slotWidth, add ellipsis.
if (!rotation &&
pick(labelOptions.overflow, 'justify') === 'justify') {
leftPos = pxPos - factor * labelWidth;
rightPos = pxPos + (1 - factor) * labelWidth;
if (leftPos < leftBound) {
modifiedSlotWidth =
xy.x + modifiedSlotWidth * (1 - factor) - leftBound;
}
else if (rightPos > rightBound) {
modifiedSlotWidth =
rightBound - xy.x + modifiedSlotWidth * factor;
goRight = -1;
}
modifiedSlotWidth = Math.min(slotWidth, modifiedSlotWidth); // #4177
if (modifiedSlotWidth < slotWidth && axis.labelAlign === 'center') {
xy.x += (goRight *
(slotWidth -
modifiedSlotWidth -
xCorrection * (slotWidth - Math.min(labelWidth, modifiedSlotWidth))));
}
// If the label width exceeds the available space, set a text width
// to be picked up below. Also, if a width has been set before, we
// need to set a new one because the reported labelWidth will be
// limited by the box (#3938).
if (labelWidth > modifiedSlotWidth ||
(axis.autoRotation && (label.styles || {}).width)) {
textWidth = modifiedSlotWidth;
}
// Add ellipsis to prevent rotated labels to be clipped against the edge
// of the chart
}
else if (rotation < 0 &&
pxPos - factor * labelWidth < leftBound) {
textWidth = Math.round(pxPos / Math.cos(rotation * deg2rad) - leftBound);
}
else if (rotation > 0 &&
pxPos + factor * labelWidth > rightBound) {
textWidth = Math.round((chartWidth - pxPos) /
Math.cos(rotation * deg2rad));
}
if (textWidth) {
if (tick.shortenLabel) {
tick.shortenLabel();
}
else {
css.width = Math.floor(textWidth) + 'px';
if (!(labelOptions.style || {}).textOverflow) {
css.textOverflow = 'ellipsis';
}
label.css(css);
}
}
};
/**
* Try to replace the label if the same one already exists.
*
* @private
* @function Highcharts.Tick#moveLabel
* @param {string} str
* @param {Highcharts.XAxisLabelsOptions} labelOptions
*
* @return {void}
*/
Tick.prototype.moveLabel = function (str, labelOptions) {
var tick = this, label = tick.label, moved = false, axis = tick.axis, labelPos, reversed = axis.reversed, xPos, yPos;
if (label && label.textStr === str) {
tick.movedLabel = label;
moved = true;
delete tick.label;
}
else { // Find a label with the same string
objectEach(axis.ticks, function (currentTick) {
if (!moved &&
!currentTick.isNew &&
currentTick !== tick &&
currentTick.label &&
currentTick.label.textStr === str) {
tick.movedLabel = currentTick.label;
moved = true;
currentTick.labelPos = tick.movedLabel.xy;
delete currentTick.label;
}
});
}
// Create new label if the actual one is moved
if (!moved && (tick.labelPos || label)) {
labelPos = tick.labelPos || label.xy;
xPos = axis.horiz ?
(reversed ? 0 : axis.width + axis.left) : labelPos.x;
yPos = axis.horiz ?
labelPos.y : (reversed ? (axis.width + axis.left) : 0);
tick.movedLabel = tick.createLabel({ x: xPos, y: yPos }, str, labelOptions);
if (tick.movedLabel) {
tick.movedLabel.attr({ opacity: 0 });
}
}
};
/**
* Put everything in place
*
* @private
* @param {number} index
* @param {boolean} [old]
* Use old coordinates to prepare an animation into new position
* @param {number} [opacity]
* @return {voids}
*/
Tick.prototype.render = function (index, old, opacity) {
var tick = this, axis = tick.axis, horiz = axis.horiz, pos = tick.pos, tickmarkOffset = pick(tick.tickmarkOffset, axis.tickmarkOffset), xy = tick.getPosition(horiz, pos, tickmarkOffset, old), x = xy.x, y = xy.y, reverseCrisp = ((horiz && x === axis.pos + axis.len) ||
(!horiz && y === axis.pos)) ? -1 : 1; // #1480, #1687
opacity = pick(opacity, 1);
this.isActive = true;
// Create the grid line
this.renderGridLine(old, opacity, reverseCrisp);
// create the tick mark
this.renderMark(xy, opacity, reverseCrisp);
// the label is created on init - now move it into place
this.renderLabel(xy, old, opacity, index);
tick.isNew = false;
fireEvent(this, 'afterRender');
};
/**
* Renders the gridLine.
*
* @private
* @param {boolean} old Whether or not the tick is old
* @param {number} opacity The opacity of the grid line
* @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1
* @return {void}
*/
Tick.prototype.renderGridLine = function (old, opacity, reverseCrisp) {
var tick = this, axis = tick.axis, options = axis.options, gridLine = tick.gridLine, gridLinePath, attribs = {}, pos = tick.pos, type = tick.type, tickmarkOffset = pick(tick.tickmarkOffset, axis.tickmarkOffset), renderer = axis.chart.renderer, gridPrefix = type ? type + 'Grid' : 'grid', gridLineWidth = options[gridPrefix + 'LineWidth'], gridLineColor = options[gridPrefix + 'LineColor'], dashStyle = options[gridPrefix + 'LineDashStyle'];
if (!gridLine) {
if (!axis.chart.styledMode) {
attribs.stroke = gridLineColor;
attribs['stroke-width'] = gridLineWidth;
if (dashStyle) {
attribs.dashstyle = dashStyle;
}
}
if (!type) {
attribs.zIndex = 1;
}
if (old) {
opacity = 0;
}
/**
* The rendered grid line of the tick.
* @name Highcharts.Tick#gridLine
* @type {Highcharts.SVGElement|undefined}
*/
tick.gridLine = gridLine = renderer.path()
.attr(attribs)
.addClass('highcharts-' + (type ? type + '-' : '') + 'grid-line')
.add(axis.gridGroup);
}
if (gridLine) {
gridLinePath = axis.getPlotLinePath({
value: pos + tickmarkOffset,
lineWidth: gridLine.strokeWidth() * reverseCrisp,
force: 'pass',
old: old
});
// If the parameter 'old' is set, the current call will be followed
// by another call, therefore do not do any animations this time
if (gridLinePath) {
gridLine[old || tick.isNew ? 'attr' : 'animate']({
d: gridLinePath,
opacity: opacity
});
}
}
};
/**
* Renders the tick mark.
*
* @private
* @param {Highcharts.PositionObject} xy The position vector of the mark
* @param {number} opacity The opacity of the mark
* @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1
* @return {void}
*/
Tick.prototype.renderMark = function (xy, opacity, reverseCrisp) {
var tick = this, axis = tick.axis, options = axis.options, renderer = axis.chart.renderer, type = tick.type, tickPrefix = type ? type + 'Tick' : 'tick', tickSize = axis.tickSize(tickPrefix), mark = tick.mark, isNewMark = !mark, x = xy.x, y = xy.y, tickWidth = pick(options[tickPrefix + 'Width'], !type && axis.isXAxis ? 1 : 0), // X axis defaults to 1
tickColor = options[tickPrefix + 'Color'];
if (tickSize) {
// negate the length
if (axis.opposite) {
tickSize[0] = -tickSize[0];
}
// First time, create it
if (isNewMark) {
/**
* The rendered mark of the tick.
* @name Highcharts.Tick#mark
* @type {Highcharts.SVGElement|undefined}
*/
tick.mark = mark = renderer.path()
.addClass('highcharts-' + (type ? type + '-' : '') + 'tick')
.add(axis.axisGroup);
if (!axis.chart.styledMode) {
mark.attr({
stroke: tickColor,
'stroke-width': tickWidth
});
}
}
mark[isNewMark ? 'attr' : 'animate']({
d: tick.getMarkPath(x, y, tickSize[0], mark.strokeWidth() * reverseCrisp, axis.horiz, renderer),
opacity: opacity
});
}
};
/**
* Renders the tick label.
* Note: The label should already be created in init(), so it should only
* have to be moved into place.
*
* @private
* @param {Highcharts.PositionObject} xy The position vector of the label
* @param {boolean} old Whether or not the tick is old
* @param {number} opacity The opacity of the label
* @param {number} index The index of the tick
* @return {void}
*/
Tick.prototype.renderLabel = function (xy, old, opacity, index) {
var tick = this, axis = tick.axis, horiz = axis.horiz, options = axis.options, label = tick.label, labelOptions = options.labels, step = labelOptions.step, tickmarkOffset = pick(tick.tickmarkOffset, axis.tickmarkOffset), show = true, x = xy.x, y = xy.y;
if (label && isNumber(x)) {
label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step);
// Apply show first and show last. If the tick is both first and
// last, it is a single centered tick, in which case we show the
// label anyway (#2100).
if ((tick.isFirst &&
!tick.isLast &&
!pick(options.showFirstLabel, 1)) ||
(tick.isLast &&
!tick.isFirst &&
!pick(options.showLastLabel, 1))) {
show = false;
// Handle label overflow and show or hide accordingly
}
else if (horiz &&
!labelOptions.step &&
!labelOptions.rotation &&
!old &&
opacity !== 0) {
tick.handleOverflow(xy);
}
// apply step
if (step && index % step) {
// show those indices dividable by step
show = false;
}
// Set the new position, and show or hide
if (show && isNumber(xy.y)) {
xy.opacity = opacity;
label[tick.isNewLabel ? 'attr' : 'animate'](xy);
tick.isNewLabel = false;
}
else {
label.attr('y', -9999); // #1338
tick.isNewLabel = true;
}
}
};
/**
* Replace labels with the moved ones to perform animation. Additionally
* destroy unused labels.
*
* @private
* @function Highcharts.Tick#replaceMovedLabel
* @return {void}
*/
Tick.prototype.replaceMovedLabel = function () {
var tick = this, label = tick.label, axis = tick.axis, reversed = axis.reversed, x, y;
// Animate and destroy
if (label && !tick.isNew) {
x = axis.horiz ? (reversed ? axis.left : axis.width + axis.left) : label.xy.x;
y = axis.horiz ?
label.xy.y :
(reversed ? axis.width + axis.top : axis.top);
label.animate({ x: x, y: y, opacity: 0 }, void 0, label.destroy);
delete tick.label;
}
axis.isDirty = true;
tick.label = tick.movedLabel;
delete tick.movedLabel;
};
return Tick;
}());
H.Tick = Tick;
export default H.Tick;
+68
View File
@@ -0,0 +1,68 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* Extenstion for 3d axes
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import U from '../Utilities.js';
var addEvent = U.addEvent, extend = U.extend, wrap = U.wrap;
/* eslint-disable valid-jsdoc */
/**
* Tick with 3D support
* @private
* @class
*/
var Tick3D = /** @class */ (function () {
function Tick3D() {
}
/* *
*
* Static Functions
*
* */
/**
* @private
*/
Tick3D.compose = function (TickClass) {
addEvent(TickClass, 'afterGetLabelPosition', Tick3D.onAfterGetLabelPosition);
var tickProto = TickClass.prototype;
wrap(tickProto, 'getMarkPath', Tick3D.wrapGetMarkPath);
};
/**
* @private
*/
Tick3D.onAfterGetLabelPosition = function (e) {
var axis3D = this.axis.axis3D;
if (axis3D) {
extend(e.pos, axis3D.fix3dPosition(e.pos));
}
};
/**
* @private
*/
Tick3D.wrapGetMarkPath = function (proceed) {
var chart = this.axis.chart;
var axis3D = this.axis.axis3D;
var path = proceed.apply(this, [].slice.call(arguments, 1));
if (axis3D) {
var start = path[0];
var end = path[1];
if (start[0] === 'M' && end[0] === 'L') {
var pArr = [
axis3D.fix3dPosition({ x: start[1], y: start[2], z: 0 }),
axis3D.fix3dPosition({ x: end[1], y: end[2], z: 0 })
];
return this.axis.chart.renderer.toLineSegments(pArr);
}
}
return path;
};
return Tick3D;
}());
export default Tick3D;
@@ -0,0 +1,677 @@
/* *
*
* (c) 2016 Highsoft AS
* Authors: Jon Arild Nygard
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Axis from './Axis.js';
import Tick from './Tick.js';
import Tree from '../../Gantt/Tree.js';
import TreeGridTick from './TreeGridTick.js';
import mixinTreeSeries from '../../Mixins/TreeSeries.js';
var getLevelOptions = mixinTreeSeries.getLevelOptions;
import U from '../Utilities.js';
var addEvent = U.addEvent, find = U.find, fireEvent = U.fireEvent, isNumber = U.isNumber, isObject = U.isObject, isString = U.isString, merge = U.merge, pick = U.pick, wrap = U.wrap;
import './GridAxis.js';
import './BrokenAxis.js';
/**
* @private
*/
var TreeGridAxis;
(function (TreeGridAxis) {
/* *
*
* Interfaces
*
* */
/* *
*
* Variables
*
* */
var applied = false;
/* *
*
* Functions
*
* */
/**
* @private
*/
function compose(AxisClass) {
if (!applied) {
wrap(AxisClass.prototype, 'generateTick', wrapGenerateTick);
wrap(AxisClass.prototype, 'getMaxLabelDimensions', wrapGetMaxLabelDimensions);
wrap(AxisClass.prototype, 'init', wrapInit);
wrap(AxisClass.prototype, 'setTickInterval', wrapSetTickInterval);
TreeGridTick.compose(Tick);
applied = true;
}
}
TreeGridAxis.compose = compose;
/**
* @private
*/
function getBreakFromNode(node, max) {
var from = node.collapseStart || 0, to = node.collapseEnd || 0;
// In broken-axis, the axis.max is minimized until it is not within a
// break. Therefore, if break.to is larger than axis.max, the axis.to
// should not add the 0.5 axis.tickMarkOffset, to avoid adding a break
// larger than axis.max.
// TODO consider simplifying broken-axis and this might solve itself
if (to >= max) {
from -= 0.5;
}
return {
from: from,
to: to,
showPoints: false
};
}
/**
* Creates a tree structure of the data, and the treegrid. Calculates
* categories, and y-values of points based on the tree.
*
* @private
* @function getTreeGridFromData
*
* @param {Array<Highcharts.GanttPointOptions>} data
* All the data points to display in the axis.
*
* @param {boolean} uniqueNames
* Wether or not the data node with the same name should share grid cell. If
* true they do share cell. False by default.
*
* @param {number} numberOfSeries
*
* @return {object}
* Returns an object containing categories, mapOfIdToNode,
* mapOfPosToGridNode, and tree.
*
* @todo There should be only one point per line.
* @todo It should be optional to have one category per point, or merge
* cells
* @todo Add unit-tests.
*/
function getTreeGridFromData(data, uniqueNames, numberOfSeries) {
var categories = [], collapsedNodes = [], mapOfIdToNode = {}, mapOfPosToGridNode = {}, posIterator = -1, uniqueNamesEnabled = typeof uniqueNames === 'boolean' ? uniqueNames : false, tree;
// Build the tree from the series data.
var treeParams = {
// After the children has been created.
after: function (node) {
var gridNode = mapOfPosToGridNode[node.pos], height = 0, descendants = 0;
gridNode.children.forEach(function (child) {
descendants += (child.descendants || 0) + 1;
height = Math.max((child.height || 0) + 1, height);
});
gridNode.descendants = descendants;
gridNode.height = height;
if (gridNode.collapsed) {
collapsedNodes.push(gridNode);
}
},
// Before the children has been created.
before: function (node) {
var data = isObject(node.data, true) ? node.data : {}, name = isString(data.name) ? data.name : '', parentNode = mapOfIdToNode[node.parent], parentGridNode = (isObject(parentNode, true) ?
mapOfPosToGridNode[parentNode.pos] :
null), hasSameName = function (x) {
return x.name === name;
}, gridNode, pos;
// If not unique names, look for sibling node with the same name
if (uniqueNamesEnabled &&
isObject(parentGridNode, true) &&
!!(gridNode = find(parentGridNode.children, hasSameName))) {
// If there is a gridNode with the same name, reuse position
pos = gridNode.pos;
// Add data node to list of nodes in the grid node.
gridNode.nodes.push(node);
}
else {
// If it is a new grid node, increment position.
pos = posIterator++;
}
// Add new grid node to map.
if (!mapOfPosToGridNode[pos]) {
mapOfPosToGridNode[pos] = gridNode = {
depth: parentGridNode ? parentGridNode.depth + 1 : 0,
name: name,
nodes: [node],
children: [],
pos: pos
};
// If not root, then add name to categories.
if (pos !== -1) {
categories.push(name);
}
// Add name to list of children.
if (isObject(parentGridNode, true)) {
parentGridNode.children.push(gridNode);
}
}
// Add data node to map
if (isString(node.id)) {
mapOfIdToNode[node.id] = node;
}
// If one of the points are collapsed, then start the grid node
// in collapsed state.
if (gridNode &&
data.collapsed === true) {
gridNode.collapsed = true;
}
// Assign pos to data node
node.pos = pos;
}
};
var updateYValuesAndTickPos = function (map, numberOfSeries) {
var setValues = function (gridNode, start, result) {
var nodes = gridNode.nodes, end = start + (start === -1 ? 0 : numberOfSeries - 1), diff = (end - start) / 2, padding = 0.5, pos = start + diff;
nodes.forEach(function (node) {
var data = node.data;
if (isObject(data, true)) {
// Update point
data.y = start + (data.seriesIndex || 0);
// Remove the property once used
delete data.seriesIndex;
}
node.pos = pos;
});
result[pos] = gridNode;
gridNode.pos = pos;
gridNode.tickmarkOffset = diff + padding;
gridNode.collapseStart = end + padding;
gridNode.children.forEach(function (child) {
setValues(child, end + 1, result);
end = (child.collapseEnd || 0) - padding;
});
// Set collapseEnd to the end of the last child node.
gridNode.collapseEnd = end + padding;
return result;
};
return setValues(map['-1'], -1, {});
};
// Create tree from data
tree = Tree.getTree(data, treeParams);
// Update y values of data, and set calculate tick positions.
mapOfPosToGridNode = updateYValuesAndTickPos(mapOfPosToGridNode, numberOfSeries);
// Return the resulting data.
return {
categories: categories,
mapOfIdToNode: mapOfIdToNode,
mapOfPosToGridNode: mapOfPosToGridNode,
collapsedNodes: collapsedNodes,
tree: tree
};
}
/**
* Builds the tree of categories and calculates its positions.
* @private
* @param {object} e Event object
* @param {object} e.target The chart instance which the event was fired on.
* @param {object[]} e.target.axes The axes of the chart.
*/
function onBeforeRender(e) {
var chart = e.target, axes = chart.axes;
axes.filter(function (axis) {
return axis.options.type === 'treegrid';
}).forEach(function (axis) {
var options = axis.options || {}, labelOptions = options.labels, uniqueNames = options.uniqueNames, numberOfSeries = 0, isDirty, data, treeGrid, max = options.max;
// Check whether any of series is rendering for the first time,
// visibility has changed, or its data is dirty,
// and only then update. #10570, #10580
// Also check if mapOfPosToGridNode exists. #10887
isDirty = (!axis.treeGrid.mapOfPosToGridNode ||
axis.series.some(function (series) {
return !series.hasRendered ||
series.isDirtyData ||
series.isDirty;
}));
if (isDirty) {
// Concatenate data from all series assigned to this axis.
data = axis.series.reduce(function (arr, s) {
if (s.visible) {
// Push all data to array
(s.options.data || []).forEach(function (data) {
if (isObject(data, true)) {
// Set series index on data. Removed again
// after use.
data.seriesIndex = numberOfSeries;
arr.push(data);
}
});
// Increment series index
if (uniqueNames === true) {
numberOfSeries++;
}
}
return arr;
}, []);
// If max is higher than set data - add a
// dummy data to render categories #10779
if (max && data.length < max) {
for (var i = data.length; i <= max; i++) {
data.push({
// Use the zero-width character
// to avoid conflict with uniqueNames
name: i + '\u200B'
});
}
}
// setScale is fired after all the series is initialized,
// which is an ideal time to update the axis.categories.
treeGrid = getTreeGridFromData(data, uniqueNames || false, (uniqueNames === true) ? numberOfSeries : 1);
// Assign values to the axis.
axis.categories = treeGrid.categories;
axis.treeGrid.mapOfPosToGridNode = treeGrid.mapOfPosToGridNode;
axis.hasNames = true;
axis.treeGrid.tree = treeGrid.tree;
// Update yData now that we have calculated the y values
axis.series.forEach(function (series) {
var data = (series.options.data || []).map(function (d) {
return isObject(d, true) ? merge(d) : d;
});
// Avoid destroying points when series is not visible
if (series.visible) {
series.setData(data, false);
}
});
// Calculate the label options for each level in the tree.
axis.treeGrid.mapOptionsToLevel =
getLevelOptions({
defaults: labelOptions,
from: 1,
levels: labelOptions && labelOptions.levels,
to: axis.treeGrid.tree && axis.treeGrid.tree.height
});
// Setting initial collapsed nodes
if (e.type === 'beforeRender') {
axis.treeGrid.collapsedNodes = treeGrid.collapsedNodes;
}
}
});
}
/**
* Generates a tick for initial positioning.
*
* @private
* @function Highcharts.GridAxis#generateTick
*
* @param {Function} proceed
* The original generateTick function.
*
* @param {number} pos
* The tick position in axis values.
*/
function wrapGenerateTick(proceed, pos) {
var axis = this, mapOptionsToLevel = axis.treeGrid.mapOptionsToLevel || {}, isTreeGrid = axis.options.type === 'treegrid', ticks = axis.ticks;
var tick = ticks[pos], levelOptions, options, gridNode;
if (isTreeGrid &&
axis.treeGrid.mapOfPosToGridNode) {
gridNode = axis.treeGrid.mapOfPosToGridNode[pos];
levelOptions = mapOptionsToLevel[gridNode.depth];
if (levelOptions) {
options = {
labels: levelOptions
};
}
if (!tick) {
ticks[pos] = tick =
new Tick(axis, pos, void 0, void 0, {
category: gridNode.name,
tickmarkOffset: gridNode.tickmarkOffset,
options: options
});
}
else {
// update labels depending on tick interval
tick.parameters.category = gridNode.name;
tick.options = options;
tick.addLabel();
}
}
else {
proceed.apply(axis, Array.prototype.slice.call(arguments, 1));
}
}
/**
* Override to add indentation to axis.maxLabelDimensions.
*
* @private
* @function Highcharts.GridAxis#getMaxLabelDimensions
*
* @param {Function} proceed
* The original function
*/
function wrapGetMaxLabelDimensions(proceed) {
var axis = this, options = axis.options, labelOptions = options && options.labels, indentation = (labelOptions && isNumber(labelOptions.indentation) ?
labelOptions.indentation :
0), retVal = proceed.apply(axis, Array.prototype.slice.call(arguments, 1)), isTreeGrid = axis.options.type === 'treegrid';
var treeDepth;
if (isTreeGrid && axis.treeGrid.mapOfPosToGridNode) {
treeDepth = axis.treeGrid.mapOfPosToGridNode[-1].height || 0;
retVal.width += indentation * (treeDepth - 1);
}
return retVal;
}
/**
* @private
*/
function wrapInit(proceed, chart, userOptions) {
var axis = this, isTreeGrid = userOptions.type === 'treegrid';
if (!axis.treeGrid) {
axis.treeGrid = new Additions(axis);
}
// Set default and forced options for TreeGrid
if (isTreeGrid) {
// Add event for updating the categories of a treegrid.
// NOTE Preferably these events should be set on the axis.
addEvent(chart, 'beforeRender', onBeforeRender);
addEvent(chart, 'beforeRedraw', onBeforeRender);
// Add new collapsed nodes on addseries
addEvent(chart, 'addSeries', function (e) {
if (e.options.data) {
var treeGrid = getTreeGridFromData(e.options.data, userOptions.uniqueNames || false, 1);
axis.treeGrid.collapsedNodes = (axis.treeGrid.collapsedNodes || []).concat(treeGrid.collapsedNodes);
}
});
// Collapse all nodes in axis.treegrid.collapsednodes
// where collapsed equals true.
addEvent(axis, 'foundExtremes', function () {
if (axis.treeGrid.collapsedNodes) {
axis.treeGrid.collapsedNodes.forEach(function (node) {
var breaks = axis.treeGrid.collapse(node);
if (axis.brokenAxis) {
axis.brokenAxis.setBreaks(breaks, false);
// remove the node from the axis collapsedNodes
if (axis.treeGrid.collapsedNodes) {
axis.treeGrid.collapsedNodes = axis.treeGrid.collapsedNodes.filter(function (n) {
return node.collapseStart !== n.collapseStart ||
node.collapseEnd !== n.collapseEnd;
});
}
}
});
}
});
// If staticScale is not defined on the yAxis
// and chart height is set, set axis.isDirty
// to ensure collapsing works (#12012)
addEvent(axis, 'afterBreaks', function () {
var _a;
if (axis.coll === 'yAxis' && !axis.staticScale && ((_a = axis.chart.options.chart) === null || _a === void 0 ? void 0 : _a.height)) {
axis.isDirty = true;
}
});
userOptions = merge({
// Default options
grid: {
enabled: true
},
// TODO: add support for align in treegrid.
labels: {
align: 'left',
/**
* Set options on specific levels in a tree grid axis. Takes
* precedence over labels options.
*
* @sample {gantt} gantt/treegrid-axis/labels-levels
* Levels on TreeGrid Labels
*
* @type {Array<*>}
* @product gantt
* @apioption yAxis.labels.levels
*
* @private
*/
levels: [{
/**
* Specify the level which the options within this object
* applies to.
*
* @type {number}
* @product gantt
* @apioption yAxis.labels.levels.level
*
* @private
*/
level: void 0
}, {
level: 1,
/**
* @type {Highcharts.CSSObject}
* @product gantt
* @apioption yAxis.labels.levels.style
*
* @private
*/
style: {
/** @ignore-option */
fontWeight: 'bold'
}
}],
/**
* The symbol for the collapse and expand icon in a
* treegrid.
*
* @product gantt
* @optionparent yAxis.labels.symbol
*
* @private
*/
symbol: {
/**
* The symbol type. Points to a definition function in
* the `Highcharts.Renderer.symbols` collection.
*
* @type {Highcharts.SymbolKeyValue}
*
* @private
*/
type: 'triangle',
x: -5,
y: -5,
height: 10,
width: 10,
padding: 5
}
},
uniqueNames: false
}, userOptions, {
// Forced options
reversed: true,
// grid.columns is not supported in treegrid
grid: {
columns: void 0
}
});
}
// Now apply the original function with the original arguments,
// which are sliced off this function's arguments
proceed.apply(axis, [chart, userOptions]);
if (isTreeGrid) {
axis.hasNames = true;
axis.options.showLastLabel = true;
}
}
/**
* Set the tick positions, tickInterval, axis min and max.
*
* @private
* @function Highcharts.GridAxis#setTickInterval
*
* @param {Function} proceed
* The original setTickInterval function.
*/
function wrapSetTickInterval(proceed) {
var axis = this, options = axis.options, isTreeGrid = options.type === 'treegrid';
if (isTreeGrid) {
axis.min = pick(axis.userMin, options.min, axis.dataMin);
axis.max = pick(axis.userMax, options.max, axis.dataMax);
fireEvent(axis, 'foundExtremes');
// setAxisTranslation modifies the min and max according to
// axis breaks.
axis.setAxisTranslation(true);
axis.tickmarkOffset = 0.5;
axis.tickInterval = 1;
axis.tickPositions = axis.treeGrid.mapOfPosToGridNode ?
axis.treeGrid.getTickPositions() :
[];
}
else {
proceed.apply(axis, Array.prototype.slice.call(arguments, 1));
}
}
/* *
*
* Classes
*
* */
/**
* @private
* @class
*/
var Additions = /** @class */ (function () {
/* *
*
* Constructors
*
* */
/**
* @private
*/
function Additions(axis) {
this.axis = axis;
}
/* *
*
* Functions
*
* */
/**
* Calculates the new axis breaks to collapse a node.
*
* @private
*
* @param {Highcharts.Axis} axis
* The axis to check against.
*
* @param {Highcharts.GridNode} node
* The node to collapse.
*
* @param {number} pos
* The tick position to collapse.
*
* @return {Array<object>}
* Returns an array of the new breaks for the axis.
*/
Additions.prototype.collapse = function (node) {
var axis = this.axis, breaks = (axis.options.breaks || []), obj = getBreakFromNode(node, axis.max);
breaks.push(obj);
return breaks;
};
/**
* Calculates the new axis breaks to expand a node.
*
* @private
*
* @param {Highcharts.Axis} axis
* The axis to check against.
*
* @param {Highcharts.GridNode} node
* The node to expand.
*
* @param {number} pos
* The tick position to expand.
*
* @return {Array<object>}
* Returns an array of the new breaks for the axis.
*/
Additions.prototype.expand = function (node) {
var axis = this.axis, breaks = (axis.options.breaks || []), obj = getBreakFromNode(node, axis.max);
// Remove the break from the axis breaks array.
return breaks.reduce(function (arr, b) {
if (b.to !== obj.to || b.from !== obj.from) {
arr.push(b);
}
return arr;
}, []);
};
/**
* Creates a list of positions for the ticks on the axis. Filters out
* positions that are outside min and max, or is inside an axis break.
*
* @private
*
* @return {Array<number>}
* List of positions.
*/
Additions.prototype.getTickPositions = function () {
var axis = this.axis;
return Object.keys(axis.treeGrid.mapOfPosToGridNode || {}).reduce(function (arr, key) {
var pos = +key;
if (axis.min <= pos &&
axis.max >= pos &&
!(axis.brokenAxis && axis.brokenAxis.isInAnyBreak(pos))) {
arr.push(pos);
}
return arr;
}, []);
};
/**
* Check if a node is collapsed.
*
* @private
*
* @param {Highcharts.Axis} axis
* The axis to check against.
*
* @param {object} node
* The node to check if is collapsed.
*
* @param {number} pos
* The tick position to collapse.
*
* @return {boolean}
* Returns true if collapsed, false if expanded.
*/
Additions.prototype.isCollapsed = function (node) {
var axis = this.axis, breaks = (axis.options.breaks || []), obj = getBreakFromNode(node, axis.max);
return breaks.some(function (b) {
return b.from === obj.from && b.to === obj.to;
});
};
/**
* Calculates the new axis breaks after toggling the collapse/expand
* state of a node. If it is collapsed it will be expanded, and if it is
* exapended it will be collapsed.
*
* @private
*
* @param {Highcharts.Axis} axis
* The axis to check against.
*
* @param {Highcharts.GridNode} node
* The node to toggle.
*
* @return {Array<object>}
* Returns an array of the new breaks for the axis.
*/
Additions.prototype.toggleCollapse = function (node) {
return (this.isCollapsed(node) ?
this.expand(node) :
this.collapse(node));
};
return Additions;
}());
TreeGridAxis.Additions = Additions;
})(TreeGridAxis || (TreeGridAxis = {}));
// Make utility functions available for testing.
Axis.prototype.utils = {
getNode: Tree.getNode
};
TreeGridAxis.compose(Axis);
export default TreeGridAxis;
@@ -0,0 +1,305 @@
/* *
*
* (c) 2016 Highsoft AS
* Authors: Jon Arild Nygard
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import U from '../Utilities.js';
var addEvent = U.addEvent, defined = U.defined, isObject = U.isObject, isNumber = U.isNumber, pick = U.pick, wrap = U.wrap;
/**
* @private
*/
var TreeGridTick;
(function (TreeGridTick) {
/* *
*
* Interfaces
*
* */
/* *
*
* Variables
*
* */
var applied = false;
/* *
*
* Functions
*
* */
/**
* @private
*/
function compose(TickClass) {
if (!applied) {
addEvent(TickClass, 'init', onInit);
wrap(TickClass.prototype, 'getLabelPosition', wrapGetLabelPosition);
wrap(TickClass.prototype, 'renderLabel', wrapRenderLabel);
// backwards compatibility
TickClass.prototype.collapse = function (redraw) {
this.treeGrid.collapse(redraw);
};
TickClass.prototype.expand = function (redraw) {
this.treeGrid.expand(redraw);
};
TickClass.prototype.toggleCollapse = function (redraw) {
this.treeGrid.toggleCollapse(redraw);
};
applied = true;
}
}
TreeGridTick.compose = compose;
/**
* @private
*/
function onInit() {
var tick = this;
if (!tick.treeGrid) {
tick.treeGrid = new Additions(tick);
}
}
/**
* @private
*/
function onTickHover(label) {
label.addClass('highcharts-treegrid-node-active');
if (!label.renderer.styledMode) {
label.css({
textDecoration: 'underline'
});
}
}
/**
* @private
*/
function onTickHoverExit(label, options) {
var css = defined(options.style) ? options.style : {};
label.removeClass('highcharts-treegrid-node-active');
if (!label.renderer.styledMode) {
label.css({ textDecoration: css.textDecoration });
}
}
/**
* @private
*/
function renderLabelIcon(tick, params) {
var treeGrid = tick.treeGrid, isNew = !treeGrid.labelIcon, renderer = params.renderer, labelBox = params.xy, options = params.options, width = options.width, height = options.height, iconCenter = {
x: labelBox.x - (width / 2) - options.padding,
y: labelBox.y - (height / 2)
}, rotation = params.collapsed ? 90 : 180, shouldRender = params.show && isNumber(iconCenter.y);
var icon = treeGrid.labelIcon;
if (!icon) {
treeGrid.labelIcon = icon = renderer
.path(renderer.symbols[options.type](options.x, options.y, width, height))
.addClass('highcharts-label-icon')
.add(params.group);
}
// Set the new position, and show or hide
if (!shouldRender) {
icon.attr({ y: -9999 }); // #1338
}
// Presentational attributes
if (!renderer.styledMode) {
icon
.attr({
'stroke-width': 1,
'fill': pick(params.color, '#666666')
})
.css({
cursor: 'pointer',
stroke: options.lineColor,
strokeWidth: options.lineWidth
});
}
// Update the icon positions
icon[isNew ? 'attr' : 'animate']({
translateX: iconCenter.x,
translateY: iconCenter.y,
rotation: rotation
});
}
/**
* @private
*/
function wrapGetLabelPosition(proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
var tick = this, lbOptions = pick(tick.options && tick.options.labels, labelOptions), pos = tick.pos, axis = tick.axis, options = axis.options, isTreeGrid = options.type === 'treegrid', result = proceed.apply(tick, [x, y, label, horiz, lbOptions, tickmarkOffset, index, step]);
var symbolOptions, indentation, mapOfPosToGridNode, node, level;
if (isTreeGrid) {
symbolOptions = (lbOptions && isObject(lbOptions.symbol, true) ?
lbOptions.symbol :
{});
indentation = (lbOptions && isNumber(lbOptions.indentation) ?
lbOptions.indentation :
0);
mapOfPosToGridNode = axis.treeGrid.mapOfPosToGridNode;
node = mapOfPosToGridNode && mapOfPosToGridNode[pos];
level = (node && node.depth) || 1;
result.x += (
// Add space for symbols
((symbolOptions.width) + (symbolOptions.padding * 2)) +
// Apply indentation
((level - 1) * indentation));
}
return result;
}
/**
* @private
*/
function wrapRenderLabel(proceed) {
var tick = this, pos = tick.pos, axis = tick.axis, label = tick.label, mapOfPosToGridNode = axis.treeGrid.mapOfPosToGridNode, options = axis.options, labelOptions = pick(tick.options && tick.options.labels, options && options.labels), symbolOptions = (labelOptions && isObject(labelOptions.symbol, true) ?
labelOptions.symbol :
{}), node = mapOfPosToGridNode && mapOfPosToGridNode[pos], level = node && node.depth, isTreeGrid = options.type === 'treegrid', shouldRender = axis.tickPositions.indexOf(pos) > -1, prefixClassName = 'highcharts-treegrid-node-', styledMode = axis.chart.styledMode;
var collapsed, addClassName, removeClassName;
if (isTreeGrid && node) {
// Add class name for hierarchical styling.
if (label &&
label.element) {
label.addClass(prefixClassName + 'level-' + level);
}
}
proceed.apply(tick, Array.prototype.slice.call(arguments, 1));
if (isTreeGrid &&
label &&
label.element &&
node &&
node.descendants &&
node.descendants > 0) {
collapsed = axis.treeGrid.isCollapsed(node);
renderLabelIcon(tick, {
color: !styledMode && label.styles && label.styles.color || '',
collapsed: collapsed,
group: label.parentGroup,
options: symbolOptions,
renderer: label.renderer,
show: shouldRender,
xy: label.xy
});
// Add class name for the node.
addClassName = prefixClassName +
(collapsed ? 'collapsed' : 'expanded');
removeClassName = prefixClassName +
(collapsed ? 'expanded' : 'collapsed');
label
.addClass(addClassName)
.removeClass(removeClassName);
if (!styledMode) {
label.css({
cursor: 'pointer'
});
}
// Add events to both label text and icon
[label, tick.treeGrid.labelIcon].forEach(function (object) {
if (object && !object.attachedTreeGridEvents) {
// On hover
addEvent(object.element, 'mouseover', function () {
onTickHover(label);
});
// On hover out
addEvent(object.element, 'mouseout', function () {
onTickHoverExit(label, labelOptions);
});
addEvent(object.element, 'click', function () {
tick.treeGrid.toggleCollapse();
});
object.attachedTreeGridEvents = true;
}
});
}
}
/* *
*
* Classes
*
* */
/**
* @private
* @class
*/
var Additions = /** @class */ (function () {
/* *
*
* Constructors
*
* */
/**
* @private
*/
function Additions(tick) {
this.tick = tick;
}
/* *
*
* Functions
*
* */
/**
* Collapse the grid cell. Used when axis is of type treegrid.
*
* @see gantt/treegrid-axis/collapsed-dynamically/demo.js
*
* @private
* @function Highcharts.Tick#collapse
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart or wait for an explicit call to
* {@link Highcharts.Chart#redraw}
*/
Additions.prototype.collapse = function (redraw) {
var tick = this.tick, axis = tick.axis, brokenAxis = axis.brokenAxis;
if (brokenAxis &&
axis.treeGrid.mapOfPosToGridNode) {
var pos = tick.pos, node = axis.treeGrid.mapOfPosToGridNode[pos], breaks = axis.treeGrid.collapse(node);
brokenAxis.setBreaks(breaks, pick(redraw, true));
}
};
/**
* Expand the grid cell. Used when axis is of type treegrid.
*
* @see gantt/treegrid-axis/collapsed-dynamically/demo.js
*
* @private
* @function Highcharts.Tick#expand
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart or wait for an explicit call to
* {@link Highcharts.Chart#redraw}
*/
Additions.prototype.expand = function (redraw) {
var tick = this.tick, axis = tick.axis, brokenAxis = axis.brokenAxis;
if (brokenAxis &&
axis.treeGrid.mapOfPosToGridNode) {
var pos = tick.pos, node = axis.treeGrid.mapOfPosToGridNode[pos], breaks = axis.treeGrid.expand(node);
brokenAxis.setBreaks(breaks, pick(redraw, true));
}
};
/**
* Toggle the collapse/expand state of the grid cell. Used when axis is
* of type treegrid.
*
* @see gantt/treegrid-axis/collapsed-dynamically/demo.js
*
* @private
* @function Highcharts.Tick#toggleCollapse
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart or wait for an explicit call to
* {@link Highcharts.Chart#redraw}
*/
Additions.prototype.toggleCollapse = function (redraw) {
var tick = this.tick, axis = tick.axis, brokenAxis = axis.brokenAxis;
if (brokenAxis &&
axis.treeGrid.mapOfPosToGridNode) {
var pos = tick.pos, node = axis.treeGrid.mapOfPosToGridNode[pos], breaks = axis.treeGrid.toggleCollapse(node);
brokenAxis.setBreaks(breaks, pick(redraw, true));
}
};
return Additions;
}());
TreeGridTick.Additions = Additions;
})(TreeGridTick || (TreeGridTick = {}));
export default TreeGridTick;
@@ -0,0 +1,76 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* Extension to the VML Renderer
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
import U from '../Utilities.js';
var addEvent = U.addEvent;
/* eslint-disable valid-jsdoc */
var VMLAxis3DAdditions = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function VMLAxis3DAdditions(axis) {
this.axis = axis;
}
return VMLAxis3DAdditions;
}());
var VMLAxis3D = /** @class */ (function () {
function VMLAxis3D() {
}
/* *
*
* Static Properties
*
* */
VMLAxis3D.compose = function (AxisClass) {
AxisClass.keepProps.push('vml');
addEvent(AxisClass, 'init', VMLAxis3D.onInit);
addEvent(AxisClass, 'render', VMLAxis3D.onRender);
};
/**
* @private
*/
VMLAxis3D.onInit = function () {
var axis = this;
if (!axis.vml) {
axis.vml = new VMLAxis3DAdditions(axis);
}
};
/**
* @private
*/
VMLAxis3D.onRender = function () {
var axis = this;
var vml = axis.vml;
// VML doesn't support a negative z-index
if (vml.sideFrame) {
vml.sideFrame.css({ zIndex: 0 });
vml.sideFrame.front.attr({
fill: vml.sideFrame.color
});
}
if (vml.bottomFrame) {
vml.bottomFrame.css({ zIndex: 1 });
vml.bottomFrame.front.attr({
fill: vml.bottomFrame.color
});
}
if (vml.backFrame) {
vml.backFrame.css({ zIndex: 0 });
vml.backFrame.front.attr({
fill: vml.backFrame.color
});
}
};
return VMLAxis3D;
}());
export default VMLAxis3D;
+155
View File
@@ -0,0 +1,155 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import Axis from './Axis.js';
import U from '../Utilities.js';
var addEvent = U.addEvent, merge = U.merge, pick = U.pick, splat = U.splat;
/* eslint-disable valid-jsdoc */
/**
* 3D chart with support of z coordinates.
* @private
* @class
*/
var ZChart = /** @class */ (function () {
function ZChart() {
}
/* *
*
* Static Functions
*
* */
ZChart.compose = function (ChartClass) {
addEvent(ChartClass, 'afterGetAxes', ZChart.onAfterGetAxes);
var chartProto = ChartClass.prototype;
chartProto.addZAxis = ZChart.wrapAddZAxis;
chartProto.collectionsWithInit.zAxis = [chartProto.addZAxis];
chartProto.collectionsWithUpdate.push('zAxis');
};
/**
* Get the Z axis in addition to the default X and Y.
* @private
*/
ZChart.onAfterGetAxes = function () {
var chart = this;
var options = this.options;
var zAxisOptions = options.zAxis = splat(options.zAxis || {});
if (!chart.is3d()) {
return;
}
chart.zAxis = [];
zAxisOptions.forEach(function (axisOptions, i) {
axisOptions.index = i;
// Z-Axis is shown horizontally, so it's kind of a X-Axis
axisOptions.isX = true;
chart
.addZAxis(axisOptions)
.setScale();
});
};
/**
* @private
*/
ZChart.wrapAddZAxis = function (options) {
return new ZAxis(this, options);
};
return ZChart;
}());
/**
* 3D axis for z coordinates.
*/
var ZAxis = /** @class */ (function (_super) {
__extends(ZAxis, _super);
/* *
*
* Constructors
*
* */
function ZAxis(chart, userOptions) {
var _this = _super.call(this, chart, userOptions) || this;
_this.isZAxis = true;
return _this;
}
/* *
*
* Functions
*
* */
ZAxis.prototype.getSeriesExtremes = function () {
var axis = this;
var chart = axis.chart;
axis.hasVisibleSeries = false;
// Reset properties in case we're redrawing (#3353)
axis.dataMin = axis.dataMax = axis.ignoreMinPadding = axis.ignoreMaxPadding = void 0;
if (axis.stacking) {
axis.stacking.buildStacks();
}
// loop through this axis' series
axis.series.forEach(function (series) {
if (series.visible ||
!(chart.options.chart &&
chart.options.chart.ignoreHiddenSeries)) {
var seriesOptions = series.options, zData, threshold = seriesOptions.threshold;
axis.hasVisibleSeries = true;
// Validate threshold in logarithmic axes
if (axis.positiveValuesOnly && threshold <= 0) {
threshold = void 0;
}
zData = series.zData;
if (zData.length) {
axis.dataMin = Math.min(pick(axis.dataMin, zData[0]), Math.min.apply(null, zData));
axis.dataMax = Math.max(pick(axis.dataMax, zData[0]), Math.max.apply(null, zData));
}
}
});
};
/**
* @private
*/
ZAxis.prototype.setAxisSize = function () {
var axis = this;
var chart = axis.chart;
_super.prototype.setAxisSize.call(this);
axis.width = axis.len = (chart.options.chart &&
chart.options.chart.options3d &&
chart.options.chart.options3d.depth) || 0;
axis.right = chart.chartWidth - axis.width - axis.left;
};
/**
* @private
*/
ZAxis.prototype.setOptions = function (userOptions) {
userOptions = merge({
offset: 0,
lineWidth: 0
}, userOptions);
_super.prototype.setOptions.call(this, userOptions);
this.coll = 'zAxis';
};
/* *
*
* Static Properties
*
* */
ZAxis.ZChartComposition = ZChart;
return ZAxis;
}(Axis));
export default ZAxis;