chore: version inicial del codigo fuente del LMS Estafeta
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
+2036
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,118 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2016-2020 Highsoft AS
|
||||
*
|
||||
* Author: Lars A. V. Cabrera
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Chart from './Chart.js';
|
||||
import H from '../Globals.js';
|
||||
import U from '../Utilities.js';
|
||||
var getOptions = U.getOptions, isArray = U.isArray, merge = U.merge, splat = U.splat;
|
||||
import '../../Series/GanttSeries.js';
|
||||
/**
|
||||
* Factory function for Gantt charts.
|
||||
*
|
||||
* @example
|
||||
* // Render a chart in to div#container
|
||||
* var chart = Highcharts.ganttChart('container', {
|
||||
* title: {
|
||||
* text: 'My chart'
|
||||
* },
|
||||
* series: [{
|
||||
* data: ...
|
||||
* }]
|
||||
* });
|
||||
*
|
||||
* @function Highcharts.ganttChart
|
||||
*
|
||||
* @param {string|Highcharts.HTMLDOMElement} renderTo
|
||||
* The DOM element to render to, or its id.
|
||||
*
|
||||
* @param {Highcharts.Options} options
|
||||
* The chart options structure.
|
||||
*
|
||||
* @param {Highcharts.ChartCallbackFunction} [callback]
|
||||
* Function to run when the chart has loaded and and all external images
|
||||
* are loaded. Defining a
|
||||
* [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load)
|
||||
* handler is equivalent.
|
||||
*
|
||||
* @return {Highcharts.Chart}
|
||||
* Returns the Chart object.
|
||||
*/
|
||||
H.ganttChart = function (renderTo, options, callback) {
|
||||
var hasRenderToArg = typeof renderTo === 'string' || renderTo.nodeName, seriesOptions = options.series, defaultOptions = getOptions(), defaultLinkedTo, userOptions = options;
|
||||
options = arguments[hasRenderToArg ? 1 : 0];
|
||||
// If user hasn't defined axes as array, make it into an array and add a
|
||||
// second axis by default.
|
||||
if (!isArray(options.xAxis)) {
|
||||
options.xAxis = [options.xAxis || {}, {}];
|
||||
}
|
||||
// apply X axis options to both single and multi x axes
|
||||
options.xAxis = options.xAxis.map(function (xAxisOptions, i) {
|
||||
if (i === 1) { // Second xAxis
|
||||
defaultLinkedTo = 0;
|
||||
}
|
||||
return merge(defaultOptions.xAxis, {
|
||||
grid: {
|
||||
enabled: true
|
||||
},
|
||||
opposite: true,
|
||||
linkedTo: defaultLinkedTo
|
||||
}, xAxisOptions, // user options
|
||||
{
|
||||
type: 'datetime'
|
||||
});
|
||||
});
|
||||
// apply Y axis options to both single and multi y axes
|
||||
options.yAxis = (splat(options.yAxis || {})).map(function (yAxisOptions) {
|
||||
return merge(defaultOptions.yAxis, // #3802
|
||||
{
|
||||
grid: {
|
||||
enabled: true
|
||||
},
|
||||
staticScale: 50,
|
||||
reversed: true,
|
||||
// Set default type treegrid, but only if 'categories' is
|
||||
// undefined
|
||||
type: yAxisOptions.categories ? yAxisOptions.type : 'treegrid'
|
||||
}, yAxisOptions // user options
|
||||
);
|
||||
});
|
||||
options.series = null;
|
||||
options = merge(true, {
|
||||
chart: {
|
||||
type: 'gantt'
|
||||
},
|
||||
title: {
|
||||
text: null
|
||||
},
|
||||
legend: {
|
||||
enabled: false
|
||||
},
|
||||
navigator: {
|
||||
series: { type: 'gantt' }
|
||||
}
|
||||
}, options, // user's options
|
||||
// forced options
|
||||
{
|
||||
isGantt: true
|
||||
});
|
||||
options.series = userOptions.series = seriesOptions;
|
||||
(options.series || []).forEach(function (series) {
|
||||
if (series.data) {
|
||||
series.data.forEach(function (point) {
|
||||
H.seriesTypes.gantt.prototype.setGanttPointAliases(point);
|
||||
});
|
||||
}
|
||||
});
|
||||
return hasRenderToArg ?
|
||||
new Chart(renderTo, options, callback) :
|
||||
new Chart(options, options); // @todo does not look correct
|
||||
};
|
||||
@@ -0,0 +1,760 @@
|
||||
/* *
|
||||
*
|
||||
* (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/Axis.js';
|
||||
import Chart from '../Chart/Chart.js';
|
||||
import H from '../Globals.js';
|
||||
import Point from '../Series/Point.js';
|
||||
import SVGRenderer from '../Renderer/SVG/SVGRenderer.js';
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, arrayMax = U.arrayMax, arrayMin = U.arrayMin, clamp = U.clamp, defined = U.defined, extend = U.extend, find = U.find, format = U.format, getOptions = U.getOptions, isNumber = U.isNumber, isString = U.isString, merge = U.merge, pick = U.pick, splat = U.splat;
|
||||
import '../Pointer.js';
|
||||
import '../Series/Series.js';
|
||||
// Has a dependency on Navigator due to the use of
|
||||
// defaultOptions.navigator
|
||||
import '../Navigator.js';
|
||||
// Has a dependency on Scrollbar due to the use of
|
||||
// defaultOptions.scrollbar
|
||||
import '../Scrollbar.js';
|
||||
// Has a dependency on RangeSelector due to the use of
|
||||
// defaultOptions.rangeSelector
|
||||
import '../../Extensions/RangeSelector.js';
|
||||
var Series = H.Series, seriesProto = Series.prototype, seriesInit = seriesProto.init, seriesProcessData = seriesProto.processData, pointTooltipFormatter = Point.prototype.tooltipFormatter;
|
||||
/**
|
||||
* Compare the values of the series against the first non-null, non-
|
||||
* zero value in the visible range. The y axis will show percentage
|
||||
* or absolute change depending on whether `compare` is set to `"percent"`
|
||||
* or `"value"`. When this is applied to multiple series, it allows
|
||||
* comparing the development of the series against each other. Adds
|
||||
* a `change` field to every point object.
|
||||
*
|
||||
* @see [compareBase](#plotOptions.series.compareBase)
|
||||
* @see [Axis.setCompare()](/class-reference/Highcharts.Axis#setCompare)
|
||||
*
|
||||
* @sample {highstock} stock/plotoptions/series-compare-percent/
|
||||
* Percent
|
||||
* @sample {highstock} stock/plotoptions/series-compare-value/
|
||||
* Value
|
||||
*
|
||||
* @type {string}
|
||||
* @since 1.0.1
|
||||
* @product highstock
|
||||
* @apioption plotOptions.series.compare
|
||||
*/
|
||||
/**
|
||||
* Defines if comparison should start from the first point within the visible
|
||||
* range or should start from the first point **before** the range.
|
||||
*
|
||||
* In other words, this flag determines if first point within the visible range
|
||||
* will have 0% (`compareStart=true`) or should have been already calculated
|
||||
* according to the previous point (`compareStart=false`).
|
||||
*
|
||||
* @sample {highstock} stock/plotoptions/series-comparestart/
|
||||
* Calculate compare within visible range
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
* @since 6.0.0
|
||||
* @product highstock
|
||||
* @apioption plotOptions.series.compareStart
|
||||
*/
|
||||
/**
|
||||
* When [compare](#plotOptions.series.compare) is `percent`, this option
|
||||
* dictates whether to use 0 or 100 as the base of comparison.
|
||||
*
|
||||
* @sample {highstock} stock/plotoptions/series-comparebase/
|
||||
* Compare base is 100
|
||||
*
|
||||
* @type {number}
|
||||
* @default 0
|
||||
* @since 5.0.6
|
||||
* @product highstock
|
||||
* @validvalue [0, 100]
|
||||
* @apioption plotOptions.series.compareBase
|
||||
*/
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* Factory function for creating new stock charts. Creates a new
|
||||
* {@link Highcharts.Chart|Chart} object with different default options than the
|
||||
* basic Chart.
|
||||
*
|
||||
* @example
|
||||
* var chart = Highcharts.stockChart('container', {
|
||||
* series: [{
|
||||
* data: [1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
* pointInterval: 24 * 60 * 60 * 1000
|
||||
* }]
|
||||
* });
|
||||
*
|
||||
* @function Highcharts.stockChart
|
||||
*
|
||||
* @param {string|Highcharts.HTMLDOMElement} [renderTo]
|
||||
* The DOM element to render to, or its id.
|
||||
*
|
||||
* @param {Highcharts.Options} options
|
||||
* The chart options structure as described in the
|
||||
* [options reference](https://api.highcharts.com/highstock).
|
||||
*
|
||||
* @param {Highcharts.ChartCallbackFunction} [callback]
|
||||
* A function to execute when the chart object is finished loading and
|
||||
* rendering. In most cases the chart is built in one thread, but in
|
||||
* Internet Explorer version 8 or less the chart is sometimes
|
||||
* initialized before the document is ready, and in these cases the
|
||||
* chart object will not be finished synchronously. As a consequence,
|
||||
* code that relies on the newly built Chart object should always run in
|
||||
* the callback. Defining a
|
||||
* [chart.events.load](https://api.highcharts.com/highstock/chart.events.load)
|
||||
* handler is equivalent.
|
||||
*
|
||||
* @return {Highcharts.Chart}
|
||||
* The chart object.
|
||||
*/
|
||||
H.StockChart = H.stockChart = function (a, b, c) {
|
||||
var hasRenderToArg = isString(a) || a.nodeName, options = arguments[hasRenderToArg ? 1 : 0], userOptions = options,
|
||||
// to increase performance, don't merge the data
|
||||
seriesOptions = options.series, defaultOptions = getOptions(), opposite,
|
||||
// Always disable startOnTick:true on the main axis when the navigator
|
||||
// is enabled (#1090)
|
||||
navigatorEnabled = pick(options.navigator && options.navigator.enabled, defaultOptions.navigator.enabled, true);
|
||||
// apply X axis options to both single and multi y axes
|
||||
options.xAxis = splat(options.xAxis || {}).map(function (xAxisOptions, i) {
|
||||
return merge({
|
||||
minPadding: 0,
|
||||
maxPadding: 0,
|
||||
overscroll: 0,
|
||||
ordinal: true,
|
||||
title: {
|
||||
text: null
|
||||
},
|
||||
labels: {
|
||||
overflow: 'justify'
|
||||
},
|
||||
showLastLabel: true
|
||||
}, defaultOptions.xAxis, // #3802
|
||||
defaultOptions.xAxis && defaultOptions.xAxis[i], // #7690
|
||||
xAxisOptions, // user options
|
||||
{
|
||||
type: 'datetime',
|
||||
categories: null
|
||||
}, (navigatorEnabled ? {
|
||||
startOnTick: false,
|
||||
endOnTick: false
|
||||
} : null));
|
||||
});
|
||||
// apply Y axis options to both single and multi y axes
|
||||
options.yAxis = splat(options.yAxis || {}).map(function (yAxisOptions, i) {
|
||||
opposite = pick(yAxisOptions.opposite, true);
|
||||
return merge({
|
||||
labels: {
|
||||
y: -2
|
||||
},
|
||||
opposite: opposite,
|
||||
/**
|
||||
* @default {highcharts} true
|
||||
* @default {highstock} false
|
||||
* @apioption yAxis.showLastLabel
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
showLastLabel: !!(
|
||||
// #6104, show last label by default for category axes
|
||||
yAxisOptions.categories ||
|
||||
yAxisOptions.type === 'category'),
|
||||
title: {
|
||||
text: null
|
||||
}
|
||||
}, defaultOptions.yAxis, // #3802
|
||||
defaultOptions.yAxis && defaultOptions.yAxis[i], // #7690
|
||||
yAxisOptions // user options
|
||||
);
|
||||
});
|
||||
options.series = null;
|
||||
options = merge({
|
||||
chart: {
|
||||
panning: {
|
||||
enabled: true,
|
||||
type: 'x'
|
||||
},
|
||||
pinchType: 'x'
|
||||
},
|
||||
navigator: {
|
||||
enabled: navigatorEnabled
|
||||
},
|
||||
scrollbar: {
|
||||
// #4988 - check if setOptions was called
|
||||
enabled: pick(defaultOptions.scrollbar.enabled, true)
|
||||
},
|
||||
rangeSelector: {
|
||||
// #4988 - check if setOptions was called
|
||||
enabled: pick(defaultOptions.rangeSelector.enabled, true)
|
||||
},
|
||||
title: {
|
||||
text: null
|
||||
},
|
||||
tooltip: {
|
||||
split: pick(defaultOptions.tooltip.split, true),
|
||||
crosshairs: true
|
||||
},
|
||||
legend: {
|
||||
enabled: false
|
||||
}
|
||||
}, options, // user's options
|
||||
{
|
||||
isStock: true // internal flag
|
||||
});
|
||||
options.series = userOptions.series = seriesOptions;
|
||||
return hasRenderToArg ?
|
||||
new Chart(a, options, c) :
|
||||
new Chart(options, b);
|
||||
};
|
||||
// Handle som Stock-specific series defaults, override the plotOptions before
|
||||
// series options are handled.
|
||||
addEvent(Series, 'setOptions', function (e) {
|
||||
var overrides;
|
||||
if (this.chart.options.isStock) {
|
||||
if (this.is('column') || this.is('columnrange')) {
|
||||
overrides = {
|
||||
borderWidth: 0,
|
||||
shadow: false
|
||||
};
|
||||
}
|
||||
else if (!this.is('scatter') && !this.is('sma')) {
|
||||
overrides = {
|
||||
marker: {
|
||||
enabled: false,
|
||||
radius: 2
|
||||
}
|
||||
};
|
||||
}
|
||||
if (overrides) {
|
||||
e.plotOptions[this.type] = merge(e.plotOptions[this.type], overrides);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Override the automatic label alignment so that the first Y axis' labels
|
||||
// are drawn on top of the grid line, and subsequent axes are drawn outside
|
||||
addEvent(Axis, 'autoLabelAlign', function (e) {
|
||||
var chart = this.chart, options = this.options, panes = chart._labelPanes = chart._labelPanes || {}, key, labelOptions = this.options.labels;
|
||||
if (this.chart.options.isStock && this.coll === 'yAxis') {
|
||||
key = options.top + ',' + options.height;
|
||||
// do it only for the first Y axis of each pane
|
||||
if (!panes[key] && labelOptions.enabled) {
|
||||
if (labelOptions.x === 15) { // default
|
||||
labelOptions.x = 0;
|
||||
}
|
||||
if (typeof labelOptions.align === 'undefined') {
|
||||
labelOptions.align = 'right';
|
||||
}
|
||||
panes[key] = this;
|
||||
e.align = 'right';
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
// Clear axis from label panes (#6071)
|
||||
addEvent(Axis, 'destroy', function () {
|
||||
var chart = this.chart, key = this.options && (this.options.top + ',' + this.options.height);
|
||||
if (key && chart._labelPanes && chart._labelPanes[key] === this) {
|
||||
delete chart._labelPanes[key];
|
||||
}
|
||||
});
|
||||
// Override getPlotLinePath to allow for multipane charts
|
||||
addEvent(Axis, 'getPlotLinePath', function (e) {
|
||||
var axis = this, series = (this.isLinked && !this.series ?
|
||||
this.linkedParent.series :
|
||||
this.series), chart = axis.chart, renderer = chart.renderer, axisLeft = axis.left, axisTop = axis.top, x1, y1, x2, y2, result = [], axes = [], // #3416 need a default array
|
||||
axes2, uniqueAxes, translatedValue = e.translatedValue, value = e.value, force = e.force, transVal;
|
||||
/**
|
||||
* Return the other axis based on either the axis option or on related
|
||||
* series.
|
||||
* @private
|
||||
*/
|
||||
function getAxis(coll) {
|
||||
var otherColl = coll === 'xAxis' ? 'yAxis' : 'xAxis', opt = axis.options[otherColl];
|
||||
// Other axis indexed by number
|
||||
if (isNumber(opt)) {
|
||||
return [chart[otherColl][opt]];
|
||||
}
|
||||
// Other axis indexed by id (like navigator)
|
||||
if (isString(opt)) {
|
||||
return [chart.get(opt)];
|
||||
}
|
||||
// Auto detect based on existing series
|
||||
return series.map(function (s) {
|
||||
return s[otherColl];
|
||||
});
|
||||
}
|
||||
if ( // For stock chart, by default render paths across the panes
|
||||
// except the case when `acrossPanes` is disabled by user (#6644)
|
||||
(chart.options.isStock && e.acrossPanes !== false) &&
|
||||
// Ignore in case of colorAxis or zAxis. #3360, #3524, #6720
|
||||
axis.coll === 'xAxis' || axis.coll === 'yAxis') {
|
||||
e.preventDefault();
|
||||
// Get the related axes based on series
|
||||
axes = getAxis(axis.coll);
|
||||
// Get the related axes based options.*Axis setting #2810
|
||||
axes2 = (axis.isXAxis ? chart.yAxis : chart.xAxis);
|
||||
axes2.forEach(function (A) {
|
||||
if (defined(A.options.id) ?
|
||||
A.options.id.indexOf('navigator') === -1 :
|
||||
true) {
|
||||
var a = (A.isXAxis ? 'yAxis' : 'xAxis'), rax = (defined(A.options[a]) ?
|
||||
chart[a][A.options[a]] :
|
||||
chart[a][0]);
|
||||
if (axis === rax) {
|
||||
axes.push(A);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Remove duplicates in the axes array. If there are no axes in the axes
|
||||
// array, we are adding an axis without data, so we need to populate
|
||||
// this with grid lines (#2796).
|
||||
uniqueAxes = axes.length ?
|
||||
[] :
|
||||
[axis.isXAxis ? chart.yAxis[0] : chart.xAxis[0]]; // #3742
|
||||
axes.forEach(function (axis2) {
|
||||
if (uniqueAxes.indexOf(axis2) === -1 &&
|
||||
// Do not draw on axis which overlap completely. #5424
|
||||
!find(uniqueAxes, function (unique) {
|
||||
return unique.pos === axis2.pos && unique.len === axis2.len;
|
||||
})) {
|
||||
uniqueAxes.push(axis2);
|
||||
}
|
||||
});
|
||||
transVal = pick(translatedValue, axis.translate(value, null, null, e.old));
|
||||
if (isNumber(transVal)) {
|
||||
if (axis.horiz) {
|
||||
uniqueAxes.forEach(function (axis2) {
|
||||
var skip;
|
||||
y1 = axis2.pos;
|
||||
y2 = y1 + axis2.len;
|
||||
x1 = x2 = Math.round(transVal + axis.transB);
|
||||
// outside plot area
|
||||
if (force !== 'pass' &&
|
||||
(x1 < axisLeft || x1 > axisLeft + axis.width)) {
|
||||
if (force) {
|
||||
x1 = x2 = clamp(x1, axisLeft, axisLeft + axis.width);
|
||||
}
|
||||
else {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
if (!skip) {
|
||||
result.push(['M', x1, y1], ['L', x2, y2]);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
uniqueAxes.forEach(function (axis2) {
|
||||
var skip;
|
||||
x1 = axis2.pos;
|
||||
x2 = x1 + axis2.len;
|
||||
y1 = y2 = Math.round(axisTop + axis.height - transVal);
|
||||
// outside plot area
|
||||
if (force !== 'pass' &&
|
||||
(y1 < axisTop || y1 > axisTop + axis.height)) {
|
||||
if (force) {
|
||||
y1 = y2 = clamp(y1, axisTop, axisTop + axis.height);
|
||||
}
|
||||
else {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
if (!skip) {
|
||||
result.push(['M', x1, y1], ['L', x2, y2]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
e.path = result.length > 0 ?
|
||||
renderer.crispPolyLine(result, e.lineWidth || 1) :
|
||||
// #3557 getPlotLinePath in regular Highcharts also returns null
|
||||
null;
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Function to crisp a line with multiple segments
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.SVGRenderer#crispPolyLine
|
||||
* @param {Highcharts.SVGPathArray} points
|
||||
* @param {number} width
|
||||
* @return {Highcharts.SVGPathArray}
|
||||
*/
|
||||
SVGRenderer.prototype.crispPolyLine = function (points, width) {
|
||||
// points format: [['M', 0, 0], ['L', 100, 0]]
|
||||
// normalize to a crisp line
|
||||
for (var i = 0; i < points.length; i = i + 2) {
|
||||
var start = points[i], end = points[i + 1];
|
||||
if (start[1] === end[1]) {
|
||||
// Substract due to #1129. Now bottom and left axis gridlines behave
|
||||
// the same.
|
||||
start[1] = end[1] =
|
||||
Math.round(start[1]) - (width % 2 / 2);
|
||||
}
|
||||
if (start[2] === end[2]) {
|
||||
start[2] = end[2] =
|
||||
Math.round(start[2]) + (width % 2 / 2);
|
||||
}
|
||||
}
|
||||
return points;
|
||||
};
|
||||
// Wrapper to hide the label
|
||||
addEvent(Axis, 'afterHideCrosshair', function () {
|
||||
if (this.crossLabel) {
|
||||
this.crossLabel = this.crossLabel.hide();
|
||||
}
|
||||
});
|
||||
// Extend crosshairs to also draw the label
|
||||
addEvent(Axis, 'afterDrawCrosshair', function (event) {
|
||||
// Check if the label has to be drawn
|
||||
if (!defined(this.crosshair.label) ||
|
||||
!this.crosshair.label.enabled ||
|
||||
!this.cross) {
|
||||
return;
|
||||
}
|
||||
var chart = this.chart, log = this.logarithmic, options = this.options.crosshair.label, // the label's options
|
||||
horiz = this.horiz, // axis orientation
|
||||
opposite = this.opposite, // axis position
|
||||
left = this.left, // left position
|
||||
top = this.top, // top position
|
||||
crossLabel = this.crossLabel, // the svgElement
|
||||
posx, posy, crossBox, formatOption = options.format, formatFormat = '', limit, align, tickInside = this.options.tickPosition === 'inside', snap = this.crosshair.snap !== false, value, offset = 0,
|
||||
// Use last available event (#5287)
|
||||
e = event.e || (this.cross && this.cross.e), point = event.point, min = this.min, max = this.max;
|
||||
if (log) {
|
||||
min = log.lin2log(min);
|
||||
max = log.lin2log(max);
|
||||
}
|
||||
align = (horiz ? 'center' : opposite ?
|
||||
(this.labelAlign === 'right' ? 'right' : 'left') :
|
||||
(this.labelAlign === 'left' ? 'left' : 'center'));
|
||||
// If the label does not exist yet, create it.
|
||||
if (!crossLabel) {
|
||||
crossLabel = this.crossLabel = chart.renderer
|
||||
.label(null, null, null, options.shape || 'callout')
|
||||
.addClass('highcharts-crosshair-label' + (this.series[0] &&
|
||||
' highcharts-color-' + this.series[0].colorIndex))
|
||||
.attr({
|
||||
align: options.align || align,
|
||||
padding: pick(options.padding, 8),
|
||||
r: pick(options.borderRadius, 3),
|
||||
zIndex: 2
|
||||
})
|
||||
.add(this.labelGroup);
|
||||
// Presentational
|
||||
if (!chart.styledMode) {
|
||||
crossLabel
|
||||
.attr({
|
||||
fill: options.backgroundColor ||
|
||||
(this.series[0] && this.series[0].color) ||
|
||||
'#666666',
|
||||
stroke: options.borderColor || '',
|
||||
'stroke-width': options.borderWidth || 0
|
||||
})
|
||||
.css(extend({
|
||||
color: '#ffffff',
|
||||
fontWeight: 'normal',
|
||||
fontSize: '11px',
|
||||
textAlign: 'center'
|
||||
}, options.style));
|
||||
}
|
||||
}
|
||||
if (horiz) {
|
||||
posx = snap ? point.plotX + left : e.chartX;
|
||||
posy = top + (opposite ? 0 : this.height);
|
||||
}
|
||||
else {
|
||||
posx = opposite ? this.width + left : 0;
|
||||
posy = snap ? point.plotY + top : e.chartY;
|
||||
}
|
||||
if (!formatOption && !options.formatter) {
|
||||
if (this.dateTime) {
|
||||
formatFormat = '%b %d, %Y';
|
||||
}
|
||||
formatOption =
|
||||
'{value' + (formatFormat ? ':' + formatFormat : '') + '}';
|
||||
}
|
||||
// Show the label
|
||||
value = snap ?
|
||||
point[this.isXAxis ? 'x' : 'y'] :
|
||||
this.toValue(horiz ? e.chartX : e.chartY);
|
||||
crossLabel.attr({
|
||||
text: formatOption ?
|
||||
format(formatOption, { value: value }, chart) :
|
||||
options.formatter.call(this, value),
|
||||
x: posx,
|
||||
y: posy,
|
||||
// Crosshair should be rendered within Axis range (#7219)
|
||||
visibility: value < min || value > max ?
|
||||
'hidden' :
|
||||
'visible'
|
||||
});
|
||||
crossBox = crossLabel.getBBox();
|
||||
// now it is placed we can correct its position
|
||||
if (isNumber(crossLabel.y)) {
|
||||
if (horiz) {
|
||||
if ((tickInside && !opposite) || (!tickInside && opposite)) {
|
||||
posy = crossLabel.y - crossBox.height;
|
||||
}
|
||||
}
|
||||
else {
|
||||
posy = crossLabel.y - (crossBox.height / 2);
|
||||
}
|
||||
}
|
||||
// check the edges
|
||||
if (horiz) {
|
||||
limit = {
|
||||
left: left - crossBox.x,
|
||||
right: left + this.width - crossBox.x
|
||||
};
|
||||
}
|
||||
else {
|
||||
limit = {
|
||||
left: this.labelAlign === 'left' ? left : 0,
|
||||
right: this.labelAlign === 'right' ?
|
||||
left + this.width :
|
||||
chart.chartWidth
|
||||
};
|
||||
}
|
||||
// left edge
|
||||
if (crossLabel.translateX < limit.left) {
|
||||
offset = limit.left - crossLabel.translateX;
|
||||
}
|
||||
// right edge
|
||||
if (crossLabel.translateX + crossBox.width >= limit.right) {
|
||||
offset = -(crossLabel.translateX + crossBox.width - limit.right);
|
||||
}
|
||||
// show the crosslabel
|
||||
crossLabel.attr({
|
||||
x: posx + offset,
|
||||
y: posy,
|
||||
// First set x and y, then anchorX and anchorY, when box is actually
|
||||
// calculated, #5702
|
||||
anchorX: horiz ?
|
||||
posx :
|
||||
(this.opposite ? 0 : chart.chartWidth),
|
||||
anchorY: horiz ?
|
||||
(this.opposite ? chart.chartHeight : 0) :
|
||||
posy + crossBox.height / 2
|
||||
});
|
||||
});
|
||||
/* ************************************************************************** *
|
||||
* Start value compare logic *
|
||||
* ************************************************************************** */
|
||||
/**
|
||||
* Extend series.init by adding a method to modify the y value used for plotting
|
||||
* on the y axis. This method is called both from the axis when finding dataMin
|
||||
* and dataMax, and from the series.translate method.
|
||||
*
|
||||
* @ignore
|
||||
* @function Highcharts.Series#init
|
||||
*/
|
||||
seriesProto.init = function () {
|
||||
// Call base method
|
||||
seriesInit.apply(this, arguments);
|
||||
// Set comparison mode
|
||||
this.setCompare(this.options.compare);
|
||||
};
|
||||
/**
|
||||
* Highstock only. Set the
|
||||
* [compare](https://api.highcharts.com/highstock/plotOptions.series.compare)
|
||||
* mode of the series after render time. In most cases it is more useful running
|
||||
* {@link Axis#setCompare} on the X axis to update all its series.
|
||||
*
|
||||
* @function Highcharts.Series#setCompare
|
||||
*
|
||||
* @param {string} [compare]
|
||||
* Can be one of `null` (default), `"percent"` or `"value"`.
|
||||
*/
|
||||
seriesProto.setCompare = function (compare) {
|
||||
// Set or unset the modifyValue method
|
||||
this.modifyValue = (compare === 'value' || compare === 'percent') ?
|
||||
function (value, point) {
|
||||
var compareValue = this.compareValue;
|
||||
if (typeof value !== 'undefined' &&
|
||||
typeof compareValue !== 'undefined') { // #2601, #5814
|
||||
// Get the modified value
|
||||
if (compare === 'value') {
|
||||
value -= compareValue;
|
||||
// Compare percent
|
||||
}
|
||||
else {
|
||||
value = 100 * (value / compareValue) -
|
||||
(this.options.compareBase === 100 ? 0 : 100);
|
||||
}
|
||||
// record for tooltip etc.
|
||||
if (point) {
|
||||
point.change = value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return 0;
|
||||
} :
|
||||
null;
|
||||
// Survive to export, #5485
|
||||
this.userOptions.compare = compare;
|
||||
// Mark dirty
|
||||
if (this.chart.hasRendered) {
|
||||
this.isDirty = true;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Extend series.processData by finding the first y value in the plot area,
|
||||
* used for comparing the following values
|
||||
*
|
||||
* @ignore
|
||||
* @function Highcharts.Series#processData
|
||||
*/
|
||||
seriesProto.processData = function (force) {
|
||||
var series = this, i, keyIndex = -1, processedXData, processedYData, compareStart = series.options.compareStart === true ? 0 : 1, length, compareValue;
|
||||
// call base method
|
||||
seriesProcessData.apply(this, arguments);
|
||||
if (series.xAxis && series.processedYData) { // not pies
|
||||
// local variables
|
||||
processedXData = series.processedXData;
|
||||
processedYData = series.processedYData;
|
||||
length = processedYData.length;
|
||||
// For series with more than one value (range, OHLC etc), compare
|
||||
// against close or the pointValKey (#4922, #3112, #9854)
|
||||
if (series.pointArrayMap) {
|
||||
keyIndex = series.pointArrayMap.indexOf(series.options.pointValKey || series.pointValKey || 'y');
|
||||
}
|
||||
// find the first value for comparison
|
||||
for (i = 0; i < length - compareStart; i++) {
|
||||
compareValue = processedYData[i] && keyIndex > -1 ?
|
||||
processedYData[i][keyIndex] :
|
||||
processedYData[i];
|
||||
if (isNumber(compareValue) &&
|
||||
processedXData[i + compareStart] >=
|
||||
series.xAxis.min &&
|
||||
compareValue !== 0) {
|
||||
series.compareValue = compareValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
};
|
||||
// Modify series extremes
|
||||
addEvent(Series, 'afterGetExtremes', function (e) {
|
||||
var dataExtremes = e.dataExtremes;
|
||||
if (this.modifyValue && dataExtremes) {
|
||||
var extremes = [
|
||||
this.modifyValue(dataExtremes.dataMin),
|
||||
this.modifyValue(dataExtremes.dataMax)
|
||||
];
|
||||
dataExtremes.dataMin = arrayMin(extremes);
|
||||
dataExtremes.dataMax = arrayMax(extremes);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Highstock only. Set the compare mode on all series belonging to an Y axis
|
||||
* after render time.
|
||||
*
|
||||
* @see [series.plotOptions.compare](https://api.highcharts.com/highstock/series.plotOptions.compare)
|
||||
*
|
||||
* @sample stock/members/axis-setcompare/
|
||||
* Set compoare
|
||||
*
|
||||
* @function Highcharts.Axis#setCompare
|
||||
*
|
||||
* @param {string} [compare]
|
||||
* The compare mode. Can be one of `null` (default), `"value"` or
|
||||
* `"percent"`.
|
||||
*
|
||||
* @param {boolean} [redraw=true]
|
||||
* Whether to redraw the chart or to wait for a later call to
|
||||
* {@link Chart#redraw}.
|
||||
*/
|
||||
Axis.prototype.setCompare = function (compare, redraw) {
|
||||
if (!this.isXAxis) {
|
||||
this.series.forEach(function (series) {
|
||||
series.setCompare(compare);
|
||||
});
|
||||
if (pick(redraw, true)) {
|
||||
this.chart.redraw();
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Extend the tooltip formatter by adding support for the point.change variable
|
||||
* as well as the changeDecimals option.
|
||||
*
|
||||
* @ignore
|
||||
* @function Highcharts.Point#tooltipFormatter
|
||||
*
|
||||
* @param {string} pointFormat
|
||||
*/
|
||||
Point.prototype.tooltipFormatter = function (pointFormat) {
|
||||
var point = this;
|
||||
var numberFormatter = point.series.chart.numberFormatter;
|
||||
pointFormat = pointFormat.replace('{point.change}', (point.change > 0 ? '+' : '') + numberFormatter(point.change, pick(point.series.tooltipOptions.changeDecimals, 2)));
|
||||
return pointTooltipFormatter.apply(this, [pointFormat]);
|
||||
};
|
||||
/* ************************************************************************** *
|
||||
* End value compare logic *
|
||||
* ************************************************************************** */
|
||||
// Extend the Series prototype to create a separate series clip box. This is
|
||||
// related to using multiple panes, and a future pane logic should incorporate
|
||||
// this feature (#2754).
|
||||
addEvent(Series, 'render', function () {
|
||||
var chart = this.chart, clipHeight;
|
||||
// Only do this on not 3d (#2939, #5904) nor polar (#6057) charts, and only
|
||||
// if the series type handles clipping in the animate method (#2975).
|
||||
if (!(chart.is3d && chart.is3d()) &&
|
||||
!chart.polar &&
|
||||
this.xAxis &&
|
||||
!this.xAxis.isRadial // Gauge, #6192
|
||||
) {
|
||||
clipHeight = this.yAxis.len;
|
||||
// Include xAxis line width (#8031) but only if the Y axis ends on the
|
||||
// edge of the X axis (#11005).
|
||||
if (this.xAxis.axisLine) {
|
||||
var dist = chart.plotTop + chart.plotHeight -
|
||||
this.yAxis.pos - this.yAxis.len, lineHeightCorrection = Math.floor(this.xAxis.axisLine.strokeWidth() / 2);
|
||||
if (dist >= 0) {
|
||||
clipHeight -= Math.max(lineHeightCorrection - dist, 0);
|
||||
}
|
||||
}
|
||||
// First render, initial clip box
|
||||
if (!this.clipBox && this.animate) {
|
||||
this.clipBox = merge(chart.clipBox);
|
||||
this.clipBox.width = this.xAxis.len;
|
||||
this.clipBox.height = clipHeight;
|
||||
// On redrawing, resizing etc, update the clip rectangle
|
||||
}
|
||||
else if (chart[this.sharedClipKey]) {
|
||||
// animate in case resize is done during initial animation
|
||||
chart[this.sharedClipKey].animate({
|
||||
width: this.xAxis.len,
|
||||
height: clipHeight
|
||||
});
|
||||
// also change markers clip animation for consistency
|
||||
// (marker clip rects should exist only on chart init)
|
||||
if (chart[this.sharedClipKey + 'm']) {
|
||||
chart[this.sharedClipKey + 'm'].animate({
|
||||
width: this.xAxis.len
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
addEvent(Chart, 'update', function (e) {
|
||||
var options = e.options;
|
||||
// Use case: enabling scrollbar from a disabled state.
|
||||
// Scrollbar needs to be initialized from a controller, Navigator in this
|
||||
// case (#6615)
|
||||
if ('scrollbar' in options && this.navigator) {
|
||||
merge(true, this.options.scrollbar, options.scrollbar);
|
||||
this.navigator.update({}, false);
|
||||
delete options.scrollbar;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,405 @@
|
||||
/* *
|
||||
*
|
||||
* (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';
|
||||
/**
|
||||
* A valid color to be parsed and handled by Highcharts. Highcharts internally
|
||||
* supports hex colors like `#ffffff`, rgb colors like `rgb(255,255,255)` and
|
||||
* rgba colors like `rgba(255,255,255,1)`. Other colors may be supported by the
|
||||
* browsers and displayed correctly, but Highcharts is not able to process them
|
||||
* and apply concepts like opacity and brightening.
|
||||
*
|
||||
* @typedef {string} Highcharts.ColorString
|
||||
*/
|
||||
/**
|
||||
* A valid color type than can be parsed and handled by Highcharts. It can be a
|
||||
* color string, a gradient object, or a pattern object.
|
||||
*
|
||||
* @typedef {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} Highcharts.ColorType
|
||||
*/
|
||||
/**
|
||||
* Gradient options instead of a solid color.
|
||||
*
|
||||
* @example
|
||||
* // Linear gradient used as a color option
|
||||
* color: {
|
||||
* linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
|
||||
* stops: [
|
||||
* [0, '#003399'], // start
|
||||
* [0.5, '#ffffff'], // middle
|
||||
* [1, '#3366AA'] // end
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* @interface Highcharts.GradientColorObject
|
||||
*/ /**
|
||||
* Holds an object that defines the start position and the end position relative
|
||||
* to the shape.
|
||||
* @name Highcharts.GradientColorObject#linearGradient
|
||||
* @type {Highcharts.LinearGradientColorObject|undefined}
|
||||
*/ /**
|
||||
* Holds an object that defines the center position and the radius.
|
||||
* @name Highcharts.GradientColorObject#radialGradient
|
||||
* @type {Highcharts.RadialGradientColorObject|undefined}
|
||||
*/ /**
|
||||
* The first item in each tuple is the position in the gradient, where 0 is the
|
||||
* start of the gradient and 1 is the end of the gradient. Multiple stops can be
|
||||
* applied. The second item is the color for each stop. This color can also be
|
||||
* given in the rgba format.
|
||||
* @name Highcharts.GradientColorObject#stops
|
||||
* @type {Array<Highcharts.GradientColorStopObject>}
|
||||
*/
|
||||
/**
|
||||
* Color stop tuple.
|
||||
*
|
||||
* @see Highcharts.GradientColorObject
|
||||
*
|
||||
* @interface Highcharts.GradientColorStopObject
|
||||
*/ /**
|
||||
* @name Highcharts.GradientColorStopObject#0
|
||||
* @type {number}
|
||||
*/ /**
|
||||
* @name Highcharts.GradientColorStopObject#1
|
||||
* @type {Highcharts.ColorString}
|
||||
*/ /**
|
||||
* @name Highcharts.GradientColorStopObject#color
|
||||
* @type {Highcharts.Color|undefined}
|
||||
*/
|
||||
/**
|
||||
* Defines the start position and the end position for a gradient relative
|
||||
* to the shape. Start position (x1, y1) and end position (x2, y2) are relative
|
||||
* to the shape, where 0 means top/left and 1 is bottom/right.
|
||||
*
|
||||
* @interface Highcharts.LinearGradientColorObject
|
||||
*/ /**
|
||||
* Start horizontal position of the gradient. Float ranges 0-1.
|
||||
* @name Highcharts.LinearGradientColorObject#x1
|
||||
* @type {number}
|
||||
*/ /**
|
||||
* End horizontal position of the gradient. Float ranges 0-1.
|
||||
* @name Highcharts.LinearGradientColorObject#x2
|
||||
* @type {number}
|
||||
*/ /**
|
||||
* Start vertical position of the gradient. Float ranges 0-1.
|
||||
* @name Highcharts.LinearGradientColorObject#y1
|
||||
* @type {number}
|
||||
*/ /**
|
||||
* End vertical position of the gradient. Float ranges 0-1.
|
||||
* @name Highcharts.LinearGradientColorObject#y2
|
||||
* @type {number}
|
||||
*/
|
||||
/**
|
||||
* Defines the center position and the radius for a gradient.
|
||||
*
|
||||
* @interface Highcharts.RadialGradientColorObject
|
||||
*/ /**
|
||||
* Center horizontal position relative to the shape. Float ranges 0-1.
|
||||
* @name Highcharts.RadialGradientColorObject#cx
|
||||
* @type {number}
|
||||
*/ /**
|
||||
* Center vertical position relative to the shape. Float ranges 0-1.
|
||||
* @name Highcharts.RadialGradientColorObject#cy
|
||||
* @type {number}
|
||||
*/ /**
|
||||
* Radius relative to the shape. Float ranges 0-1.
|
||||
* @name Highcharts.RadialGradientColorObject#r
|
||||
* @type {number}
|
||||
*/
|
||||
import U from './Utilities.js';
|
||||
var isNumber = U.isNumber, merge = U.merge, pInt = U.pInt;
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* Handle color operations. Some object methods are chainable.
|
||||
*
|
||||
* @class
|
||||
* @name Highcharts.Color
|
||||
*
|
||||
* @param {Highcharts.ColorType} input
|
||||
* The input color in either rbga or hex format
|
||||
*/
|
||||
var Color = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function Color(input) {
|
||||
// Collection of parsers. This can be extended from the outside by pushing
|
||||
// parsers to Highcharts.Color.prototype.parsers.
|
||||
this.parsers = [{
|
||||
// RGBA color
|
||||
// eslint-disable-next-line max-len
|
||||
regex: /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,
|
||||
parse: function (result) {
|
||||
return [
|
||||
pInt(result[1]),
|
||||
pInt(result[2]),
|
||||
pInt(result[3]),
|
||||
parseFloat(result[4], 10)
|
||||
];
|
||||
}
|
||||
}, {
|
||||
// RGB color
|
||||
regex: /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,
|
||||
parse: function (result) {
|
||||
return [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1];
|
||||
}
|
||||
}];
|
||||
this.rgba = [];
|
||||
// Backwards compatibility, allow instanciation without new (#13053)
|
||||
if (!(this instanceof Color)) {
|
||||
return new Color(input);
|
||||
}
|
||||
this.init(input);
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Static Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Creates a color instance out of a color string or object.
|
||||
*
|
||||
* @function Highcharts.Color.parse
|
||||
*
|
||||
* @param {Highcharts.ColorType} input
|
||||
* The input color in either rbga or hex format.
|
||||
*
|
||||
* @return {Highcharts.Color}
|
||||
* Color instance.
|
||||
*/
|
||||
Color.parse = function (input) {
|
||||
return new Color(input);
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Parse the input color to rgba array
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Color#init
|
||||
*
|
||||
* @param {Highcharts.ColorType} input
|
||||
* The input color in either rbga or hex format
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
Color.prototype.init = function (input) {
|
||||
var result, rgba, i, parser, len;
|
||||
this.input = input = Color.names[input && input.toLowerCase ?
|
||||
input.toLowerCase() :
|
||||
''] || input;
|
||||
// Gradients
|
||||
if (input && input.stops) {
|
||||
this.stops = input.stops.map(function (stop) {
|
||||
return new Color(stop[1]);
|
||||
});
|
||||
// Solid colors
|
||||
}
|
||||
else {
|
||||
// Bitmasking as input[0] is not working for legacy IE.
|
||||
if (input &&
|
||||
input.charAt &&
|
||||
input.charAt() === '#') {
|
||||
len = input.length;
|
||||
input = parseInt(input.substr(1), 16);
|
||||
// Handle long-form, e.g. #AABBCC
|
||||
if (len === 7) {
|
||||
rgba = [
|
||||
(input & 0xFF0000) >> 16,
|
||||
(input & 0xFF00) >> 8,
|
||||
(input & 0xFF),
|
||||
1
|
||||
];
|
||||
// Handle short-form, e.g. #ABC
|
||||
// In short form, the value is assumed to be the same
|
||||
// for both nibbles for each component. e.g. #ABC = #AABBCC
|
||||
}
|
||||
else if (len === 4) {
|
||||
rgba = [
|
||||
(((input & 0xF00) >> 4) |
|
||||
(input & 0xF00) >> 8),
|
||||
(((input & 0xF0) >> 4) |
|
||||
(input & 0xF0)),
|
||||
((input & 0xF) << 4) | (input & 0xF),
|
||||
1
|
||||
];
|
||||
}
|
||||
}
|
||||
// Otherwise, check regex parsers
|
||||
if (!rgba) {
|
||||
i = this.parsers.length;
|
||||
while (i-- && !rgba) {
|
||||
parser = this.parsers[i];
|
||||
result = parser.regex.exec(input);
|
||||
if (result) {
|
||||
rgba = parser.parse(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.rgba = rgba || [];
|
||||
};
|
||||
/**
|
||||
* Return the color or gradient stops in the specified format
|
||||
*
|
||||
* @function Highcharts.Color#get
|
||||
*
|
||||
* @param {string} [format]
|
||||
* Possible values are 'a', 'rgb', 'rgba' (default).
|
||||
*
|
||||
* @return {Highcharts.ColorType}
|
||||
* This color as a string or gradient stops.
|
||||
*/
|
||||
Color.prototype.get = function (format) {
|
||||
var input = this.input, rgba = this.rgba, ret;
|
||||
if (typeof this.stops !== 'undefined') {
|
||||
ret = merge(input);
|
||||
ret.stops = [].concat(ret.stops);
|
||||
this.stops.forEach(function (stop, i) {
|
||||
ret.stops[i] = [
|
||||
ret.stops[i][0],
|
||||
stop.get(format)
|
||||
];
|
||||
});
|
||||
// it's NaN if gradient colors on a column chart
|
||||
}
|
||||
else if (rgba && isNumber(rgba[0])) {
|
||||
if (format === 'rgb' || (!format && rgba[3] === 1)) {
|
||||
ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
|
||||
}
|
||||
else if (format === 'a') {
|
||||
ret = rgba[3];
|
||||
}
|
||||
else {
|
||||
ret = 'rgba(' + rgba.join(',') + ')';
|
||||
}
|
||||
}
|
||||
else {
|
||||
ret = input;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
/**
|
||||
* Brighten the color instance.
|
||||
*
|
||||
* @function Highcharts.Color#brighten
|
||||
*
|
||||
* @param {number} alpha
|
||||
* The alpha value.
|
||||
*
|
||||
* @return {Highcharts.Color}
|
||||
* This color with modifications.
|
||||
*/
|
||||
Color.prototype.brighten = function (alpha) {
|
||||
var i, rgba = this.rgba;
|
||||
if (this.stops) {
|
||||
this.stops.forEach(function (stop) {
|
||||
stop.brighten(alpha);
|
||||
});
|
||||
}
|
||||
else if (isNumber(alpha) && alpha !== 0) {
|
||||
for (i = 0; i < 3; i++) {
|
||||
rgba[i] += pInt(alpha * 255);
|
||||
if (rgba[i] < 0) {
|
||||
rgba[i] = 0;
|
||||
}
|
||||
if (rgba[i] > 255) {
|
||||
rgba[i] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
/**
|
||||
* Set the color's opacity to a given alpha value.
|
||||
*
|
||||
* @function Highcharts.Color#setOpacity
|
||||
*
|
||||
* @param {number} alpha
|
||||
* Opacity between 0 and 1.
|
||||
*
|
||||
* @return {Highcharts.Color}
|
||||
* Color with modifications.
|
||||
*/
|
||||
Color.prototype.setOpacity = function (alpha) {
|
||||
this.rgba[3] = alpha;
|
||||
return this;
|
||||
};
|
||||
/**
|
||||
* Return an intermediate color between two colors.
|
||||
*
|
||||
* @function Highcharts.Color#tweenTo
|
||||
*
|
||||
* @param {Highcharts.Color} to
|
||||
* The color object to tween to.
|
||||
*
|
||||
* @param {number} pos
|
||||
* The intermediate position, where 0 is the from color (current
|
||||
* color item), and 1 is the `to` color.
|
||||
*
|
||||
* @return {Highcharts.ColorString}
|
||||
* The intermediate color in rgba notation.
|
||||
*/
|
||||
Color.prototype.tweenTo = function (to, pos) {
|
||||
// Check for has alpha, because rgba colors perform worse due to lack of
|
||||
// support in WebKit.
|
||||
var fromRgba = this.rgba, toRgba = to.rgba, hasAlpha, ret;
|
||||
// Unsupported color, return to-color (#3920, #7034)
|
||||
if (!toRgba.length || !fromRgba || !fromRgba.length) {
|
||||
ret = to.input || 'none';
|
||||
// Interpolate
|
||||
}
|
||||
else {
|
||||
hasAlpha = (toRgba[3] !== 1 || fromRgba[3] !== 1);
|
||||
ret = (hasAlpha ? 'rgba(' : 'rgb(') +
|
||||
Math.round(toRgba[0] + (fromRgba[0] - toRgba[0]) * (1 - pos)) +
|
||||
',' +
|
||||
Math.round(toRgba[1] + (fromRgba[1] - toRgba[1]) * (1 - pos)) +
|
||||
',' +
|
||||
Math.round(toRgba[2] + (fromRgba[2] - toRgba[2]) * (1 - pos)) +
|
||||
(hasAlpha ?
|
||||
(',' +
|
||||
(toRgba[3] + (fromRgba[3] - toRgba[3]) * (1 - pos))) :
|
||||
'') +
|
||||
')';
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Static Properties
|
||||
*
|
||||
* */
|
||||
// Collection of named colors. Can be extended from the outside by adding
|
||||
// colors to Highcharts.Color.names.
|
||||
Color.names = {
|
||||
white: '#ffffff',
|
||||
black: '#000000'
|
||||
};
|
||||
return Color;
|
||||
}());
|
||||
H.Color = Color;
|
||||
/**
|
||||
* Creates a color instance out of a color string.
|
||||
*
|
||||
* @function Highcharts.color
|
||||
*
|
||||
* @param {Highcharts.ColorType} input
|
||||
* The input color in either rbga or hex format
|
||||
*
|
||||
* @return {Highcharts.Color}
|
||||
* Color instance
|
||||
*/
|
||||
H.color = Color.parse;
|
||||
export default H.Color;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,85 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
/* globals Image, window */
|
||||
/**
|
||||
* Reference to the global SVGElement class as a workaround for a name conflict
|
||||
* in the Highcharts namespace.
|
||||
*
|
||||
* @global
|
||||
* @typedef {global.SVGElement} GlobalSVGElement
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/SVGElement
|
||||
*/
|
||||
// glob is a temporary fix to allow our es-modules to work.
|
||||
var glob = ( // @todo UMD variable named `window`, and glob named `win`
|
||||
typeof win !== 'undefined' ?
|
||||
win :
|
||||
typeof window !== 'undefined' ?
|
||||
window :
|
||||
{}), doc = glob.document, SVG_NS = 'http://www.w3.org/2000/svg', userAgent = (glob.navigator && glob.navigator.userAgent) || '', svg = (doc &&
|
||||
doc.createElementNS &&
|
||||
!!doc.createElementNS(SVG_NS, 'svg').createSVGRect), isMS = /(edge|msie|trident)/i.test(userAgent) && !glob.opera, isFirefox = userAgent.indexOf('Firefox') !== -1, isChrome = userAgent.indexOf('Chrome') !== -1, hasBidiBug = (isFirefox &&
|
||||
parseInt(userAgent.split('Firefox/')[1], 10) < 4 // issue #38
|
||||
);
|
||||
var H = {
|
||||
product: 'Highcharts',
|
||||
version: '8.2.0',
|
||||
deg2rad: Math.PI * 2 / 360,
|
||||
doc: doc,
|
||||
hasBidiBug: hasBidiBug,
|
||||
hasTouch: !!glob.TouchEvent,
|
||||
isMS: isMS,
|
||||
isWebKit: userAgent.indexOf('AppleWebKit') !== -1,
|
||||
isFirefox: isFirefox,
|
||||
isChrome: isChrome,
|
||||
isSafari: !isChrome && userAgent.indexOf('Safari') !== -1,
|
||||
isTouchDevice: /(Mobile|Android|Windows Phone)/.test(userAgent),
|
||||
SVG_NS: SVG_NS,
|
||||
chartCount: 0,
|
||||
seriesTypes: {},
|
||||
symbolSizes: {},
|
||||
svg: svg,
|
||||
win: glob,
|
||||
marginNames: ['plotTop', 'marginRight', 'marginBottom', 'plotLeft'],
|
||||
noop: function () { },
|
||||
/**
|
||||
* Theme options that should get applied to the chart. In module mode it
|
||||
* might not be possible to change this property because of read-only
|
||||
* restrictions, instead use {@link Highcharts.setOptions}.
|
||||
*
|
||||
* @name Highcharts.theme
|
||||
* @type {Highcharts.Options}
|
||||
*/
|
||||
/**
|
||||
* An array containing the current chart objects in the page. A chart's
|
||||
* position in the array is preserved throughout the page's lifetime. When
|
||||
* a chart is destroyed, the array item becomes `undefined`.
|
||||
*
|
||||
* @name Highcharts.charts
|
||||
* @type {Array<Highcharts.Chart|undefined>}
|
||||
*/
|
||||
charts: [],
|
||||
/**
|
||||
* A hook for defining additional date format specifiers. New
|
||||
* specifiers are defined as key-value pairs by using the
|
||||
* specifier as key, and a function which takes the timestamp as
|
||||
* value. This function returns the formatted portion of the
|
||||
* date.
|
||||
*
|
||||
* @sample highcharts/global/dateformats/
|
||||
* Adding support for week number
|
||||
*
|
||||
* @name Highcharts.dateFormats
|
||||
* @type {Highcharts.Dictionary<Highcharts.TimeFormatCallbackFunction>}
|
||||
*/
|
||||
dateFormats: {}
|
||||
};
|
||||
export default H;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,160 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
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 H from './Globals.js';
|
||||
var charts = H.charts, doc = H.doc, noop = H.noop, win = H.win;
|
||||
import Pointer from './Pointer.js';
|
||||
import U from './Utilities.js';
|
||||
var addEvent = U.addEvent, css = U.css, objectEach = U.objectEach, removeEvent = U.removeEvent;
|
||||
/* globals MSPointerEvent, PointerEvent */
|
||||
// The touches object keeps track of the points being touched at all times
|
||||
var touches = {};
|
||||
var hasPointerEvent = !!win.PointerEvent;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/** @private */
|
||||
function getWebkitTouches() {
|
||||
var fake = [];
|
||||
fake.item = function (i) {
|
||||
return this[i];
|
||||
};
|
||||
objectEach(touches, function (touch) {
|
||||
fake.push({
|
||||
pageX: touch.pageX,
|
||||
pageY: touch.pageY,
|
||||
target: touch.target
|
||||
});
|
||||
});
|
||||
return fake;
|
||||
}
|
||||
/** @private */
|
||||
function translateMSPointer(e, method, wktype, func) {
|
||||
var p;
|
||||
if ((e.pointerType === 'touch' ||
|
||||
e.pointerType === e.MSPOINTER_TYPE_TOUCH) && charts[H.hoverChartIndex]) {
|
||||
func(e);
|
||||
p = charts[H.hoverChartIndex].pointer;
|
||||
p[method]({
|
||||
type: wktype,
|
||||
target: e.currentTarget,
|
||||
preventDefault: noop,
|
||||
touches: getWebkitTouches()
|
||||
});
|
||||
}
|
||||
}
|
||||
/** @private */
|
||||
var MSPointer = /** @class */ (function (_super) {
|
||||
__extends(MSPointer, _super);
|
||||
function MSPointer() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Add or remove the MS Pointer specific events
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Pointer#batchMSEvents
|
||||
*
|
||||
* @param {Function} fn
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
MSPointer.prototype.batchMSEvents = function (fn) {
|
||||
fn(this.chart.container, hasPointerEvent ? 'pointerdown' : 'MSPointerDown', this.onContainerPointerDown);
|
||||
fn(this.chart.container, hasPointerEvent ? 'pointermove' : 'MSPointerMove', this.onContainerPointerMove);
|
||||
fn(doc, hasPointerEvent ? 'pointerup' : 'MSPointerUp', this.onDocumentPointerUp);
|
||||
};
|
||||
// Destroy MS events also
|
||||
MSPointer.prototype.destroy = function () {
|
||||
this.batchMSEvents(removeEvent);
|
||||
_super.prototype.destroy.call(this);
|
||||
};
|
||||
// Disable default IE actions for pinch and such on chart element
|
||||
MSPointer.prototype.init = function (chart, options) {
|
||||
_super.prototype.init.call(this, chart, options);
|
||||
if (this.hasZoom) { // #4014
|
||||
css(chart.container, {
|
||||
'-ms-touch-action': 'none',
|
||||
'touch-action': 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @function Highcharts.Pointer#onContainerPointerDown
|
||||
*
|
||||
* @param {Highcharts.PointerEventObject} e
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
MSPointer.prototype.onContainerPointerDown = function (e) {
|
||||
translateMSPointer(e, 'onContainerTouchStart', 'touchstart', function (e) {
|
||||
touches[e.pointerId] = {
|
||||
pageX: e.pageX,
|
||||
pageY: e.pageY,
|
||||
target: e.currentTarget
|
||||
};
|
||||
});
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @function Highcharts.Pointer#onContainerPointerMove
|
||||
*
|
||||
* @param {Highcharts.PointerEventObject} e
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
MSPointer.prototype.onContainerPointerMove = function (e) {
|
||||
translateMSPointer(e, 'onContainerTouchMove', 'touchmove', function (e) {
|
||||
touches[e.pointerId] = ({ pageX: e.pageX, pageY: e.pageY });
|
||||
if (!touches[e.pointerId].target) {
|
||||
touches[e.pointerId].target = e.currentTarget;
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @function Highcharts.Pointer#onDocumentPointerUp
|
||||
*
|
||||
* @param {Highcharts.PointerEventObject} e
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
MSPointer.prototype.onDocumentPointerUp = function (e) {
|
||||
translateMSPointer(e, 'onDocumentTouchEnd', 'touchend', function (e) {
|
||||
delete touches[e.pointerId];
|
||||
});
|
||||
};
|
||||
// Add IE specific touch events to chart
|
||||
MSPointer.prototype.setDOMEvents = function () {
|
||||
_super.prototype.setDOMEvents.call(this);
|
||||
if (this.hasZoom || this.followTouchMove) {
|
||||
this.batchMSEvents(addEvent);
|
||||
}
|
||||
};
|
||||
return MSPointer;
|
||||
}(Pointer));
|
||||
export default MSPointer;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,427 @@
|
||||
/* *
|
||||
*
|
||||
* (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';
|
||||
import SVGElement from '../SVG/SVGElement.js';
|
||||
import SVGRenderer from '../SVG/SVGRenderer.js';
|
||||
import U from '../../Utilities.js';
|
||||
var attr = U.attr, createElement = U.createElement, css = U.css, defined = U.defined, extend = U.extend, pick = U.pick, pInt = U.pInt;
|
||||
var isFirefox = H.isFirefox, isMS = H.isMS, isWebKit = H.isWebKit, win = H.win;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
// Extend SvgElement for useHTML option.
|
||||
extend(SVGElement.prototype, /** @lends SVGElement.prototype */ {
|
||||
/**
|
||||
* Apply CSS to HTML elements. This is used in text within SVG rendering and
|
||||
* by the VML renderer
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.SVGElement#htmlCss
|
||||
*
|
||||
* @param {Highcharts.CSSObject} styles
|
||||
*
|
||||
* @return {Highcharts.SVGElement}
|
||||
*/
|
||||
htmlCss: function (styles) {
|
||||
var wrapper = this, element = wrapper.element,
|
||||
// When setting or unsetting the width style, we need to update
|
||||
// transform (#8809)
|
||||
isSettingWidth = (element.tagName === 'SPAN' &&
|
||||
styles &&
|
||||
'width' in styles), textWidth = pick(isSettingWidth && styles.width, void 0), doTransform;
|
||||
if (isSettingWidth) {
|
||||
delete styles.width;
|
||||
wrapper.textWidth = textWidth;
|
||||
doTransform = true;
|
||||
}
|
||||
if (styles && styles.textOverflow === 'ellipsis') {
|
||||
styles.whiteSpace = 'nowrap';
|
||||
styles.overflow = 'hidden';
|
||||
}
|
||||
wrapper.styles = extend(wrapper.styles, styles);
|
||||
css(wrapper.element, styles);
|
||||
// Now that all styles are applied, to the transform
|
||||
if (doTransform) {
|
||||
wrapper.htmlUpdateTransform();
|
||||
}
|
||||
return wrapper;
|
||||
},
|
||||
/**
|
||||
* VML and useHTML method for calculating the bounding box based on offsets.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.SVGElement#htmlGetBBox
|
||||
*
|
||||
* @param {boolean} refresh
|
||||
* Whether to force a fresh value from the DOM or to use the cached
|
||||
* value.
|
||||
*
|
||||
* @return {Highcharts.BBoxObject}
|
||||
* A hash containing values for x, y, width and height.
|
||||
*/
|
||||
htmlGetBBox: function () {
|
||||
var wrapper = this, element = wrapper.element;
|
||||
return {
|
||||
x: element.offsetLeft,
|
||||
y: element.offsetTop,
|
||||
width: element.offsetWidth,
|
||||
height: element.offsetHeight
|
||||
};
|
||||
},
|
||||
/**
|
||||
* VML override private method to update elements based on internal
|
||||
* properties based on SVG transform.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.SVGElement#htmlUpdateTransform
|
||||
* @return {void}
|
||||
*/
|
||||
htmlUpdateTransform: function () {
|
||||
// aligning non added elements is expensive
|
||||
if (!this.added) {
|
||||
this.alignOnAdd = true;
|
||||
return;
|
||||
}
|
||||
var wrapper = this, renderer = wrapper.renderer, elem = wrapper.element, translateX = wrapper.translateX || 0, translateY = wrapper.translateY || 0, x = wrapper.x || 0, y = wrapper.y || 0, align = wrapper.textAlign || 'left', alignCorrection = {
|
||||
left: 0, center: 0.5, right: 1
|
||||
}[align], styles = wrapper.styles, whiteSpace = styles && styles.whiteSpace;
|
||||
/**
|
||||
* @private
|
||||
* @return {number}
|
||||
*/
|
||||
function getTextPxLength() {
|
||||
// Reset multiline/ellipsis in order to read width (#4928,
|
||||
// #5417)
|
||||
css(elem, {
|
||||
width: '',
|
||||
whiteSpace: whiteSpace || 'nowrap'
|
||||
});
|
||||
return elem.offsetWidth;
|
||||
}
|
||||
// apply translate
|
||||
css(elem, {
|
||||
marginLeft: translateX,
|
||||
marginTop: translateY
|
||||
});
|
||||
if (!renderer.styledMode && wrapper.shadows) { // used in labels/tooltip
|
||||
wrapper.shadows.forEach(function (shadow) {
|
||||
css(shadow, {
|
||||
marginLeft: translateX + 1,
|
||||
marginTop: translateY + 1
|
||||
});
|
||||
});
|
||||
}
|
||||
// apply inversion
|
||||
if (wrapper.inverted) { // wrapper is a group
|
||||
[].forEach.call(elem.childNodes, function (child) {
|
||||
renderer.invertChild(child, elem);
|
||||
});
|
||||
}
|
||||
if (elem.tagName === 'SPAN') {
|
||||
var rotation = wrapper.rotation, baseline, textWidth = wrapper.textWidth && pInt(wrapper.textWidth), currentTextTransform = [
|
||||
rotation,
|
||||
align,
|
||||
elem.innerHTML,
|
||||
wrapper.textWidth,
|
||||
wrapper.textAlign
|
||||
].join(',');
|
||||
// Update textWidth. Use the memoized textPxLength if possible, to
|
||||
// avoid the getTextPxLength function using elem.offsetWidth.
|
||||
// Calling offsetWidth affects rendering time as it forces layout
|
||||
// (#7656).
|
||||
if (textWidth !== wrapper.oldTextWidth &&
|
||||
((textWidth > wrapper.oldTextWidth) ||
|
||||
(wrapper.textPxLength || getTextPxLength()) > textWidth) && (
|
||||
// Only set the width if the text is able to word-wrap, or
|
||||
// text-overflow is ellipsis (#9537)
|
||||
/[ \-]/.test(elem.textContent || elem.innerText) ||
|
||||
elem.style.textOverflow === 'ellipsis')) { // #983, #1254
|
||||
css(elem, {
|
||||
width: textWidth + 'px',
|
||||
display: 'block',
|
||||
whiteSpace: whiteSpace || 'normal' // #3331
|
||||
});
|
||||
wrapper.oldTextWidth = textWidth;
|
||||
wrapper.hasBoxWidthChanged = true; // #8159
|
||||
}
|
||||
else {
|
||||
wrapper.hasBoxWidthChanged = false; // #8159
|
||||
}
|
||||
// Do the calculations and DOM access only if properties changed
|
||||
if (currentTextTransform !== wrapper.cTT) {
|
||||
baseline = renderer.fontMetrics(elem.style.fontSize, elem).b;
|
||||
// Renderer specific handling of span rotation, but only if we
|
||||
// have something to update.
|
||||
if (defined(rotation) &&
|
||||
((rotation !== (wrapper.oldRotation || 0)) ||
|
||||
(align !== wrapper.oldAlign))) {
|
||||
wrapper.setSpanRotation(rotation, alignCorrection, baseline);
|
||||
}
|
||||
wrapper.getSpanCorrection(
|
||||
// Avoid elem.offsetWidth if we can, it affects rendering
|
||||
// time heavily (#7656)
|
||||
((!defined(rotation) && wrapper.textPxLength) || // #7920
|
||||
elem.offsetWidth), baseline, alignCorrection, rotation, align);
|
||||
}
|
||||
// apply position with correction
|
||||
css(elem, {
|
||||
left: (x + (wrapper.xCorr || 0)) + 'px',
|
||||
top: (y + (wrapper.yCorr || 0)) + 'px'
|
||||
});
|
||||
// record current text transform
|
||||
wrapper.cTT = currentTextTransform;
|
||||
wrapper.oldRotation = rotation;
|
||||
wrapper.oldAlign = align;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Set the rotation of an individual HTML span.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.SVGElement#setSpanRotation
|
||||
* @param {number} rotation
|
||||
* @param {number} alignCorrection
|
||||
* @param {number} baseline
|
||||
* @return {void}
|
||||
*/
|
||||
setSpanRotation: function (rotation, alignCorrection, baseline) {
|
||||
var rotationStyle = {}, cssTransformKey = this.renderer.getTransformKey();
|
||||
rotationStyle[cssTransformKey] = rotationStyle.transform =
|
||||
'rotate(' + rotation + 'deg)';
|
||||
rotationStyle[cssTransformKey + (isFirefox ? 'Origin' : '-origin')] =
|
||||
rotationStyle.transformOrigin =
|
||||
(alignCorrection * 100) + '% ' + baseline + 'px';
|
||||
css(this.element, rotationStyle);
|
||||
},
|
||||
/**
|
||||
* Get the correction in X and Y positioning as the element is rotated.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.SVGElement#getSpanCorrection
|
||||
* @param {number} width
|
||||
* @param {number} baseline
|
||||
* @param {number} alignCorrection
|
||||
* @return {void}
|
||||
*/
|
||||
getSpanCorrection: function (width, baseline, alignCorrection) {
|
||||
this.xCorr = -width * alignCorrection;
|
||||
this.yCorr = -baseline;
|
||||
}
|
||||
});
|
||||
// Extend SvgRenderer for useHTML option.
|
||||
extend(SVGRenderer.prototype, /** @lends SVGRenderer.prototype */ {
|
||||
/**
|
||||
* @private
|
||||
* @function Highcharts.SVGRenderer#getTransformKey
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
getTransformKey: function () {
|
||||
return isMS && !/Edge/.test(win.navigator.userAgent) ?
|
||||
'-ms-transform' :
|
||||
isWebKit ?
|
||||
'-webkit-transform' :
|
||||
isFirefox ?
|
||||
'MozTransform' :
|
||||
win.opera ?
|
||||
'-o-transform' :
|
||||
'';
|
||||
},
|
||||
/**
|
||||
* Create HTML text node. This is used by the VML renderer as well as the
|
||||
* SVG renderer through the useHTML option.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.SVGRenderer#html
|
||||
*
|
||||
* @param {string} str
|
||||
* The text of (subset) HTML to draw.
|
||||
*
|
||||
* @param {number} x
|
||||
* The x position of the text's lower left corner.
|
||||
*
|
||||
* @param {number} y
|
||||
* The y position of the text's lower left corner.
|
||||
*
|
||||
* @return {Highcharts.HTMLDOMElement}
|
||||
*/
|
||||
html: function (str, x, y) {
|
||||
var wrapper = this.createElement('span'), element = wrapper.element, renderer = wrapper.renderer, isSVG = renderer.isSVG, addSetters = function (gWrapper, style) {
|
||||
// These properties are set as attributes on the SVG group, and
|
||||
// as identical CSS properties on the div. (#3542)
|
||||
['opacity', 'visibility'].forEach(function (prop) {
|
||||
gWrapper[prop + 'Setter'] = function (value, key, elem) {
|
||||
var styleObject = gWrapper.div ?
|
||||
gWrapper.div.style :
|
||||
style;
|
||||
SVGElement.prototype[prop + 'Setter']
|
||||
.call(this, value, key, elem);
|
||||
if (styleObject) {
|
||||
styleObject[key] = value;
|
||||
}
|
||||
};
|
||||
});
|
||||
gWrapper.addedSetters = true;
|
||||
};
|
||||
// Text setter
|
||||
wrapper.textSetter = function (value) {
|
||||
if (value !== element.innerHTML) {
|
||||
delete this.bBox;
|
||||
delete this.oldTextWidth;
|
||||
}
|
||||
this.textStr = value;
|
||||
element.innerHTML = pick(value, '');
|
||||
wrapper.doTransform = true;
|
||||
};
|
||||
// Add setters for the element itself (#4938)
|
||||
if (isSVG) { // #4938, only for HTML within SVG
|
||||
addSetters(wrapper, wrapper.element.style);
|
||||
}
|
||||
// Various setters which rely on update transform
|
||||
wrapper.xSetter =
|
||||
wrapper.ySetter =
|
||||
wrapper.alignSetter =
|
||||
wrapper.rotationSetter =
|
||||
function (value, key) {
|
||||
if (key === 'align') {
|
||||
// Do not overwrite the SVGElement.align method. Same as VML.
|
||||
wrapper.alignValue = wrapper.textAlign = value;
|
||||
}
|
||||
else {
|
||||
wrapper[key] = value;
|
||||
}
|
||||
wrapper.doTransform = true;
|
||||
};
|
||||
// Runs at the end of .attr()
|
||||
wrapper.afterSetters = function () {
|
||||
// Update transform. Do this outside the loop to prevent redundant
|
||||
// updating for batch setting of attributes.
|
||||
if (this.doTransform) {
|
||||
this.htmlUpdateTransform();
|
||||
this.doTransform = false;
|
||||
}
|
||||
};
|
||||
// Set the default attributes
|
||||
wrapper
|
||||
.attr({
|
||||
text: str,
|
||||
x: Math.round(x),
|
||||
y: Math.round(y)
|
||||
})
|
||||
.css({
|
||||
position: 'absolute'
|
||||
});
|
||||
if (!renderer.styledMode) {
|
||||
wrapper.css({
|
||||
fontFamily: this.style.fontFamily,
|
||||
fontSize: this.style.fontSize
|
||||
});
|
||||
}
|
||||
// Keep the whiteSpace style outside the wrapper.styles collection
|
||||
element.style.whiteSpace = 'nowrap';
|
||||
// Use the HTML specific .css method
|
||||
wrapper.css = wrapper.htmlCss;
|
||||
// This is specific for HTML within SVG
|
||||
if (isSVG) {
|
||||
wrapper.add = function (svgGroupWrapper) {
|
||||
var htmlGroup, container = renderer.box.parentNode, parentGroup, parents = [];
|
||||
this.parentGroup = svgGroupWrapper;
|
||||
// Create a mock group to hold the HTML elements
|
||||
if (svgGroupWrapper) {
|
||||
htmlGroup = svgGroupWrapper.div;
|
||||
if (!htmlGroup) {
|
||||
// Read the parent chain into an array and read from top
|
||||
// down
|
||||
parentGroup = svgGroupWrapper;
|
||||
while (parentGroup) {
|
||||
parents.push(parentGroup);
|
||||
// Move up to the next parent group
|
||||
parentGroup = parentGroup.parentGroup;
|
||||
}
|
||||
// Ensure dynamically updating position when any parent
|
||||
// is translated
|
||||
parents.reverse().forEach(function (parentGroup) {
|
||||
var htmlGroupStyle, cls = attr(parentGroup.element, 'class');
|
||||
/**
|
||||
* Common translate setter for X and Y on the HTML
|
||||
* group. Reverted the fix for #6957 du to
|
||||
* positioning problems and offline export (#7254,
|
||||
* #7280, #7529)
|
||||
* @private
|
||||
* @param {*} value
|
||||
* @param {string} key
|
||||
* @return {void}
|
||||
*/
|
||||
function translateSetter(value, key) {
|
||||
parentGroup[key] = value;
|
||||
if (key === 'translateX') {
|
||||
htmlGroupStyle.left = value + 'px';
|
||||
}
|
||||
else {
|
||||
htmlGroupStyle.top = value + 'px';
|
||||
}
|
||||
parentGroup.doTransform = true;
|
||||
}
|
||||
// Create a HTML div and append it to the parent div
|
||||
// to emulate the SVG group structure
|
||||
htmlGroup =
|
||||
parentGroup.div =
|
||||
parentGroup.div || createElement('div', cls ? { className: cls } : void 0, {
|
||||
position: 'absolute',
|
||||
left: (parentGroup.translateX || 0) + 'px',
|
||||
top: (parentGroup.translateY || 0) + 'px',
|
||||
display: parentGroup.display,
|
||||
opacity: parentGroup.opacity,
|
||||
pointerEvents: (parentGroup.styles &&
|
||||
parentGroup.styles.pointerEvents) // #5595
|
||||
// the top group is appended to container
|
||||
}, htmlGroup || container);
|
||||
// Shortcut
|
||||
htmlGroupStyle = htmlGroup.style;
|
||||
// Set listeners to update the HTML div's position
|
||||
// whenever the SVG group position is changed.
|
||||
extend(parentGroup, {
|
||||
// (#7287) Pass htmlGroup to use
|
||||
// the related group
|
||||
classSetter: (function (htmlGroup) {
|
||||
return function (value) {
|
||||
this.element.setAttribute('class', value);
|
||||
htmlGroup.className = value;
|
||||
};
|
||||
}(htmlGroup)),
|
||||
on: function () {
|
||||
if (parents[0].div) { // #6418
|
||||
wrapper.on.apply({ element: parents[0].div }, arguments);
|
||||
}
|
||||
return parentGroup;
|
||||
},
|
||||
translateXSetter: translateSetter,
|
||||
translateYSetter: translateSetter
|
||||
});
|
||||
if (!parentGroup.addedSetters) {
|
||||
addSetters(parentGroup);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
htmlGroup = container;
|
||||
}
|
||||
htmlGroup.appendChild(element);
|
||||
// Shared with VML:
|
||||
wrapper.added = true;
|
||||
if (wrapper.alignOnAdd) {
|
||||
wrapper.htmlUpdateTransform();
|
||||
}
|
||||
return wrapper;
|
||||
};
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,411 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
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 SVGElement from './SVGElement.js';
|
||||
import U from '../../Utilities.js';
|
||||
var defined = U.defined, extend = U.extend, isNumber = U.isNumber, merge = U.merge, removeEvent = U.removeEvent;
|
||||
/**
|
||||
* SVG label to render text.
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.SVGLabel
|
||||
* @augments Highcharts.SVGElement
|
||||
*/
|
||||
var SVGLabel = /** @class */ (function (_super) {
|
||||
__extends(SVGLabel, _super);
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function SVGLabel(renderer, str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) {
|
||||
var _this = _super.call(this) || this;
|
||||
_this.init(renderer, 'g');
|
||||
_this.textStr = str;
|
||||
_this.x = x;
|
||||
_this.y = y;
|
||||
_this.anchorX = anchorX;
|
||||
_this.anchorY = anchorY;
|
||||
_this.baseline = baseline;
|
||||
_this.className = className;
|
||||
if (className !== 'button') {
|
||||
_this.addClass('highcharts-label');
|
||||
}
|
||||
if (className) {
|
||||
_this.addClass('highcharts-' + className);
|
||||
}
|
||||
_this.text = renderer.text('', 0, 0, useHTML)
|
||||
.attr({
|
||||
zIndex: 1
|
||||
});
|
||||
// Validate the shape argument
|
||||
var hasBGImage;
|
||||
if (typeof shape === 'string') {
|
||||
hasBGImage = /^url\((.*?)\)$/.test(shape);
|
||||
if (_this.renderer.symbols[shape] || hasBGImage) {
|
||||
_this.symbolKey = shape;
|
||||
}
|
||||
}
|
||||
_this.bBox = SVGLabel.emptyBBox;
|
||||
_this.padding = 3;
|
||||
_this.paddingLeft = 0;
|
||||
_this.baselineOffset = 0;
|
||||
_this.needsBox = renderer.styledMode || hasBGImage;
|
||||
_this.deferredAttr = {};
|
||||
_this.alignFactor = 0;
|
||||
return _this;
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
SVGLabel.prototype.alignSetter = function (value) {
|
||||
var alignFactor = {
|
||||
left: 0,
|
||||
center: 0.5,
|
||||
right: 1
|
||||
}[value];
|
||||
if (alignFactor !== this.alignFactor) {
|
||||
this.alignFactor = alignFactor;
|
||||
// Bounding box exists, means we're dynamically changing
|
||||
if (this.bBox && isNumber(this.xSetting)) {
|
||||
this.attr({ x: this.xSetting }); // #5134
|
||||
}
|
||||
}
|
||||
};
|
||||
SVGLabel.prototype.anchorXSetter = function (value, key) {
|
||||
this.anchorX = value;
|
||||
this.boxAttr(key, Math.round(value) - this.getCrispAdjust() - this.xSetting);
|
||||
};
|
||||
SVGLabel.prototype.anchorYSetter = function (value, key) {
|
||||
this.anchorY = value;
|
||||
this.boxAttr(key, value - this.ySetting);
|
||||
};
|
||||
/*
|
||||
* Set a box attribute, or defer it if the box is not yet created
|
||||
*/
|
||||
SVGLabel.prototype.boxAttr = function (key, value) {
|
||||
if (this.box) {
|
||||
this.box.attr(key, value);
|
||||
}
|
||||
else {
|
||||
this.deferredAttr[key] = value;
|
||||
}
|
||||
};
|
||||
/*
|
||||
* Pick up some properties and apply them to the text instead of the
|
||||
* wrapper.
|
||||
*/
|
||||
SVGLabel.prototype.css = function (styles) {
|
||||
if (styles) {
|
||||
var textStyles = {}, isWidth, isFontStyle;
|
||||
// Create a copy to avoid altering the original object
|
||||
// (#537)
|
||||
styles = merge(styles);
|
||||
SVGLabel.textProps.forEach(function (prop) {
|
||||
if (typeof styles[prop] !== 'undefined') {
|
||||
textStyles[prop] = styles[prop];
|
||||
delete styles[prop];
|
||||
}
|
||||
});
|
||||
this.text.css(textStyles);
|
||||
isWidth = 'width' in textStyles;
|
||||
isFontStyle = 'fontSize' in textStyles ||
|
||||
'fontWeight' in textStyles;
|
||||
// Update existing text, box (#9400, #12163)
|
||||
if (isWidth || isFontStyle) {
|
||||
this.updateBoxSize();
|
||||
// Keep updated (#9400, #12163)
|
||||
if (isFontStyle) {
|
||||
this.updateTextPadding();
|
||||
}
|
||||
}
|
||||
}
|
||||
return SVGElement.prototype.css.call(this, styles);
|
||||
};
|
||||
/*
|
||||
* Destroy and release memory.
|
||||
*/
|
||||
SVGLabel.prototype.destroy = function () {
|
||||
// Added by button implementation
|
||||
removeEvent(this.element, 'mouseenter');
|
||||
removeEvent(this.element, 'mouseleave');
|
||||
if (this.text) {
|
||||
this.text.destroy();
|
||||
}
|
||||
if (this.box) {
|
||||
this.box = this.box.destroy();
|
||||
}
|
||||
// Call base implementation to destroy the rest
|
||||
SVGElement.prototype.destroy.call(this);
|
||||
return void 0;
|
||||
};
|
||||
SVGLabel.prototype.fillSetter = function (value, key) {
|
||||
if (value) {
|
||||
this.needsBox = true;
|
||||
}
|
||||
// for animation getter (#6776)
|
||||
this.fill = value;
|
||||
this.boxAttr(key, value);
|
||||
};
|
||||
/*
|
||||
* Return the bounding box of the box, not the group.
|
||||
*/
|
||||
SVGLabel.prototype.getBBox = function () {
|
||||
var bBox = this.bBox;
|
||||
var padding = this.padding;
|
||||
return {
|
||||
width: bBox.width + 2 * padding,
|
||||
height: bBox.height + 2 * padding,
|
||||
x: bBox.x - padding,
|
||||
y: bBox.y - padding
|
||||
};
|
||||
};
|
||||
SVGLabel.prototype.getCrispAdjust = function () {
|
||||
return this.renderer.styledMode && this.box ?
|
||||
this.box.strokeWidth() % 2 / 2 :
|
||||
(this['stroke-width'] ? parseInt(this['stroke-width'], 10) : 0) % 2 / 2;
|
||||
};
|
||||
SVGLabel.prototype.heightSetter = function (value) {
|
||||
this.heightSetting = value;
|
||||
};
|
||||
// Event handling. In case of useHTML, we need to make sure that events
|
||||
// are captured on the span as well, and that mouseenter/mouseleave
|
||||
// between the SVG group and the HTML span are not treated as real
|
||||
// enter/leave events. #13310.
|
||||
SVGLabel.prototype.on = function (eventType, handler) {
|
||||
var label = this;
|
||||
var text = label.text;
|
||||
var span = text && text.element.tagName === 'SPAN' ? text : void 0;
|
||||
var selectiveHandler;
|
||||
if (span) {
|
||||
selectiveHandler = function (e) {
|
||||
if ((eventType === 'mouseenter' ||
|
||||
eventType === 'mouseleave') &&
|
||||
e.relatedTarget instanceof Element &&
|
||||
(label.element.contains(e.relatedTarget) ||
|
||||
span.element.contains(e.relatedTarget))) {
|
||||
return;
|
||||
}
|
||||
handler.call(label.element, e);
|
||||
};
|
||||
span.on(eventType, selectiveHandler);
|
||||
}
|
||||
SVGElement.prototype.on.call(label, eventType, selectiveHandler || handler);
|
||||
return label;
|
||||
};
|
||||
/*
|
||||
* After the text element is added, get the desired size of the border
|
||||
* box and add it before the text in the DOM.
|
||||
*/
|
||||
SVGLabel.prototype.onAdd = function () {
|
||||
var str = this.textStr;
|
||||
this.text.add(this);
|
||||
this.attr({
|
||||
// Alignment is available now (#3295, 0 not rendered if given
|
||||
// as a value)
|
||||
text: (defined(str) ? str : ''),
|
||||
x: this.x,
|
||||
y: this.y
|
||||
});
|
||||
if (this.box && defined(this.anchorX)) {
|
||||
this.attr({
|
||||
anchorX: this.anchorX,
|
||||
anchorY: this.anchorY
|
||||
});
|
||||
}
|
||||
};
|
||||
SVGLabel.prototype.paddingSetter = function (value) {
|
||||
if (defined(value) && value !== this.padding) {
|
||||
this.padding = value;
|
||||
this.updateTextPadding();
|
||||
}
|
||||
};
|
||||
SVGLabel.prototype.paddingLeftSetter = function (value) {
|
||||
if (defined(value) && value !== this.paddingLeft) {
|
||||
this.paddingLeft = value;
|
||||
this.updateTextPadding();
|
||||
}
|
||||
};
|
||||
SVGLabel.prototype.rSetter = function (value, key) {
|
||||
this.boxAttr(key, value);
|
||||
};
|
||||
SVGLabel.prototype.shadow = function (b) {
|
||||
if (b && !this.renderer.styledMode) {
|
||||
this.updateBoxSize();
|
||||
if (this.box) {
|
||||
this.box.shadow(b);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
SVGLabel.prototype.strokeSetter = function (value, key) {
|
||||
// for animation getter (#6776)
|
||||
this.stroke = value;
|
||||
this.boxAttr(key, value);
|
||||
};
|
||||
SVGLabel.prototype['stroke-widthSetter'] = function (value, key) {
|
||||
if (value) {
|
||||
this.needsBox = true;
|
||||
}
|
||||
this['stroke-width'] = value;
|
||||
this.boxAttr(key, value);
|
||||
};
|
||||
SVGLabel.prototype['text-alignSetter'] = function (value) {
|
||||
this.textAlign = value;
|
||||
};
|
||||
SVGLabel.prototype.textSetter = function (text) {
|
||||
if (typeof text !== 'undefined') {
|
||||
// Must use .attr to ensure transforms are done (#10009)
|
||||
this.text.attr({ text: text });
|
||||
}
|
||||
this.updateBoxSize();
|
||||
this.updateTextPadding();
|
||||
};
|
||||
/*
|
||||
* This function runs after the label is added to the DOM (when the bounding
|
||||
* box is available), and after the text of the label is updated to detect
|
||||
* the new bounding box and reflect it in the border box.
|
||||
*/
|
||||
SVGLabel.prototype.updateBoxSize = function () {
|
||||
var style = this.text.element.style, crispAdjust, attribs = {};
|
||||
var padding = this.padding;
|
||||
var paddingLeft = this.paddingLeft;
|
||||
// #12165 error when width is null (auto)
|
||||
// #12163 when fontweight: bold, recalculate bBox withot cache
|
||||
// #3295 && 3514 box failure when string equals 0
|
||||
var bBox = ((!isNumber(this.widthSetting) || !isNumber(this.heightSetting) || this.textAlign) &&
|
||||
defined(this.text.textStr)) ?
|
||||
this.text.getBBox() : SVGLabel.emptyBBox;
|
||||
this.width = ((this.widthSetting || bBox.width || 0) +
|
||||
2 * padding +
|
||||
paddingLeft);
|
||||
this.height = (this.heightSetting || bBox.height || 0) + 2 * padding;
|
||||
// Update the label-scoped y offset. Math.min because of inline
|
||||
// style (#9400)
|
||||
this.baselineOffset = padding + Math.min(this.renderer.fontMetrics(style && style.fontSize, this.text).b,
|
||||
// When the height is 0, there is no bBox, so go with the font
|
||||
// metrics. Highmaps CSS demos.
|
||||
bBox.height || Infinity);
|
||||
if (this.needsBox) {
|
||||
// Create the border box if it is not already present
|
||||
if (!this.box) {
|
||||
// Symbol definition exists (#5324)
|
||||
var box = this.box = this.symbolKey ?
|
||||
this.renderer.symbol(this.symbolKey) :
|
||||
this.renderer.rect();
|
||||
box.addClass(// Don't use label className for buttons
|
||||
(this.className === 'button' ? '' : 'highcharts-label-box') +
|
||||
(this.className ? ' highcharts-' + this.className + '-box' : ''));
|
||||
box.add(this);
|
||||
crispAdjust = this.getCrispAdjust();
|
||||
attribs.x = crispAdjust;
|
||||
attribs.y = (this.baseline ? -this.baselineOffset : 0) + crispAdjust;
|
||||
}
|
||||
// Apply the box attributes
|
||||
attribs.width = Math.round(this.width);
|
||||
attribs.height = Math.round(this.height);
|
||||
this.box.attr(extend(attribs, this.deferredAttr));
|
||||
this.deferredAttr = {};
|
||||
}
|
||||
this.bBox = bBox;
|
||||
};
|
||||
/*
|
||||
* This function runs after setting text or padding, but only if padding
|
||||
* is changed.
|
||||
*/
|
||||
SVGLabel.prototype.updateTextPadding = function () {
|
||||
var text = this.text;
|
||||
// Determine y based on the baseline
|
||||
var textY = this.baseline ? 0 : this.baselineOffset;
|
||||
var textX = this.paddingLeft + this.padding;
|
||||
// compensate for alignment
|
||||
if (defined(this.widthSetting) &&
|
||||
this.bBox &&
|
||||
(this.textAlign === 'center' || this.textAlign === 'right')) {
|
||||
textX += { center: 0.5, right: 1 }[this.textAlign] *
|
||||
(this.widthSetting - this.bBox.width);
|
||||
}
|
||||
// update if anything changed
|
||||
if (textX !== text.x || textY !== text.y) {
|
||||
text.attr('x', textX);
|
||||
// #8159 - prevent misplaced data labels in treemap
|
||||
// (useHTML: true)
|
||||
if (text.hasBoxWidthChanged) {
|
||||
this.bBox = text.getBBox(true);
|
||||
this.updateBoxSize();
|
||||
}
|
||||
if (typeof textY !== 'undefined') {
|
||||
text.attr('y', textY);
|
||||
}
|
||||
}
|
||||
// record current values
|
||||
text.x = textX;
|
||||
text.y = textY;
|
||||
};
|
||||
SVGLabel.prototype.widthSetter = function (value) {
|
||||
// width:auto => null
|
||||
this.widthSetting = isNumber(value) ? value : void 0;
|
||||
};
|
||||
SVGLabel.prototype.xSetter = function (value) {
|
||||
this.x = value; // for animation getter
|
||||
if (this.alignFactor) {
|
||||
value -= this.alignFactor * ((this.widthSetting || this.bBox.width) +
|
||||
2 * this.padding);
|
||||
// Force animation even when setting to the same value (#7898)
|
||||
this['forceAnimate:x'] = true;
|
||||
}
|
||||
this.xSetting = Math.round(value);
|
||||
this.attr('translateX', this.xSetting);
|
||||
};
|
||||
SVGLabel.prototype.ySetter = function (value) {
|
||||
this.ySetting = this.y = Math.round(value);
|
||||
this.attr('translateY', this.ySetting);
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Static Properties
|
||||
*
|
||||
* */
|
||||
SVGLabel.emptyBBox = { width: 0, height: 0, x: 0, y: 0 };
|
||||
/* *
|
||||
*
|
||||
* Properties
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* For labels, these CSS properties are applied to the `text` node directly.
|
||||
*
|
||||
* @private
|
||||
* @name Highcharts.SVGLabel#textProps
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
SVGLabel.textProps = [
|
||||
'color', 'cursor', 'direction', 'fontFamily', 'fontSize', 'fontStyle',
|
||||
'fontWeight', 'lineHeight', 'textAlign', 'textDecoration',
|
||||
'textOutline', 'textOverflow', 'width'
|
||||
];
|
||||
return SVGLabel;
|
||||
}(SVGElement));
|
||||
export default SVGLabel;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,882 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* Extensions to the SVGRenderer class to enable 3D shapes
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Color from '../../Color.js';
|
||||
var color = Color.parse;
|
||||
import H from '../../Globals.js';
|
||||
import Math3D from '../../../Extensions/Math3D.js';
|
||||
var perspective = Math3D.perspective, shapeArea = Math3D.shapeArea;
|
||||
import SVGElement from './SVGElement.js';
|
||||
import SVGRenderer from './SVGRenderer.js';
|
||||
import U from '../../Utilities.js';
|
||||
var animObject = U.animObject, defined = U.defined, extend = U.extend, merge = U.merge, objectEach = U.objectEach, pick = U.pick;
|
||||
var cos = Math.cos, PI = Math.PI, sin = Math.sin;
|
||||
var charts = H.charts, deg2rad = H.deg2rad,
|
||||
// internal:
|
||||
dFactor, element3dMethods, cuboidMethods;
|
||||
/*
|
||||
EXTENSION TO THE SVG-RENDERER TO ENABLE 3D SHAPES
|
||||
*/
|
||||
// HELPER METHODS
|
||||
dFactor = (4 * (Math.sqrt(2) - 1) / 3) / (PI / 2);
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* Method to construct a curved path. Can 'wrap' around more then 180 degrees.
|
||||
* @private
|
||||
*/
|
||||
function curveTo(cx, cy, rx, ry, start, end, dx, dy) {
|
||||
var result = [], arcAngle = end - start;
|
||||
if ((end > start) && (end - start > Math.PI / 2 + 0.0001)) {
|
||||
result = result.concat(curveTo(cx, cy, rx, ry, start, start + (Math.PI / 2), dx, dy));
|
||||
result = result.concat(curveTo(cx, cy, rx, ry, start + (Math.PI / 2), end, dx, dy));
|
||||
return result;
|
||||
}
|
||||
if ((end < start) && (start - end > Math.PI / 2 + 0.0001)) {
|
||||
result = result.concat(curveTo(cx, cy, rx, ry, start, start - (Math.PI / 2), dx, dy));
|
||||
result = result.concat(curveTo(cx, cy, rx, ry, start - (Math.PI / 2), end, dx, dy));
|
||||
return result;
|
||||
}
|
||||
return [[
|
||||
'C',
|
||||
cx + (rx * Math.cos(start)) -
|
||||
((rx * dFactor * arcAngle) * Math.sin(start)) + dx,
|
||||
cy + (ry * Math.sin(start)) +
|
||||
((ry * dFactor * arcAngle) * Math.cos(start)) + dy,
|
||||
cx + (rx * Math.cos(end)) +
|
||||
((rx * dFactor * arcAngle) * Math.sin(end)) + dx,
|
||||
cy + (ry * Math.sin(end)) -
|
||||
((ry * dFactor * arcAngle) * Math.cos(end)) + dy,
|
||||
cx + (rx * Math.cos(end)) + dx,
|
||||
cy + (ry * Math.sin(end)) + dy
|
||||
]];
|
||||
}
|
||||
SVGRenderer.prototype.toLinePath = function (points, closed) {
|
||||
var result = [];
|
||||
// Put "L x y" for each point
|
||||
points.forEach(function (point) {
|
||||
result.push(['L', point.x, point.y]);
|
||||
});
|
||||
if (points.length) {
|
||||
// Set the first element to M
|
||||
result[0][0] = 'M';
|
||||
// If it is a closed line, add Z
|
||||
if (closed) {
|
||||
result.push(['Z']);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
SVGRenderer.prototype.toLineSegments = function (points) {
|
||||
var result = [], m = true;
|
||||
points.forEach(function (point) {
|
||||
result.push(m ? ['M', point.x, point.y] : ['L', point.x, point.y]);
|
||||
m = !m;
|
||||
});
|
||||
return result;
|
||||
};
|
||||
// A 3-D Face is defined by it's 3D vertexes, and is only visible if it's
|
||||
// vertexes are counter-clockwise (Back-face culling). It is used as a
|
||||
// polyhedron Element
|
||||
SVGRenderer.prototype.face3d = function (args) {
|
||||
var renderer = this, ret = this.createElement('path');
|
||||
ret.vertexes = [];
|
||||
ret.insidePlotArea = false;
|
||||
ret.enabled = true;
|
||||
ret.attr = function (hash) {
|
||||
if (typeof hash === 'object' &&
|
||||
(defined(hash.enabled) ||
|
||||
defined(hash.vertexes) ||
|
||||
defined(hash.insidePlotArea))) {
|
||||
this.enabled = pick(hash.enabled, this.enabled);
|
||||
this.vertexes = pick(hash.vertexes, this.vertexes);
|
||||
this.insidePlotArea = pick(hash.insidePlotArea, this.insidePlotArea);
|
||||
delete hash.enabled;
|
||||
delete hash.vertexes;
|
||||
delete hash.insidePlotArea;
|
||||
var chart = charts[renderer.chartIndex], vertexes2d = perspective(this.vertexes, chart, this.insidePlotArea), path = renderer.toLinePath(vertexes2d, true), area = shapeArea(vertexes2d), visibility = (this.enabled && area > 0) ? 'visible' : 'hidden';
|
||||
hash.d = path;
|
||||
hash.visibility = visibility;
|
||||
}
|
||||
return SVGElement.prototype.attr.apply(this, arguments);
|
||||
};
|
||||
ret.animate = function (params) {
|
||||
if (typeof params === 'object' &&
|
||||
(defined(params.enabled) ||
|
||||
defined(params.vertexes) ||
|
||||
defined(params.insidePlotArea))) {
|
||||
this.enabled = pick(params.enabled, this.enabled);
|
||||
this.vertexes = pick(params.vertexes, this.vertexes);
|
||||
this.insidePlotArea = pick(params.insidePlotArea, this.insidePlotArea);
|
||||
delete params.enabled;
|
||||
delete params.vertexes;
|
||||
delete params.insidePlotArea;
|
||||
var chart = charts[renderer.chartIndex], vertexes2d = perspective(this.vertexes, chart, this.insidePlotArea), path = renderer.toLinePath(vertexes2d, true), area = shapeArea(vertexes2d), visibility = (this.enabled && area > 0) ? 'visible' : 'hidden';
|
||||
params.d = path;
|
||||
this.attr('visibility', visibility);
|
||||
}
|
||||
return SVGElement.prototype.animate.apply(this, arguments);
|
||||
};
|
||||
return ret.attr(args);
|
||||
};
|
||||
// A Polyhedron is a handy way of defining a group of 3-D faces. It's only
|
||||
// attribute is `faces`, an array of attributes of each one of it's Face3D
|
||||
// instances.
|
||||
SVGRenderer.prototype.polyhedron = function (args) {
|
||||
var renderer = this, result = this.g(), destroy = result.destroy;
|
||||
if (!this.styledMode) {
|
||||
result.attr({
|
||||
'stroke-linejoin': 'round'
|
||||
});
|
||||
}
|
||||
result.faces = [];
|
||||
// destroy all children
|
||||
result.destroy = function () {
|
||||
for (var i = 0; i < result.faces.length; i++) {
|
||||
result.faces[i].destroy();
|
||||
}
|
||||
return destroy.call(this);
|
||||
};
|
||||
result.attr = function (hash, val, complete, continueAnimation) {
|
||||
if (typeof hash === 'object' && defined(hash.faces)) {
|
||||
while (result.faces.length > hash.faces.length) {
|
||||
result.faces.pop().destroy();
|
||||
}
|
||||
while (result.faces.length < hash.faces.length) {
|
||||
result.faces.push(renderer.face3d().add(result));
|
||||
}
|
||||
for (var i = 0; i < hash.faces.length; i++) {
|
||||
if (renderer.styledMode) {
|
||||
delete hash.faces[i].fill;
|
||||
}
|
||||
result.faces[i].attr(hash.faces[i], null, complete, continueAnimation);
|
||||
}
|
||||
delete hash.faces;
|
||||
}
|
||||
return SVGElement.prototype.attr.apply(this, arguments);
|
||||
};
|
||||
result.animate = function (params, duration, complete) {
|
||||
if (params && params.faces) {
|
||||
while (result.faces.length > params.faces.length) {
|
||||
result.faces.pop().destroy();
|
||||
}
|
||||
while (result.faces.length < params.faces.length) {
|
||||
result.faces.push(renderer.face3d().add(result));
|
||||
}
|
||||
for (var i = 0; i < params.faces.length; i++) {
|
||||
result.faces[i].animate(params.faces[i], duration, complete);
|
||||
}
|
||||
delete params.faces;
|
||||
}
|
||||
return SVGElement.prototype.animate.apply(this, arguments);
|
||||
};
|
||||
return result.attr(args);
|
||||
};
|
||||
// Base, abstract prototype member for 3D elements
|
||||
element3dMethods = {
|
||||
/**
|
||||
* The init is used by base - renderer.Element
|
||||
* @private
|
||||
*/
|
||||
initArgs: function (args) {
|
||||
var elem3d = this, renderer = elem3d.renderer, paths = renderer[elem3d.pathType + 'Path'](args), zIndexes = paths.zIndexes;
|
||||
// build parts
|
||||
elem3d.parts.forEach(function (part) {
|
||||
elem3d[part] = renderer.path(paths[part]).attr({
|
||||
'class': 'highcharts-3d-' + part,
|
||||
zIndex: zIndexes[part] || 0
|
||||
}).add(elem3d);
|
||||
});
|
||||
elem3d.attr({
|
||||
'stroke-linejoin': 'round',
|
||||
zIndex: zIndexes.group
|
||||
});
|
||||
// store original destroy
|
||||
elem3d.originalDestroy = elem3d.destroy;
|
||||
elem3d.destroy = elem3d.destroyParts;
|
||||
// Store information if any side of element was rendered by force.
|
||||
elem3d.forcedSides = paths.forcedSides;
|
||||
},
|
||||
/**
|
||||
* Single property setter that applies options to each part
|
||||
* @private
|
||||
*/
|
||||
singleSetterForParts: function (prop, val, values, verb, duration, complete) {
|
||||
var elem3d = this, newAttr = {}, optionsToApply = [null, null, (verb || 'attr'), duration, complete], hasZIndexes = values && values.zIndexes;
|
||||
if (!values) {
|
||||
newAttr[prop] = val;
|
||||
optionsToApply[0] = newAttr;
|
||||
}
|
||||
else {
|
||||
// It is needed to deal with the whole group zIndexing
|
||||
// in case of graph rotation
|
||||
if (hasZIndexes && hasZIndexes.group) {
|
||||
this.attr({
|
||||
zIndex: hasZIndexes.group
|
||||
});
|
||||
}
|
||||
objectEach(values, function (partVal, part) {
|
||||
newAttr[part] = {};
|
||||
newAttr[part][prop] = partVal;
|
||||
// include zIndexes if provided
|
||||
if (hasZIndexes) {
|
||||
newAttr[part].zIndex = values.zIndexes[part] || 0;
|
||||
}
|
||||
});
|
||||
optionsToApply[1] = newAttr;
|
||||
}
|
||||
return elem3d.processParts.apply(elem3d, optionsToApply);
|
||||
},
|
||||
/**
|
||||
* Calls function for each part. Used for attr, animate and destroy.
|
||||
* @private
|
||||
*/
|
||||
processParts: function (props, partsProps, verb, duration, complete) {
|
||||
var elem3d = this;
|
||||
elem3d.parts.forEach(function (part) {
|
||||
// if different props for different parts
|
||||
if (partsProps) {
|
||||
props = pick(partsProps[part], false);
|
||||
}
|
||||
// only if something to set, but allow undefined
|
||||
if (props !== false) {
|
||||
elem3d[part][verb](props, duration, complete);
|
||||
}
|
||||
});
|
||||
return elem3d;
|
||||
},
|
||||
/**
|
||||
* Destroy all parts
|
||||
* @private
|
||||
*/
|
||||
destroyParts: function () {
|
||||
this.processParts(null, null, 'destroy');
|
||||
return this.originalDestroy();
|
||||
}
|
||||
};
|
||||
// CUBOID
|
||||
cuboidMethods = merge(element3dMethods, {
|
||||
parts: ['front', 'top', 'side'],
|
||||
pathType: 'cuboid',
|
||||
attr: function (args, val, complete, continueAnimation) {
|
||||
// Resolve setting attributes by string name
|
||||
if (typeof args === 'string' && typeof val !== 'undefined') {
|
||||
var key = args;
|
||||
args = {};
|
||||
args[key] = val;
|
||||
}
|
||||
if (args.shapeArgs || defined(args.x)) {
|
||||
return this.singleSetterForParts('d', null, this.renderer[this.pathType + 'Path'](args.shapeArgs || args));
|
||||
}
|
||||
return SVGElement.prototype.attr.call(this, args, void 0, complete, continueAnimation);
|
||||
},
|
||||
animate: function (args, duration, complete) {
|
||||
if (defined(args.x) && defined(args.y)) {
|
||||
var paths = this.renderer[this.pathType + 'Path'](args), forcedSides = paths.forcedSides;
|
||||
this.singleSetterForParts('d', null, paths, 'animate', duration, complete);
|
||||
this.attr({
|
||||
zIndex: paths.zIndexes.group
|
||||
});
|
||||
// If sides that are forced to render changed, recalculate colors.
|
||||
if (forcedSides !== this.forcedSides) {
|
||||
this.forcedSides = forcedSides;
|
||||
cuboidMethods.fillSetter.call(this, this.fill);
|
||||
}
|
||||
}
|
||||
else {
|
||||
SVGElement.prototype.animate.call(this, args, duration, complete);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
fillSetter: function (fill) {
|
||||
var elem3d = this;
|
||||
elem3d.forcedSides = elem3d.forcedSides || [];
|
||||
elem3d.singleSetterForParts('fill', null, {
|
||||
front: fill,
|
||||
// Do not change color if side was forced to render.
|
||||
top: color(fill).brighten(elem3d.forcedSides.indexOf('top') >= 0 ? 0 : 0.1).get(),
|
||||
side: color(fill).brighten(elem3d.forcedSides.indexOf('side') >= 0 ? 0 : -0.1).get()
|
||||
});
|
||||
// fill for animation getter (#6776)
|
||||
elem3d.color = elem3d.fill = fill;
|
||||
return elem3d;
|
||||
}
|
||||
});
|
||||
// set them up
|
||||
SVGRenderer.prototype.elements3d = {
|
||||
base: element3dMethods,
|
||||
cuboid: cuboidMethods
|
||||
};
|
||||
/**
|
||||
* return result, generalization
|
||||
* @private
|
||||
* @requires highcharts-3d
|
||||
*/
|
||||
SVGRenderer.prototype.element3d = function (type, shapeArgs) {
|
||||
// base
|
||||
var ret = this.g();
|
||||
// extend
|
||||
extend(ret, this.elements3d[type]);
|
||||
// init
|
||||
ret.initArgs(shapeArgs);
|
||||
// return
|
||||
return ret;
|
||||
};
|
||||
// generelized, so now use simply
|
||||
SVGRenderer.prototype.cuboid = function (shapeArgs) {
|
||||
return this.element3d('cuboid', shapeArgs);
|
||||
};
|
||||
// Generates a cuboid path and zIndexes
|
||||
SVGRenderer.prototype.cuboidPath = function (shapeArgs) {
|
||||
var x = shapeArgs.x, y = shapeArgs.y, z = shapeArgs.z || 0,
|
||||
// For side calculation (right/left)
|
||||
// there is a need for height (and other shapeArgs arguments)
|
||||
// to be at least 1px
|
||||
h = shapeArgs.height, w = shapeArgs.width, d = shapeArgs.depth, chart = charts[this.chartIndex], front, back, top, bottom, left, right, shape, path1, path2, path3, isFront, isTop, isRight, options3d = chart.options.chart.options3d, alpha = options3d.alpha,
|
||||
// Priority for x axis is the biggest,
|
||||
// because of x direction has biggest influence on zIndex
|
||||
incrementX = 1000000,
|
||||
// y axis has the smallest priority in case of our charts
|
||||
// (needs to be set because of stacking)
|
||||
incrementY = 10, incrementZ = 100, zIndex = 0,
|
||||
// The 8 corners of the cube
|
||||
pArr = [{
|
||||
x: x,
|
||||
y: y,
|
||||
z: z
|
||||
}, {
|
||||
x: x + w,
|
||||
y: y,
|
||||
z: z
|
||||
}, {
|
||||
x: x + w,
|
||||
y: y + h,
|
||||
z: z
|
||||
}, {
|
||||
x: x,
|
||||
y: y + h,
|
||||
z: z
|
||||
}, {
|
||||
x: x,
|
||||
y: y + h,
|
||||
z: z + d
|
||||
}, {
|
||||
x: x + w,
|
||||
y: y + h,
|
||||
z: z + d
|
||||
}, {
|
||||
x: x + w,
|
||||
y: y,
|
||||
z: z + d
|
||||
}, {
|
||||
x: x,
|
||||
y: y,
|
||||
z: z + d
|
||||
}], forcedSides = [], pickShape;
|
||||
// apply perspective
|
||||
pArr = perspective(pArr, chart, shapeArgs.insidePlotArea);
|
||||
/**
|
||||
* helper method to decide which side is visible
|
||||
* @private
|
||||
*/
|
||||
function mapSidePath(i) {
|
||||
// Added support for 0 value in columns, where height is 0
|
||||
// but the shape is rendered.
|
||||
// Height is used from 1st to 6th element of pArr
|
||||
if (h === 0 && i > 1 && i < 6) { // [2, 3, 4, 5]
|
||||
return {
|
||||
x: pArr[i].x,
|
||||
// when height is 0 instead of cuboid we render plane
|
||||
// so it is needed to add fake 10 height to imitate cuboid
|
||||
// for side calculation
|
||||
y: pArr[i].y + 10,
|
||||
z: pArr[i].z
|
||||
};
|
||||
}
|
||||
// It is needed to calculate dummy sides (front/back) for breaking
|
||||
// points in case of x and depth values. If column has side,
|
||||
// it means that x values of front and back side are different.
|
||||
if (pArr[0].x === pArr[7].x && i >= 4) { // [4, 5, 6, 7]
|
||||
return {
|
||||
x: pArr[i].x + 10,
|
||||
// when height is 0 instead of cuboid we render plane
|
||||
// so it is needed to add fake 10 height to imitate cuboid
|
||||
// for side calculation
|
||||
y: pArr[i].y,
|
||||
z: pArr[i].z
|
||||
};
|
||||
}
|
||||
// Added dummy depth
|
||||
if (d === 0 && i < 2 || i > 5) { // [0, 1, 6, 7]
|
||||
return {
|
||||
x: pArr[i].x,
|
||||
// when height is 0 instead of cuboid we render plane
|
||||
// so it is needed to add fake 10 height to imitate cuboid
|
||||
// for side calculation
|
||||
y: pArr[i].y,
|
||||
z: pArr[i].z + 10
|
||||
};
|
||||
}
|
||||
return pArr[i];
|
||||
}
|
||||
/**
|
||||
* method creating the final side
|
||||
* @private
|
||||
*/
|
||||
function mapPath(i) {
|
||||
return pArr[i];
|
||||
}
|
||||
/**
|
||||
* First value - path with specific face
|
||||
* Second value - added information about side for later calculations.
|
||||
* Possible second values are 0 for path1, 1 for path2 and -1 for no path
|
||||
* chosen.
|
||||
* Third value - string containing information about current side
|
||||
* of cuboid for forcing side rendering.
|
||||
* @private
|
||||
*/
|
||||
pickShape = function (verticesIndex1, verticesIndex2, side) {
|
||||
var ret = [[], -1],
|
||||
// An array of vertices for cuboid face
|
||||
face1 = verticesIndex1.map(mapPath), face2 = verticesIndex2.map(mapPath),
|
||||
// dummy face is calculated the same way as standard face,
|
||||
// but if cuboid height is 0 additional height is added so it is
|
||||
// possible to use this vertices array for visible face calculation
|
||||
dummyFace1 = verticesIndex1.map(mapSidePath), dummyFace2 = verticesIndex2.map(mapSidePath);
|
||||
if (shapeArea(face1) < 0) {
|
||||
ret = [face1, 0];
|
||||
}
|
||||
else if (shapeArea(face2) < 0) {
|
||||
ret = [face2, 1];
|
||||
}
|
||||
else if (side) {
|
||||
forcedSides.push(side);
|
||||
if (shapeArea(dummyFace1) < 0) {
|
||||
ret = [face1, 0];
|
||||
}
|
||||
else if (shapeArea(dummyFace2) < 0) {
|
||||
ret = [face2, 1];
|
||||
}
|
||||
else {
|
||||
ret = [face1, 0]; // force side calculation.
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
// front or back
|
||||
front = [3, 2, 1, 0];
|
||||
back = [7, 6, 5, 4];
|
||||
shape = pickShape(front, back, 'front');
|
||||
path1 = shape[0];
|
||||
isFront = shape[1];
|
||||
// top or bottom
|
||||
top = [1, 6, 7, 0];
|
||||
bottom = [4, 5, 2, 3];
|
||||
shape = pickShape(top, bottom, 'top');
|
||||
path2 = shape[0];
|
||||
isTop = shape[1];
|
||||
// side
|
||||
right = [1, 2, 5, 6];
|
||||
left = [0, 7, 4, 3];
|
||||
shape = pickShape(right, left, 'side');
|
||||
path3 = shape[0];
|
||||
isRight = shape[1];
|
||||
/* New block used for calculating zIndex. It is basing on X, Y and Z
|
||||
position of specific columns. All zIndexes (for X, Y and Z values) are
|
||||
added to the final zIndex, where every value has different priority. The
|
||||
biggest priority is in X and Z directions, the lowest index is for
|
||||
stacked columns (Y direction and the same X and Z positions). Big
|
||||
differences between priorities is made because we need to ensure that
|
||||
even for big changes in Y and Z parameters all columns will be drawn
|
||||
correctly. */
|
||||
if (isRight === 1) {
|
||||
// It is needed to connect value with current chart width
|
||||
// for big chart size.
|
||||
zIndex += incrementX * (chart.plotWidth - x);
|
||||
}
|
||||
else if (!isRight) {
|
||||
zIndex += incrementX * x;
|
||||
}
|
||||
zIndex += incrementY * (!isTop ||
|
||||
// Numbers checked empirically
|
||||
(alpha >= 0 && alpha <= 180 || alpha < 360 && alpha > 357.5) ?
|
||||
chart.plotHeight - y : 10 + y);
|
||||
if (isFront === 1) {
|
||||
zIndex += incrementZ * (z);
|
||||
}
|
||||
else if (!isFront) {
|
||||
zIndex += incrementZ * (1000 - z);
|
||||
}
|
||||
return {
|
||||
front: this.toLinePath(path1, true),
|
||||
top: this.toLinePath(path2, true),
|
||||
side: this.toLinePath(path3, true),
|
||||
zIndexes: {
|
||||
group: Math.round(zIndex)
|
||||
},
|
||||
forcedSides: forcedSides,
|
||||
// additional info about zIndexes
|
||||
isFront: isFront,
|
||||
isTop: isTop
|
||||
}; // #4774
|
||||
};
|
||||
// SECTORS //
|
||||
SVGRenderer.prototype.arc3d = function (attribs) {
|
||||
var wrapper = this.g(), renderer = wrapper.renderer, customAttribs = ['x', 'y', 'r', 'innerR', 'start', 'end', 'depth'];
|
||||
/**
|
||||
* Get custom attributes. Don't mutate the original object and return an
|
||||
* object with only custom attr.
|
||||
* @private
|
||||
*/
|
||||
function suckOutCustom(params) {
|
||||
var hasCA = false, ca = {}, key;
|
||||
params = merge(params); // Don't mutate the original object
|
||||
for (key in params) {
|
||||
if (customAttribs.indexOf(key) !== -1) {
|
||||
ca[key] = params[key];
|
||||
delete params[key];
|
||||
hasCA = true;
|
||||
}
|
||||
}
|
||||
return hasCA ? [ca, params] : false;
|
||||
}
|
||||
attribs = merge(attribs);
|
||||
attribs.alpha = (attribs.alpha || 0) * deg2rad;
|
||||
attribs.beta = (attribs.beta || 0) * deg2rad;
|
||||
// Create the different sub sections of the shape
|
||||
wrapper.top = renderer.path();
|
||||
wrapper.side1 = renderer.path();
|
||||
wrapper.side2 = renderer.path();
|
||||
wrapper.inn = renderer.path();
|
||||
wrapper.out = renderer.path();
|
||||
// Add all faces
|
||||
wrapper.onAdd = function () {
|
||||
var parent = wrapper.parentGroup, className = wrapper.attr('class');
|
||||
wrapper.top.add(wrapper);
|
||||
// These faces are added outside the wrapper group because the z index
|
||||
// relates to neighbour elements as well
|
||||
['out', 'inn', 'side1', 'side2'].forEach(function (face) {
|
||||
wrapper[face]
|
||||
.attr({
|
||||
'class': className + ' highcharts-3d-side'
|
||||
})
|
||||
.add(parent);
|
||||
});
|
||||
};
|
||||
// Cascade to faces
|
||||
['addClass', 'removeClass'].forEach(function (fn) {
|
||||
wrapper[fn] = function () {
|
||||
var args = arguments;
|
||||
['top', 'out', 'inn', 'side1', 'side2'].forEach(function (face) {
|
||||
wrapper[face][fn].apply(wrapper[face], args);
|
||||
});
|
||||
};
|
||||
});
|
||||
/**
|
||||
* Compute the transformed paths and set them to the composite shapes
|
||||
* @private
|
||||
*/
|
||||
wrapper.setPaths = function (attribs) {
|
||||
var paths = wrapper.renderer.arc3dPath(attribs), zIndex = paths.zTop * 100;
|
||||
wrapper.attribs = attribs;
|
||||
wrapper.top.attr({ d: paths.top, zIndex: paths.zTop });
|
||||
wrapper.inn.attr({ d: paths.inn, zIndex: paths.zInn });
|
||||
wrapper.out.attr({ d: paths.out, zIndex: paths.zOut });
|
||||
wrapper.side1.attr({ d: paths.side1, zIndex: paths.zSide1 });
|
||||
wrapper.side2.attr({ d: paths.side2, zIndex: paths.zSide2 });
|
||||
// show all children
|
||||
wrapper.zIndex = zIndex;
|
||||
wrapper.attr({ zIndex: zIndex });
|
||||
// Set the radial gradient center the first time
|
||||
if (attribs.center) {
|
||||
wrapper.top.setRadialReference(attribs.center);
|
||||
delete attribs.center;
|
||||
}
|
||||
};
|
||||
wrapper.setPaths(attribs);
|
||||
/**
|
||||
* Apply the fill to the top and a darker shade to the sides
|
||||
* @private
|
||||
*/
|
||||
wrapper.fillSetter = function (value) {
|
||||
var darker = color(value).brighten(-0.1).get();
|
||||
this.fill = value;
|
||||
this.side1.attr({ fill: darker });
|
||||
this.side2.attr({ fill: darker });
|
||||
this.inn.attr({ fill: darker });
|
||||
this.out.attr({ fill: darker });
|
||||
this.top.attr({ fill: value });
|
||||
return this;
|
||||
};
|
||||
// Apply the same value to all. These properties cascade down to the
|
||||
// children when set to the composite arc3d.
|
||||
['opacity', 'translateX', 'translateY', 'visibility'].forEach(function (setter) {
|
||||
wrapper[setter + 'Setter'] = function (value, key) {
|
||||
wrapper[key] = value;
|
||||
['out', 'inn', 'side1', 'side2', 'top'].forEach(function (el) {
|
||||
wrapper[el].attr(key, value);
|
||||
});
|
||||
};
|
||||
});
|
||||
// Override attr to remove shape attributes and use those to set child paths
|
||||
wrapper.attr = function (params) {
|
||||
var ca, paramArr;
|
||||
if (typeof params === 'object') {
|
||||
paramArr = suckOutCustom(params);
|
||||
if (paramArr) {
|
||||
ca = paramArr[0];
|
||||
arguments[0] = paramArr[1];
|
||||
extend(wrapper.attribs, ca);
|
||||
wrapper.setPaths(wrapper.attribs);
|
||||
}
|
||||
}
|
||||
return SVGElement.prototype.attr.apply(wrapper, arguments);
|
||||
};
|
||||
// Override the animate function by sucking out custom parameters related to
|
||||
// the shapes directly, and update the shapes from the animation step.
|
||||
wrapper.animate = function (params, animation, complete) {
|
||||
var paramArr, from = this.attribs, to, anim, randomProp = 'data-' + Math.random().toString(26).substring(2, 9);
|
||||
// Attribute-line properties connected to 3D. These shouldn't have been
|
||||
// in the attribs collection in the first place.
|
||||
delete params.center;
|
||||
delete params.z;
|
||||
delete params.alpha;
|
||||
delete params.beta;
|
||||
anim = animObject(pick(animation, this.renderer.globalAnimation));
|
||||
if (anim.duration) {
|
||||
paramArr = suckOutCustom(params);
|
||||
// Params need to have a property in order for the step to run
|
||||
// (#5765, #7097, #7437)
|
||||
wrapper[randomProp] = 0;
|
||||
params[randomProp] = 1;
|
||||
wrapper[randomProp + 'Setter'] = H.noop;
|
||||
if (paramArr) {
|
||||
to = paramArr[0]; // custom attr
|
||||
anim.step = function (a, fx) {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function interpolate(key) {
|
||||
return from[key] + (pick(to[key], from[key]) -
|
||||
from[key]) * fx.pos;
|
||||
}
|
||||
if (fx.prop === randomProp) {
|
||||
fx.elem.setPaths(merge(from, {
|
||||
x: interpolate('x'),
|
||||
y: interpolate('y'),
|
||||
r: interpolate('r'),
|
||||
innerR: interpolate('innerR'),
|
||||
start: interpolate('start'),
|
||||
end: interpolate('end'),
|
||||
depth: interpolate('depth')
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
animation = anim; // Only when duration (#5572)
|
||||
}
|
||||
return SVGElement.prototype.animate.call(this, params, animation, complete);
|
||||
};
|
||||
// destroy all children
|
||||
wrapper.destroy = function () {
|
||||
this.top.destroy();
|
||||
this.out.destroy();
|
||||
this.inn.destroy();
|
||||
this.side1.destroy();
|
||||
this.side2.destroy();
|
||||
return SVGElement.prototype.destroy.call(this);
|
||||
};
|
||||
// hide all children
|
||||
wrapper.hide = function () {
|
||||
this.top.hide();
|
||||
this.out.hide();
|
||||
this.inn.hide();
|
||||
this.side1.hide();
|
||||
this.side2.hide();
|
||||
};
|
||||
wrapper.show = function (inherit) {
|
||||
this.top.show(inherit);
|
||||
this.out.show(inherit);
|
||||
this.inn.show(inherit);
|
||||
this.side1.show(inherit);
|
||||
this.side2.show(inherit);
|
||||
};
|
||||
return wrapper;
|
||||
};
|
||||
// Generate the paths required to draw a 3D arc
|
||||
SVGRenderer.prototype.arc3dPath = function (shapeArgs) {
|
||||
var cx = shapeArgs.x, // x coordinate of the center
|
||||
cy = shapeArgs.y, // y coordinate of the center
|
||||
start = shapeArgs.start, // start angle
|
||||
end = shapeArgs.end - 0.00001, // end angle
|
||||
r = shapeArgs.r, // radius
|
||||
ir = shapeArgs.innerR || 0, // inner radius
|
||||
d = shapeArgs.depth || 0, // depth
|
||||
alpha = shapeArgs.alpha, // alpha rotation of the chart
|
||||
beta = shapeArgs.beta; // beta rotation of the chart
|
||||
// Derived Variables
|
||||
var cs = Math.cos(start), // cosinus of the start angle
|
||||
ss = Math.sin(start), // sinus of the start angle
|
||||
ce = Math.cos(end), // cosinus of the end angle
|
||||
se = Math.sin(end), // sinus of the end angle
|
||||
rx = r * Math.cos(beta), // x-radius
|
||||
ry = r * Math.cos(alpha), // y-radius
|
||||
irx = ir * Math.cos(beta), // x-radius (inner)
|
||||
iry = ir * Math.cos(alpha), // y-radius (inner)
|
||||
dx = d * Math.sin(beta), // distance between top and bottom in x
|
||||
dy = d * Math.sin(alpha); // distance between top and bottom in y
|
||||
// TOP
|
||||
var top = [
|
||||
['M', cx + (rx * cs), cy + (ry * ss)]
|
||||
];
|
||||
top = top.concat(curveTo(cx, cy, rx, ry, start, end, 0, 0));
|
||||
top.push([
|
||||
'L', cx + (irx * ce), cy + (iry * se)
|
||||
]);
|
||||
top = top.concat(curveTo(cx, cy, irx, iry, end, start, 0, 0));
|
||||
top.push(['Z']);
|
||||
// OUTSIDE
|
||||
var b = (beta > 0 ? Math.PI / 2 : 0), a = (alpha > 0 ? 0 : Math.PI / 2);
|
||||
var start2 = start > -b ? start : (end > -b ? -b : start), end2 = end < PI - a ? end : (start < PI - a ? PI - a : end), midEnd = 2 * PI - a;
|
||||
// When slice goes over bottom middle, need to add both, left and right
|
||||
// outer side. Additionally, when we cross right hand edge, create sharp
|
||||
// edge. Outer shape/wall:
|
||||
//
|
||||
// -------
|
||||
// / ^ \
|
||||
// 4) / / \ \ 1)
|
||||
// / / \ \
|
||||
// / / \ \
|
||||
// (c)=> ==== ==== <=(d)
|
||||
// \ \ / /
|
||||
// \ \<=(a)/ /
|
||||
// \ \ / / <=(b)
|
||||
// 3) \ v / 2)
|
||||
// -------
|
||||
//
|
||||
// (a) - inner side
|
||||
// (b) - outer side
|
||||
// (c) - left edge (sharp)
|
||||
// (d) - right edge (sharp)
|
||||
// 1..n - rendering order for startAngle = 0, when set to e.g 90, order
|
||||
// changes clockwise (1->2, 2->3, n->1) and counterclockwise for negative
|
||||
// startAngle
|
||||
var out = [
|
||||
['M', cx + (rx * cos(start2)), cy + (ry * sin(start2))]
|
||||
];
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, start2, end2, 0, 0));
|
||||
// When shape is wide, it can cross both, (c) and (d) edges, when using
|
||||
// startAngle
|
||||
if (end > midEnd && start < midEnd) {
|
||||
// Go to outer side
|
||||
out.push([
|
||||
'L', cx + (rx * cos(end2)) + dx, cy + (ry * sin(end2)) + dy
|
||||
]);
|
||||
// Curve to the right edge of the slice (d)
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, end2, midEnd, dx, dy));
|
||||
// Go to the inner side
|
||||
out.push([
|
||||
'L', cx + (rx * cos(midEnd)), cy + (ry * sin(midEnd))
|
||||
]);
|
||||
// Curve to the true end of the slice
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, midEnd, end, 0, 0));
|
||||
// Go to the outer side
|
||||
out.push([
|
||||
'L', cx + (rx * cos(end)) + dx, cy + (ry * sin(end)) + dy
|
||||
]);
|
||||
// Go back to middle (d)
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, end, midEnd, dx, dy));
|
||||
out.push([
|
||||
'L', cx + (rx * cos(midEnd)), cy + (ry * sin(midEnd))
|
||||
]);
|
||||
// Go back to the left edge
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, midEnd, end2, 0, 0));
|
||||
// But shape can cross also only (c) edge:
|
||||
}
|
||||
else if (end > PI - a && start < PI - a) {
|
||||
// Go to outer side
|
||||
out.push([
|
||||
'L',
|
||||
cx + (rx * Math.cos(end2)) + dx,
|
||||
cy + (ry * Math.sin(end2)) + dy
|
||||
]);
|
||||
// Curve to the true end of the slice
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, end2, end, dx, dy));
|
||||
// Go to the inner side
|
||||
out.push([
|
||||
'L', cx + (rx * Math.cos(end)), cy + (ry * Math.sin(end))
|
||||
]);
|
||||
// Go back to the artifical end2
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, end, end2, 0, 0));
|
||||
}
|
||||
out.push([
|
||||
'L', cx + (rx * Math.cos(end2)) + dx, cy + (ry * Math.sin(end2)) + dy
|
||||
]);
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, end2, start2, dx, dy));
|
||||
out.push(['Z']);
|
||||
// INSIDE
|
||||
var inn = [
|
||||
['M', cx + (irx * cs), cy + (iry * ss)]
|
||||
];
|
||||
inn = inn.concat(curveTo(cx, cy, irx, iry, start, end, 0, 0));
|
||||
inn.push([
|
||||
'L', cx + (irx * Math.cos(end)) + dx, cy + (iry * Math.sin(end)) + dy
|
||||
]);
|
||||
inn = inn.concat(curveTo(cx, cy, irx, iry, end, start, dx, dy));
|
||||
inn.push(['Z']);
|
||||
// SIDES
|
||||
var side1 = [
|
||||
['M', cx + (rx * cs), cy + (ry * ss)],
|
||||
['L', cx + (rx * cs) + dx, cy + (ry * ss) + dy],
|
||||
['L', cx + (irx * cs) + dx, cy + (iry * ss) + dy],
|
||||
['L', cx + (irx * cs), cy + (iry * ss)],
|
||||
['Z']
|
||||
];
|
||||
var side2 = [
|
||||
['M', cx + (rx * ce), cy + (ry * se)],
|
||||
['L', cx + (rx * ce) + dx, cy + (ry * se) + dy],
|
||||
['L', cx + (irx * ce) + dx, cy + (iry * se) + dy],
|
||||
['L', cx + (irx * ce), cy + (iry * se)],
|
||||
['Z']
|
||||
];
|
||||
// correction for changed position of vanishing point caused by alpha and
|
||||
// beta rotations
|
||||
var angleCorr = Math.atan2(dy, -dx), angleEnd = Math.abs(end + angleCorr), angleStart = Math.abs(start + angleCorr), angleMid = Math.abs((start + end) / 2 + angleCorr);
|
||||
/**
|
||||
* set to 0-PI range
|
||||
* @private
|
||||
*/
|
||||
function toZeroPIRange(angle) {
|
||||
angle = angle % (2 * Math.PI);
|
||||
if (angle > Math.PI) {
|
||||
angle = 2 * Math.PI - angle;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
angleEnd = toZeroPIRange(angleEnd);
|
||||
angleStart = toZeroPIRange(angleStart);
|
||||
angleMid = toZeroPIRange(angleMid);
|
||||
// *1e5 is to compensate pInt in zIndexSetter
|
||||
var incPrecision = 1e5, a1 = angleMid * incPrecision, a2 = angleStart * incPrecision, a3 = angleEnd * incPrecision;
|
||||
return {
|
||||
top: top,
|
||||
// max angle is PI, so this is always higher
|
||||
zTop: Math.PI * incPrecision + 1,
|
||||
out: out,
|
||||
zOut: Math.max(a1, a2, a3),
|
||||
inn: inn,
|
||||
zInn: Math.max(a1, a2, a3),
|
||||
side1: side1,
|
||||
zSide1: a3 * 0.99,
|
||||
side2: side2,
|
||||
zSide2: a2 * 0.99
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* Extension to the VML Renderer
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Axis from '../../Axis/Axis.js';
|
||||
import H from '../../Globals.js';
|
||||
import SVGRenderer from '../../Renderer/SVG/SVGRenderer.js';
|
||||
import U from '../../Utilities.js';
|
||||
var setOptions = U.setOptions;
|
||||
import VMLAxis3D from '../../Axis/VMLAxis3D.js';
|
||||
var VMLRenderer = H.VMLRenderer;
|
||||
if (VMLRenderer) {
|
||||
setOptions({ animate: false });
|
||||
VMLRenderer.prototype.face3d = SVGRenderer.prototype.face3d;
|
||||
VMLRenderer.prototype.polyhedron = SVGRenderer.prototype.polyhedron;
|
||||
VMLRenderer.prototype.elements3d = SVGRenderer.prototype.elements3d;
|
||||
VMLRenderer.prototype.element3d = SVGRenderer.prototype.element3d;
|
||||
VMLRenderer.prototype.cuboid = SVGRenderer.prototype.cuboid;
|
||||
VMLRenderer.prototype.cuboidPath = SVGRenderer.prototype.cuboidPath;
|
||||
VMLRenderer.prototype.toLinePath = SVGRenderer.prototype.toLinePath;
|
||||
VMLRenderer.prototype.toLineSegments = SVGRenderer.prototype.toLineSegments;
|
||||
VMLRenderer.prototype.arc3d = function (shapeArgs) {
|
||||
var result = SVGRenderer.prototype.arc3d.call(this, shapeArgs);
|
||||
result.css({ zIndex: result.zIndex });
|
||||
return result;
|
||||
};
|
||||
H.VMLRenderer.prototype.arc3dPath = SVGRenderer.prototype.arc3dPath;
|
||||
VMLAxis3D.compose(Axis);
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Chart from './Chart/Chart.js';
|
||||
import U from './Utilities.js';
|
||||
var find = U.find, isArray = U.isArray, isObject = U.isObject, merge = U.merge, objectEach = U.objectEach, pick = U.pick, splat = U.splat, uniqueKey = U.uniqueKey;
|
||||
/**
|
||||
* A callback function to gain complete control on when the responsive rule
|
||||
* applies.
|
||||
*
|
||||
* @callback Highcharts.ResponsiveCallbackFunction
|
||||
*
|
||||
* @param {Highcharts.Chart} this
|
||||
* Chart context.
|
||||
*
|
||||
* @return {boolean}
|
||||
* Return `true` if it applies.
|
||||
*/
|
||||
/**
|
||||
* Allows setting a set of rules to apply for different screen or chart
|
||||
* sizes. Each rule specifies additional chart options.
|
||||
*
|
||||
* @sample {highstock} stock/demo/responsive/
|
||||
* Stock chart
|
||||
* @sample highcharts/responsive/axis/
|
||||
* Axis
|
||||
* @sample highcharts/responsive/legend/
|
||||
* Legend
|
||||
* @sample highcharts/responsive/classname/
|
||||
* Class name
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @apioption responsive
|
||||
*/
|
||||
/**
|
||||
* A set of rules for responsive settings. The rules are executed from
|
||||
* the top down.
|
||||
*
|
||||
* @sample {highcharts} highcharts/responsive/axis/
|
||||
* Axis changes
|
||||
* @sample {highstock} highcharts/responsive/axis/
|
||||
* Axis changes
|
||||
* @sample {highmaps} highcharts/responsive/axis/
|
||||
* Axis changes
|
||||
*
|
||||
* @type {Array<*>}
|
||||
* @since 5.0.0
|
||||
* @apioption responsive.rules
|
||||
*/
|
||||
/**
|
||||
* A full set of chart options to apply as overrides to the general
|
||||
* chart options. The chart options are applied when the given rule
|
||||
* is active.
|
||||
*
|
||||
* A special case is configuration objects that take arrays, for example
|
||||
* [xAxis](#xAxis), [yAxis](#yAxis) or [series](#series). For these
|
||||
* collections, an `id` option is used to map the new option set to
|
||||
* an existing object. If an existing object of the same id is not found,
|
||||
* the item of the same indexupdated. So for example, setting `chartOptions`
|
||||
* with two series items without an `id`, will cause the existing chart's
|
||||
* two series to be updated with respective options.
|
||||
*
|
||||
* @sample {highstock} stock/demo/responsive/
|
||||
* Stock chart
|
||||
* @sample highcharts/responsive/axis/
|
||||
* Axis
|
||||
* @sample highcharts/responsive/legend/
|
||||
* Legend
|
||||
* @sample highcharts/responsive/classname/
|
||||
* Class name
|
||||
*
|
||||
* @type {Highcharts.Options}
|
||||
* @since 5.0.0
|
||||
* @apioption responsive.rules.chartOptions
|
||||
*/
|
||||
/**
|
||||
* Under which conditions the rule applies.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @apioption responsive.rules.condition
|
||||
*/
|
||||
/**
|
||||
* A callback function to gain complete control on when the responsive
|
||||
* rule applies. Return `true` if it applies. This opens for checking
|
||||
* against other metrics than the chart size, for example the document
|
||||
* size or other elements.
|
||||
*
|
||||
* @type {Highcharts.ResponsiveCallbackFunction}
|
||||
* @since 5.0.0
|
||||
* @context Highcharts.Chart
|
||||
* @apioption responsive.rules.condition.callback
|
||||
*/
|
||||
/**
|
||||
* The responsive rule applies if the chart height is less than this.
|
||||
*
|
||||
* @type {number}
|
||||
* @since 5.0.0
|
||||
* @apioption responsive.rules.condition.maxHeight
|
||||
*/
|
||||
/**
|
||||
* The responsive rule applies if the chart width is less than this.
|
||||
*
|
||||
* @sample highcharts/responsive/axis/
|
||||
* Max width is 500
|
||||
*
|
||||
* @type {number}
|
||||
* @since 5.0.0
|
||||
* @apioption responsive.rules.condition.maxWidth
|
||||
*/
|
||||
/**
|
||||
* The responsive rule applies if the chart height is greater than this.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 0
|
||||
* @since 5.0.0
|
||||
* @apioption responsive.rules.condition.minHeight
|
||||
*/
|
||||
/**
|
||||
* The responsive rule applies if the chart width is greater than this.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 0
|
||||
* @since 5.0.0
|
||||
* @apioption responsive.rules.condition.minWidth
|
||||
*/
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* Update the chart based on the current chart/document size and options for
|
||||
* responsiveness.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Chart#setResponsive
|
||||
* @param {boolean} [redraw=true]
|
||||
* @param {boolean} [reset=false]
|
||||
* Reset by un-applying all rules. Chart.update resets all rules before applying
|
||||
* updated options.
|
||||
*/
|
||||
Chart.prototype.setResponsive = function (redraw, reset) {
|
||||
var options = this.options.responsive, ruleIds = [], currentResponsive = this.currentResponsive, currentRuleIds, undoOptions;
|
||||
if (!reset && options && options.rules) {
|
||||
options.rules.forEach(function (rule) {
|
||||
if (typeof rule._id === 'undefined') {
|
||||
rule._id = uniqueKey();
|
||||
}
|
||||
this.matchResponsiveRule(rule, ruleIds /* , redraw */);
|
||||
}, this);
|
||||
}
|
||||
// Merge matching rules
|
||||
var mergedOptions = merge.apply(0, ruleIds.map(function (ruleId) {
|
||||
return find(options.rules, function (rule) {
|
||||
return rule._id === ruleId;
|
||||
}).chartOptions;
|
||||
}));
|
||||
mergedOptions.isResponsiveOptions = true;
|
||||
// Stringified key for the rules that currently apply.
|
||||
ruleIds = (ruleIds.toString() || void 0);
|
||||
currentRuleIds = currentResponsive && currentResponsive.ruleIds;
|
||||
// Changes in what rules apply
|
||||
if (ruleIds !== currentRuleIds) {
|
||||
// Undo previous rules. Before we apply a new set of rules, we need to
|
||||
// roll back completely to base options (#6291).
|
||||
if (currentResponsive) {
|
||||
this.update(currentResponsive.undoOptions, redraw, true);
|
||||
}
|
||||
if (ruleIds) {
|
||||
// Get undo-options for matching rules
|
||||
undoOptions = this.currentOptions(mergedOptions);
|
||||
undoOptions.isResponsiveOptions = true;
|
||||
this.currentResponsive = {
|
||||
ruleIds: ruleIds,
|
||||
mergedOptions: mergedOptions,
|
||||
undoOptions: undoOptions
|
||||
};
|
||||
this.update(mergedOptions, redraw, true);
|
||||
}
|
||||
else {
|
||||
this.currentResponsive = void 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Handle a single responsiveness rule.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Chart#matchResponsiveRule
|
||||
* @param {Highcharts.ResponsiveRulesOptions} rule
|
||||
* @param {Array<string>} matches
|
||||
*/
|
||||
Chart.prototype.matchResponsiveRule = function (rule, matches) {
|
||||
var condition = rule.condition, fn = condition.callback || function () {
|
||||
return (this.chartWidth <= pick(condition.maxWidth, Number.MAX_VALUE) &&
|
||||
this.chartHeight <=
|
||||
pick(condition.maxHeight, Number.MAX_VALUE) &&
|
||||
this.chartWidth >= pick(condition.minWidth, 0) &&
|
||||
this.chartHeight >= pick(condition.minHeight, 0));
|
||||
};
|
||||
if (fn.call(this)) {
|
||||
matches.push(rule._id);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Get the current values for a given set of options. Used before we update
|
||||
* the chart with a new responsiveness rule.
|
||||
*
|
||||
* @todo Restore axis options (by id?). The matching of items in collections
|
||||
* bears resemblance to the oneToOne matching in Chart.update. Probably we can
|
||||
* refactor out that matching and reuse it in both functions.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Chart#currentOptions
|
||||
* @param {Highcharts.Options} options
|
||||
* @return {Highcharts.Options}
|
||||
*/
|
||||
Chart.prototype.currentOptions = function (options) {
|
||||
var chart = this, ret = {};
|
||||
/**
|
||||
* Recurse over a set of options and its current values,
|
||||
* and store the current values in the ret object.
|
||||
*/
|
||||
function getCurrent(options, curr, ret, depth) {
|
||||
var i;
|
||||
objectEach(options, function (val, key) {
|
||||
if (!depth &&
|
||||
chart.collectionsWithUpdate.indexOf(key) > -1) {
|
||||
val = splat(val);
|
||||
ret[key] = [];
|
||||
// Iterate over collections like series, xAxis or yAxis and map
|
||||
// the items by index.
|
||||
for (i = 0; i < Math.max(val.length, curr[key].length); i++) {
|
||||
// Item exists in current data (#6347)
|
||||
if (curr[key][i]) {
|
||||
// If the item is missing from the new data, we need to
|
||||
// save the whole config structure. Like when
|
||||
// responsively updating from a dual axis layout to a
|
||||
// single axis and back (#13544).
|
||||
if (val[i] === void 0) {
|
||||
ret[key][i] = curr[key][i];
|
||||
// Otherwise, proceed
|
||||
}
|
||||
else {
|
||||
ret[key][i] = {};
|
||||
getCurrent(val[i], curr[key][i], ret[key][i], depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isObject(val)) {
|
||||
ret[key] = isArray(val) ? [] : {};
|
||||
getCurrent(val, curr[key] || {}, ret[key], depth + 1);
|
||||
}
|
||||
else if (typeof curr[key] === 'undefined') { // #10286
|
||||
ret[key] = null;
|
||||
}
|
||||
else {
|
||||
ret[key] = curr[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
getCurrent(options, this.options, ret, 0);
|
||||
return ret;
|
||||
};
|
||||
@@ -0,0 +1,813 @@
|
||||
/* *
|
||||
*
|
||||
* (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/Axis.js';
|
||||
import H from './Globals.js';
|
||||
import ScrollbarAxis from './Axis/ScrollbarAxis.js';
|
||||
import U from './Utilities.js';
|
||||
var addEvent = U.addEvent, correctFloat = U.correctFloat, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, fireEvent = U.fireEvent, merge = U.merge, pick = U.pick, removeEvent = U.removeEvent;
|
||||
import O from './Options.js';
|
||||
var defaultOptions = O.defaultOptions;
|
||||
var hasTouch = H.hasTouch, isTouchDevice = H.isTouchDevice;
|
||||
/**
|
||||
* When we have vertical scrollbar, rifles and arrow in buttons should be
|
||||
* rotated. The same method is used in Navigator's handles, to rotate them.
|
||||
*
|
||||
* @function Highcharts.swapXY
|
||||
*
|
||||
* @param {Highcharts.SVGPathArray} path
|
||||
* Path to be rotated.
|
||||
*
|
||||
* @param {boolean} [vertical]
|
||||
* If vertical scrollbar, swap x-y values.
|
||||
*
|
||||
* @return {Highcharts.SVGPathArray}
|
||||
* Rotated path.
|
||||
*
|
||||
* @requires modules/stock
|
||||
*/
|
||||
var swapXY = H.swapXY = function (path, vertical) {
|
||||
if (vertical) {
|
||||
path.forEach(function (seg) {
|
||||
var len = seg.length;
|
||||
var temp;
|
||||
for (var i = 0; i < len; i += 2) {
|
||||
temp = seg[i + 1];
|
||||
if (typeof temp === 'number') {
|
||||
seg[i + 1] = seg[i + 2];
|
||||
seg[i + 2] = temp;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return path;
|
||||
};
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* A reusable scrollbar, internally used in Highstock's navigator and optionally
|
||||
* on individual axes.
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.Scrollbar
|
||||
* @param {Highcharts.SVGRenderer} renderer
|
||||
* @param {Highcharts.ScrollbarOptions} options
|
||||
* @param {Highcharts.Chart} chart
|
||||
*/
|
||||
var Scrollbar = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function Scrollbar(renderer, options, chart) {
|
||||
/* *
|
||||
*
|
||||
* Properties
|
||||
*
|
||||
* */
|
||||
this._events = [];
|
||||
this.chartX = 0;
|
||||
this.chartY = 0;
|
||||
this.from = 0;
|
||||
this.group = void 0;
|
||||
this.scrollbar = void 0;
|
||||
this.scrollbarButtons = [];
|
||||
this.scrollbarGroup = void 0;
|
||||
this.scrollbarLeft = 0;
|
||||
this.scrollbarRifles = void 0;
|
||||
this.scrollbarStrokeWidth = 1;
|
||||
this.scrollbarTop = 0;
|
||||
this.size = 0;
|
||||
this.to = 0;
|
||||
this.track = void 0;
|
||||
this.trackBorderWidth = 1;
|
||||
this.userOptions = {};
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
this.chart = chart;
|
||||
this.options = options;
|
||||
this.renderer = chart.renderer;
|
||||
this.init(renderer, options, chart);
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Set up the mouse and touch events for the Scrollbar
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#addEvents
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.addEvents = function () {
|
||||
var buttonsOrder = this.options.inverted ? [1, 0] : [0, 1], buttons = this.scrollbarButtons, bar = this.scrollbarGroup.element, track = this.track.element, mouseDownHandler = this.mouseDownHandler.bind(this), mouseMoveHandler = this.mouseMoveHandler.bind(this), mouseUpHandler = this.mouseUpHandler.bind(this), _events;
|
||||
// Mouse events
|
||||
_events = [
|
||||
[buttons[buttonsOrder[0]].element, 'click', this.buttonToMinClick.bind(this)],
|
||||
[buttons[buttonsOrder[1]].element, 'click', this.buttonToMaxClick.bind(this)],
|
||||
[track, 'click', this.trackClick.bind(this)],
|
||||
[bar, 'mousedown', mouseDownHandler],
|
||||
[bar.ownerDocument, 'mousemove', mouseMoveHandler],
|
||||
[bar.ownerDocument, 'mouseup', mouseUpHandler]
|
||||
];
|
||||
// Touch events
|
||||
if (hasTouch) {
|
||||
_events.push([bar, 'touchstart', mouseDownHandler], [bar.ownerDocument, 'touchmove', mouseMoveHandler], [bar.ownerDocument, 'touchend', mouseUpHandler]);
|
||||
}
|
||||
// Add them all
|
||||
_events.forEach(function (args) {
|
||||
addEvent.apply(null, args);
|
||||
});
|
||||
this._events = _events;
|
||||
};
|
||||
Scrollbar.prototype.buttonToMaxClick = function (e) {
|
||||
var scroller = this;
|
||||
var range = (scroller.to - scroller.from) * pick(scroller.options.step, 0.2);
|
||||
scroller.updatePosition(scroller.from + range, scroller.to + range);
|
||||
fireEvent(scroller, 'changed', {
|
||||
from: scroller.from,
|
||||
to: scroller.to,
|
||||
trigger: 'scrollbar',
|
||||
DOMEvent: e
|
||||
});
|
||||
};
|
||||
Scrollbar.prototype.buttonToMinClick = function (e) {
|
||||
var scroller = this;
|
||||
var range = correctFloat(scroller.to - scroller.from) *
|
||||
pick(scroller.options.step, 0.2);
|
||||
scroller.updatePosition(correctFloat(scroller.from - range), correctFloat(scroller.to - range));
|
||||
fireEvent(scroller, 'changed', {
|
||||
from: scroller.from,
|
||||
to: scroller.to,
|
||||
trigger: 'scrollbar',
|
||||
DOMEvent: e
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Get normalized (0-1) cursor position over the scrollbar
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#cursorToScrollbarPosition
|
||||
*
|
||||
* @param {*} normalizedEvent
|
||||
* normalized event, with chartX and chartY values
|
||||
*
|
||||
* @return {Highcharts.Dictionary<number>}
|
||||
* Local position {chartX, chartY}
|
||||
*/
|
||||
Scrollbar.prototype.cursorToScrollbarPosition = function (normalizedEvent) {
|
||||
var scroller = this, options = scroller.options, minWidthDifference = options.minWidth > scroller.calculatedWidth ?
|
||||
options.minWidth :
|
||||
0; // minWidth distorts translation
|
||||
return {
|
||||
chartX: (normalizedEvent.chartX - scroller.x -
|
||||
scroller.xOffset) /
|
||||
(scroller.barWidth - minWidthDifference),
|
||||
chartY: (normalizedEvent.chartY - scroller.y -
|
||||
scroller.yOffset) /
|
||||
(scroller.barWidth - minWidthDifference)
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Destroys allocated elements.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#destroy
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.destroy = function () {
|
||||
var scroller = this.chart.scroller;
|
||||
// Disconnect events added in addEvents
|
||||
this.removeEvents();
|
||||
// Destroy properties
|
||||
[
|
||||
'track',
|
||||
'scrollbarRifles',
|
||||
'scrollbar',
|
||||
'scrollbarGroup',
|
||||
'group'
|
||||
].forEach(function (prop) {
|
||||
if (this[prop] && this[prop].destroy) {
|
||||
this[prop] = this[prop].destroy();
|
||||
}
|
||||
}, this);
|
||||
// #6421, chart may have more scrollbars
|
||||
if (scroller && this === scroller.scrollbar) {
|
||||
scroller.scrollbar = null;
|
||||
// Destroy elements in collection
|
||||
destroyObjectProperties(scroller.scrollbarButtons);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Draw the scrollbar buttons with arrows
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#drawScrollbarButton
|
||||
* @param {number} index
|
||||
* 0 is left, 1 is right
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.drawScrollbarButton = function (index) {
|
||||
var scroller = this, renderer = scroller.renderer, scrollbarButtons = scroller.scrollbarButtons, options = scroller.options, size = scroller.size, group, tempElem;
|
||||
group = renderer.g().add(scroller.group);
|
||||
scrollbarButtons.push(group);
|
||||
// Create a rectangle for the scrollbar button
|
||||
tempElem = renderer.rect()
|
||||
.addClass('highcharts-scrollbar-button')
|
||||
.add(group);
|
||||
// Presentational attributes
|
||||
if (!this.chart.styledMode) {
|
||||
tempElem.attr({
|
||||
stroke: options.buttonBorderColor,
|
||||
'stroke-width': options.buttonBorderWidth,
|
||||
fill: options.buttonBackgroundColor
|
||||
});
|
||||
}
|
||||
// Place the rectangle based on the rendered stroke width
|
||||
tempElem.attr(tempElem.crisp({
|
||||
x: -0.5,
|
||||
y: -0.5,
|
||||
width: size + 1,
|
||||
height: size + 1,
|
||||
r: options.buttonBorderRadius
|
||||
}, tempElem.strokeWidth()));
|
||||
// Button arrow
|
||||
tempElem = renderer
|
||||
.path(swapXY([[
|
||||
'M',
|
||||
size / 2 + (index ? -1 : 1),
|
||||
size / 2 - 3
|
||||
], [
|
||||
'L',
|
||||
size / 2 + (index ? -1 : 1),
|
||||
size / 2 + 3
|
||||
], [
|
||||
'L',
|
||||
size / 2 + (index ? 2 : -2),
|
||||
size / 2
|
||||
]], options.vertical))
|
||||
.addClass('highcharts-scrollbar-arrow')
|
||||
.add(scrollbarButtons[index]);
|
||||
if (!this.chart.styledMode) {
|
||||
tempElem.attr({
|
||||
fill: options.buttonArrowColor
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#init
|
||||
* @param {Highcharts.SVGRenderer} renderer
|
||||
* @param {Highcharts.ScrollbarOptions} options
|
||||
* @param {Highcharts.Chart} chart
|
||||
*/
|
||||
Scrollbar.prototype.init = function (renderer, options, chart) {
|
||||
this.scrollbarButtons = [];
|
||||
this.renderer = renderer;
|
||||
this.userOptions = options;
|
||||
this.options = merge(Scrollbar.defaultOptions, options);
|
||||
this.chart = chart;
|
||||
// backward compatibility
|
||||
this.size = pick(this.options.size, this.options.height);
|
||||
// Init
|
||||
if (options.enabled) {
|
||||
this.render();
|
||||
this.addEvents();
|
||||
}
|
||||
};
|
||||
Scrollbar.prototype.mouseDownHandler = function (e) {
|
||||
var scroller = this;
|
||||
var normalizedEvent = scroller.chart.pointer.normalize(e), mousePosition = scroller.cursorToScrollbarPosition(normalizedEvent);
|
||||
scroller.chartX = mousePosition.chartX;
|
||||
scroller.chartY = mousePosition.chartY;
|
||||
scroller.initPositions = [scroller.from, scroller.to];
|
||||
scroller.grabbedCenter = true;
|
||||
};
|
||||
/**
|
||||
* Event handler for the mouse move event.
|
||||
* @private
|
||||
*/
|
||||
Scrollbar.prototype.mouseMoveHandler = function (e) {
|
||||
var scroller = this;
|
||||
var normalizedEvent = scroller.chart.pointer.normalize(e), options = scroller.options, direction = options.vertical ? 'chartY' : 'chartX', initPositions = scroller.initPositions || [], scrollPosition, chartPosition, change;
|
||||
// In iOS, a mousemove event with e.pageX === 0 is fired when
|
||||
// holding the finger down in the center of the scrollbar. This
|
||||
// should be ignored.
|
||||
if (scroller.grabbedCenter &&
|
||||
// #4696, scrollbar failed on Android
|
||||
(!e.touches || e.touches[0][direction] !== 0)) {
|
||||
chartPosition = scroller.cursorToScrollbarPosition(normalizedEvent)[direction];
|
||||
scrollPosition = scroller[direction];
|
||||
change = chartPosition - scrollPosition;
|
||||
scroller.hasDragged = true;
|
||||
scroller.updatePosition(initPositions[0] + change, initPositions[1] + change);
|
||||
if (scroller.hasDragged) {
|
||||
fireEvent(scroller, 'changed', {
|
||||
from: scroller.from,
|
||||
to: scroller.to,
|
||||
trigger: 'scrollbar',
|
||||
DOMType: e.type,
|
||||
DOMEvent: e
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Event handler for the mouse up event.
|
||||
* @private
|
||||
*/
|
||||
Scrollbar.prototype.mouseUpHandler = function (e) {
|
||||
var scroller = this;
|
||||
if (scroller.hasDragged) {
|
||||
fireEvent(scroller, 'changed', {
|
||||
from: scroller.from,
|
||||
to: scroller.to,
|
||||
trigger: 'scrollbar',
|
||||
DOMType: e.type,
|
||||
DOMEvent: e
|
||||
});
|
||||
}
|
||||
scroller.grabbedCenter =
|
||||
scroller.hasDragged =
|
||||
scroller.chartX =
|
||||
scroller.chartY = null;
|
||||
};
|
||||
/**
|
||||
* Position the scrollbar, method called from a parent with defined
|
||||
* dimensions.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#position
|
||||
* @param {number} x
|
||||
* x-position on the chart
|
||||
* @param {number} y
|
||||
* y-position on the chart
|
||||
* @param {number} width
|
||||
* width of the scrollbar
|
||||
* @param {number} height
|
||||
* height of the scorllbar
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.position = function (x, y, width, height) {
|
||||
var scroller = this, options = scroller.options, vertical = options.vertical, xOffset = height, yOffset = 0, method = scroller.rendered ? 'animate' : 'attr';
|
||||
scroller.x = x;
|
||||
scroller.y = y + this.trackBorderWidth;
|
||||
scroller.width = width; // width with buttons
|
||||
scroller.height = height;
|
||||
scroller.xOffset = xOffset;
|
||||
scroller.yOffset = yOffset;
|
||||
// If Scrollbar is a vertical type, swap options:
|
||||
if (vertical) {
|
||||
scroller.width = scroller.yOffset = width = yOffset = scroller.size;
|
||||
scroller.xOffset = xOffset = 0;
|
||||
scroller.barWidth = height - width * 2; // width without buttons
|
||||
scroller.x = x = x + scroller.options.margin;
|
||||
}
|
||||
else {
|
||||
scroller.height = scroller.xOffset = height = xOffset =
|
||||
scroller.size;
|
||||
scroller.barWidth = width - height * 2; // width without buttons
|
||||
scroller.y = scroller.y + scroller.options.margin;
|
||||
}
|
||||
// Set general position for a group:
|
||||
scroller.group[method]({
|
||||
translateX: x,
|
||||
translateY: scroller.y
|
||||
});
|
||||
// Resize background/track:
|
||||
scroller.track[method]({
|
||||
width: width,
|
||||
height: height
|
||||
});
|
||||
// Move right/bottom button ot it's place:
|
||||
scroller.scrollbarButtons[1][method]({
|
||||
translateX: vertical ? 0 : width - xOffset,
|
||||
translateY: vertical ? height - yOffset : 0
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Removes the event handlers attached previously with addEvents.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#removeEvents
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.removeEvents = function () {
|
||||
this._events.forEach(function (args) {
|
||||
removeEvent.apply(null, args);
|
||||
});
|
||||
this._events.length = 0;
|
||||
};
|
||||
/**
|
||||
* Render scrollbar with all required items.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#render
|
||||
*/
|
||||
Scrollbar.prototype.render = function () {
|
||||
var scroller = this, renderer = scroller.renderer, options = scroller.options, size = scroller.size, styledMode = this.chart.styledMode, group;
|
||||
// Draw the scrollbar group
|
||||
scroller.group = group = renderer.g('scrollbar').attr({
|
||||
zIndex: options.zIndex,
|
||||
translateY: -99999
|
||||
}).add();
|
||||
// Draw the scrollbar track:
|
||||
scroller.track = renderer.rect()
|
||||
.addClass('highcharts-scrollbar-track')
|
||||
.attr({
|
||||
x: 0,
|
||||
r: options.trackBorderRadius || 0,
|
||||
height: size,
|
||||
width: size
|
||||
}).add(group);
|
||||
if (!styledMode) {
|
||||
scroller.track.attr({
|
||||
fill: options.trackBackgroundColor,
|
||||
stroke: options.trackBorderColor,
|
||||
'stroke-width': options.trackBorderWidth
|
||||
});
|
||||
}
|
||||
this.trackBorderWidth = scroller.track.strokeWidth();
|
||||
scroller.track.attr({
|
||||
y: -this.trackBorderWidth % 2 / 2
|
||||
});
|
||||
// Draw the scrollbar itself
|
||||
scroller.scrollbarGroup = renderer.g().add(group);
|
||||
scroller.scrollbar = renderer.rect()
|
||||
.addClass('highcharts-scrollbar-thumb')
|
||||
.attr({
|
||||
height: size,
|
||||
width: size,
|
||||
r: options.barBorderRadius || 0
|
||||
}).add(scroller.scrollbarGroup);
|
||||
scroller.scrollbarRifles = renderer
|
||||
.path(swapXY([
|
||||
['M', -3, size / 4],
|
||||
['L', -3, 2 * size / 3],
|
||||
['M', 0, size / 4],
|
||||
['L', 0, 2 * size / 3],
|
||||
['M', 3, size / 4],
|
||||
['L', 3, 2 * size / 3]
|
||||
], options.vertical))
|
||||
.addClass('highcharts-scrollbar-rifles')
|
||||
.add(scroller.scrollbarGroup);
|
||||
if (!styledMode) {
|
||||
scroller.scrollbar.attr({
|
||||
fill: options.barBackgroundColor,
|
||||
stroke: options.barBorderColor,
|
||||
'stroke-width': options.barBorderWidth
|
||||
});
|
||||
scroller.scrollbarRifles.attr({
|
||||
stroke: options.rifleColor,
|
||||
'stroke-width': 1
|
||||
});
|
||||
}
|
||||
scroller.scrollbarStrokeWidth = scroller.scrollbar.strokeWidth();
|
||||
scroller.scrollbarGroup.translate(-scroller.scrollbarStrokeWidth % 2 / 2, -scroller.scrollbarStrokeWidth % 2 / 2);
|
||||
// Draw the buttons:
|
||||
scroller.drawScrollbarButton(0);
|
||||
scroller.drawScrollbarButton(1);
|
||||
};
|
||||
/**
|
||||
* Set scrollbar size, with a given scale.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#setRange
|
||||
* @param {number} from
|
||||
* scale (0-1) where bar should start
|
||||
* @param {number} to
|
||||
* scale (0-1) where bar should end
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.setRange = function (from, to) {
|
||||
var scroller = this, options = scroller.options, vertical = options.vertical, minWidth = options.minWidth, fullWidth = scroller.barWidth, fromPX, toPX, newPos, newSize, newRiflesPos, method = (this.rendered &&
|
||||
!this.hasDragged &&
|
||||
!(this.chart.navigator && this.chart.navigator.hasDragged)) ? 'animate' : 'attr';
|
||||
if (!defined(fullWidth)) {
|
||||
return;
|
||||
}
|
||||
from = Math.max(from, 0);
|
||||
fromPX = Math.ceil(fullWidth * from);
|
||||
toPX = fullWidth * Math.min(to, 1);
|
||||
scroller.calculatedWidth = newSize = correctFloat(toPX - fromPX);
|
||||
// We need to recalculate position, if minWidth is used
|
||||
if (newSize < minWidth) {
|
||||
fromPX = (fullWidth - minWidth + newSize) * from;
|
||||
newSize = minWidth;
|
||||
}
|
||||
newPos = Math.floor(fromPX + scroller.xOffset + scroller.yOffset);
|
||||
newRiflesPos = newSize / 2 - 0.5; // -0.5 -> rifle line width / 2
|
||||
// Store current position:
|
||||
scroller.from = from;
|
||||
scroller.to = to;
|
||||
if (!vertical) {
|
||||
scroller.scrollbarGroup[method]({
|
||||
translateX: newPos
|
||||
});
|
||||
scroller.scrollbar[method]({
|
||||
width: newSize
|
||||
});
|
||||
scroller.scrollbarRifles[method]({
|
||||
translateX: newRiflesPos
|
||||
});
|
||||
scroller.scrollbarLeft = newPos;
|
||||
scroller.scrollbarTop = 0;
|
||||
}
|
||||
else {
|
||||
scroller.scrollbarGroup[method]({
|
||||
translateY: newPos
|
||||
});
|
||||
scroller.scrollbar[method]({
|
||||
height: newSize
|
||||
});
|
||||
scroller.scrollbarRifles[method]({
|
||||
translateY: newRiflesPos
|
||||
});
|
||||
scroller.scrollbarTop = newPos;
|
||||
scroller.scrollbarLeft = 0;
|
||||
}
|
||||
if (newSize <= 12) {
|
||||
scroller.scrollbarRifles.hide();
|
||||
}
|
||||
else {
|
||||
scroller.scrollbarRifles.show(true);
|
||||
}
|
||||
// Show or hide the scrollbar based on the showFull setting
|
||||
if (options.showFull === false) {
|
||||
if (from <= 0 && to >= 1) {
|
||||
scroller.group.hide();
|
||||
}
|
||||
else {
|
||||
scroller.group.show();
|
||||
}
|
||||
}
|
||||
scroller.rendered = true;
|
||||
};
|
||||
Scrollbar.prototype.trackClick = function (e) {
|
||||
var scroller = this;
|
||||
var normalizedEvent = scroller.chart.pointer.normalize(e), range = scroller.to - scroller.from, top = scroller.y + scroller.scrollbarTop, left = scroller.x + scroller.scrollbarLeft;
|
||||
if ((scroller.options.vertical && normalizedEvent.chartY > top) ||
|
||||
(!scroller.options.vertical && normalizedEvent.chartX > left)) {
|
||||
// On the top or on the left side of the track:
|
||||
scroller.updatePosition(scroller.from + range, scroller.to + range);
|
||||
}
|
||||
else {
|
||||
// On the bottom or the right side of the track:
|
||||
scroller.updatePosition(scroller.from - range, scroller.to - range);
|
||||
}
|
||||
fireEvent(scroller, 'changed', {
|
||||
from: scroller.from,
|
||||
to: scroller.to,
|
||||
trigger: 'scrollbar',
|
||||
DOMEvent: e
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Update the scrollbar with new options
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#update
|
||||
* @param {Highcharts.ScrollbarOptions} options
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.update = function (options) {
|
||||
this.destroy();
|
||||
this.init(this.chart.renderer, merge(true, this.options, options), this.chart);
|
||||
};
|
||||
/**
|
||||
* Update position option in the Scrollbar, with normalized 0-1 scale
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#updatePosition
|
||||
* @param {number} from
|
||||
* @param {number} to
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.updatePosition = function (from, to) {
|
||||
if (to > 1) {
|
||||
from = correctFloat(1 - correctFloat(to - from));
|
||||
to = 1;
|
||||
}
|
||||
if (from < 0) {
|
||||
to = correctFloat(to - from);
|
||||
from = 0;
|
||||
}
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Static Properties
|
||||
*
|
||||
* */
|
||||
/**
|
||||
*
|
||||
* The scrollbar is a means of panning over the X axis of a stock chart.
|
||||
* Scrollbars can also be applied to other types of axes.
|
||||
*
|
||||
* Another approach to scrollable charts is the [chart.scrollablePlotArea](
|
||||
* https://api.highcharts.com/highcharts/chart.scrollablePlotArea) option that
|
||||
* is especially suitable for simpler cartesian charts on mobile.
|
||||
*
|
||||
* In styled mode, all the presentational options for the
|
||||
* scrollbar are replaced by the classes `.highcharts-scrollbar-thumb`,
|
||||
* `.highcharts-scrollbar-arrow`, `.highcharts-scrollbar-button`,
|
||||
* `.highcharts-scrollbar-rifles` and `.highcharts-scrollbar-track`.
|
||||
*
|
||||
* @sample stock/yaxis/inverted-bar-scrollbar/
|
||||
* A scrollbar on a simple bar chart
|
||||
*
|
||||
* @product highstock gantt
|
||||
* @optionparent scrollbar
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Scrollbar.defaultOptions = {
|
||||
/**
|
||||
* The height of the scrollbar. The height also applies to the width
|
||||
* of the scroll arrows so that they are always squares. Defaults to
|
||||
* 20 for touch devices and 14 for mouse devices.
|
||||
*
|
||||
* @sample stock/scrollbar/height/
|
||||
* A 30px scrollbar
|
||||
*
|
||||
* @type {number}
|
||||
* @default 20/14
|
||||
*/
|
||||
height: isTouchDevice ? 20 : 14,
|
||||
/**
|
||||
* The border rounding radius of the bar.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*/
|
||||
barBorderRadius: 0,
|
||||
/**
|
||||
* The corner radius of the scrollbar buttons.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*/
|
||||
buttonBorderRadius: 0,
|
||||
/**
|
||||
* Enable or disable the scrollbar.
|
||||
*
|
||||
* @sample stock/scrollbar/enabled/
|
||||
* Disable the scrollbar, only use navigator
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
* @apioption scrollbar.enabled
|
||||
*/
|
||||
/**
|
||||
* Whether to redraw the main chart as the scrollbar or the navigator
|
||||
* zoomed window is moved. Defaults to `true` for modern browsers and
|
||||
* `false` for legacy IE browsers as well as mobile devices.
|
||||
*
|
||||
* @sample stock/scrollbar/liveredraw
|
||||
* Setting live redraw to false
|
||||
*
|
||||
* @type {boolean}
|
||||
* @since 1.3
|
||||
*/
|
||||
liveRedraw: void 0,
|
||||
/**
|
||||
* The margin between the scrollbar and its axis when the scrollbar is
|
||||
* applied directly to an axis.
|
||||
*/
|
||||
margin: 10,
|
||||
/**
|
||||
* The minimum width of the scrollbar.
|
||||
*
|
||||
* @since 1.2.5
|
||||
*/
|
||||
minWidth: 6,
|
||||
/**
|
||||
* Whether to show or hide the scrollbar when the scrolled content is
|
||||
* zoomed out to it full extent.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
* @apioption scrollbar.showFull
|
||||
*/
|
||||
step: 0.2,
|
||||
/**
|
||||
* The z index of the scrollbar group.
|
||||
*/
|
||||
zIndex: 3,
|
||||
/**
|
||||
* The background color of the scrollbar itself.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
barBackgroundColor: '#cccccc',
|
||||
/**
|
||||
* The width of the bar's border.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*/
|
||||
barBorderWidth: 1,
|
||||
/**
|
||||
* The color of the scrollbar's border.
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
barBorderColor: '#cccccc',
|
||||
/**
|
||||
* The color of the small arrow inside the scrollbar buttons.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
buttonArrowColor: '#333333',
|
||||
/**
|
||||
* The color of scrollbar buttons.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
buttonBackgroundColor: '#e6e6e6',
|
||||
/**
|
||||
* The color of the border of the scrollbar buttons.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
buttonBorderColor: '#cccccc',
|
||||
/**
|
||||
* The border width of the scrollbar buttons.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*/
|
||||
buttonBorderWidth: 1,
|
||||
/**
|
||||
* The color of the small rifles in the middle of the scrollbar.
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
rifleColor: '#333333',
|
||||
/**
|
||||
* The color of the track background.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
trackBackgroundColor: '#f2f2f2',
|
||||
/**
|
||||
* The color of the border of the scrollbar track.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
trackBorderColor: '#f2f2f2',
|
||||
/**
|
||||
* The corner radius of the border of the scrollbar track.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*
|
||||
* @type {number}
|
||||
* @default 0
|
||||
* @apioption scrollbar.trackBorderRadius
|
||||
*/
|
||||
/**
|
||||
* The width of the border of the scrollbar track.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*/
|
||||
trackBorderWidth: 1
|
||||
};
|
||||
return Scrollbar;
|
||||
}());
|
||||
if (!H.Scrollbar) {
|
||||
defaultOptions.scrollbar = merge(true, Scrollbar.defaultOptions, defaultOptions.scrollbar);
|
||||
H.Scrollbar = Scrollbar;
|
||||
ScrollbarAxis.compose(Axis, Scrollbar);
|
||||
}
|
||||
export default H.Scrollbar;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,802 @@
|
||||
/* *
|
||||
*
|
||||
* (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';
|
||||
import U from '../Utilities.js';
|
||||
var animObject = U.animObject, defined = U.defined, erase = U.erase, extend = U.extend, fireEvent = U.fireEvent, format = U.format, getNestedProperty = U.getNestedProperty, isArray = U.isArray, isNumber = U.isNumber, isObject = U.isObject, syncTimeout = U.syncTimeout, pick = U.pick, removeEvent = U.removeEvent, uniqueKey = U.uniqueKey;
|
||||
/**
|
||||
* Function callback when a series point is clicked. Return false to cancel the
|
||||
* action.
|
||||
*
|
||||
* @callback Highcharts.PointClickCallbackFunction
|
||||
*
|
||||
* @param {Highcharts.Point} this
|
||||
* The point where the event occured.
|
||||
*
|
||||
* @param {Highcharts.PointClickEventObject} event
|
||||
* Event arguments.
|
||||
*/
|
||||
/**
|
||||
* Common information for a click event on a series point.
|
||||
*
|
||||
* @interface Highcharts.PointClickEventObject
|
||||
* @extends Highcharts.PointerEventObject
|
||||
*/ /**
|
||||
* Clicked point.
|
||||
* @name Highcharts.PointClickEventObject#point
|
||||
* @type {Highcharts.Point}
|
||||
*/
|
||||
/**
|
||||
* Configuration hash for the data label and tooltip formatters.
|
||||
*
|
||||
* @interface Highcharts.PointLabelObject
|
||||
*/ /**
|
||||
* The point's current color.
|
||||
* @name Highcharts.PointLabelObject#color
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined}
|
||||
*/ /**
|
||||
* The point's current color index, used in styled mode instead of `color`. The
|
||||
* color index is inserted in class names used for styling.
|
||||
* @name Highcharts.PointLabelObject#colorIndex
|
||||
* @type {number}
|
||||
*/ /**
|
||||
* The name of the related point.
|
||||
* @name Highcharts.PointLabelObject#key
|
||||
* @type {string|undefined}
|
||||
*/ /**
|
||||
* The percentage for related points in a stacked series or pies.
|
||||
* @name Highcharts.PointLabelObject#percentage
|
||||
* @type {number}
|
||||
*/ /**
|
||||
* The related point. The point name, if defined, is available through
|
||||
* `this.point.name`.
|
||||
* @name Highcharts.PointLabelObject#point
|
||||
* @type {Highcharts.Point}
|
||||
*/ /**
|
||||
* The related series. The series name is available through `this.series.name`.
|
||||
* @name Highcharts.PointLabelObject#series
|
||||
* @type {Highcharts.Series}
|
||||
*/ /**
|
||||
* The total of values in either a stack for stacked series, or a pie in a pie
|
||||
* series.
|
||||
* @name Highcharts.PointLabelObject#total
|
||||
* @type {number|undefined}
|
||||
*/ /**
|
||||
* For categorized axes this property holds the category name for the point. For
|
||||
* other axes it holds the X value.
|
||||
* @name Highcharts.PointLabelObject#x
|
||||
* @type {number|string|undefined}
|
||||
*/ /**
|
||||
* The y value of the point.
|
||||
* @name Highcharts.PointLabelObject#y
|
||||
* @type {number|undefined}
|
||||
*/
|
||||
/**
|
||||
* Gets fired when the mouse leaves the area close to the point.
|
||||
*
|
||||
* @callback Highcharts.PointMouseOutCallbackFunction
|
||||
*
|
||||
* @param {Highcharts.Point} this
|
||||
* Point where the event occured.
|
||||
*
|
||||
* @param {global.PointerEvent} event
|
||||
* Event that occured.
|
||||
*/
|
||||
/**
|
||||
* Gets fired when the mouse enters the area close to the point.
|
||||
*
|
||||
* @callback Highcharts.PointMouseOverCallbackFunction
|
||||
*
|
||||
* @param {Highcharts.Point} this
|
||||
* Point where the event occured.
|
||||
*
|
||||
* @param {global.Event} event
|
||||
* Event that occured.
|
||||
*/
|
||||
/**
|
||||
* The generic point options for all series.
|
||||
*
|
||||
* In TypeScript you have to extend `PointOptionsObject` with an additional
|
||||
* declaration to allow custom data options:
|
||||
*
|
||||
* ```
|
||||
* declare interface PointOptionsObject {
|
||||
* customProperty: string;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @interface Highcharts.PointOptionsObject
|
||||
*/
|
||||
/**
|
||||
* Possible option types for a data point.
|
||||
*
|
||||
* @typedef {number|string|Array<(number|string)>|Highcharts.PointOptionsObject|null} Highcharts.PointOptionsType
|
||||
*/
|
||||
/**
|
||||
* Gets fired when the point is removed using the `.remove()` method.
|
||||
*
|
||||
* @callback Highcharts.PointRemoveCallbackFunction
|
||||
*
|
||||
* @param {Highcharts.Point} this
|
||||
* Point where the event occured.
|
||||
*
|
||||
* @param {global.Event} event
|
||||
* Event that occured.
|
||||
*/
|
||||
/**
|
||||
* Possible key values for the point state options.
|
||||
*
|
||||
* @typedef {"hover"|"inactive"|"normal"|"select"} Highcharts.PointStateValue
|
||||
*/
|
||||
/**
|
||||
* Gets fired when the point is updated programmatically through the `.update()`
|
||||
* method.
|
||||
*
|
||||
* @callback Highcharts.PointUpdateCallbackFunction
|
||||
*
|
||||
* @param {Highcharts.Point} this
|
||||
* Point where the event occured.
|
||||
*
|
||||
* @param {Highcharts.PointUpdateEventObject} event
|
||||
* Event that occured.
|
||||
*/
|
||||
/**
|
||||
* Information about the update event.
|
||||
*
|
||||
* @interface Highcharts.PointUpdateEventObject
|
||||
* @extends global.Event
|
||||
*/ /**
|
||||
* Options data of the update event.
|
||||
* @name Highcharts.PointUpdateEventObject#options
|
||||
* @type {Highcharts.PointOptionsType}
|
||||
*/
|
||||
''; // detach doclet above
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* The Point object. The point objects are generated from the `series.data`
|
||||
* configuration objects or raw numbers. They can be accessed from the
|
||||
* `Series.points` array. Other ways to instantiate points are through {@link
|
||||
* Highcharts.Series#addPoint} or {@link Highcharts.Series#setData}.
|
||||
*
|
||||
* @class
|
||||
* @name Highcharts.Point
|
||||
*/
|
||||
var Point = /** @class */ (function () {
|
||||
function Point() {
|
||||
/* *
|
||||
*
|
||||
* Properties
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* For categorized axes this property holds the category name for the
|
||||
* point. For other axes it holds the X value.
|
||||
*
|
||||
* @name Highcharts.Point#category
|
||||
* @type {string}
|
||||
*/
|
||||
this.category = void 0;
|
||||
/**
|
||||
* The point's current color index, used in styled mode instead of
|
||||
* `color`. The color index is inserted in class names used for styling.
|
||||
*
|
||||
* @name Highcharts.Point#colorIndex
|
||||
* @type {number}
|
||||
*/
|
||||
this.colorIndex = void 0;
|
||||
this.formatPrefix = 'point';
|
||||
this.id = void 0;
|
||||
this.isNull = false;
|
||||
/**
|
||||
* The name of the point. The name can be given as the first position of the
|
||||
* point configuration array, or as a `name` property in the configuration:
|
||||
*
|
||||
* @example
|
||||
* // Array config
|
||||
* data: [
|
||||
* ['John', 1],
|
||||
* ['Jane', 2]
|
||||
* ]
|
||||
*
|
||||
* // Object config
|
||||
* data: [{
|
||||
* name: 'John',
|
||||
* y: 1
|
||||
* }, {
|
||||
* name: 'Jane',
|
||||
* y: 2
|
||||
* }]
|
||||
*
|
||||
* @name Highcharts.Point#name
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = void 0;
|
||||
/**
|
||||
* The point's options as applied in the initial configuration, or
|
||||
* extended through `Point.update`.
|
||||
*
|
||||
* In TypeScript you have to extend `PointOptionsObject` via an
|
||||
* additional interface to allow custom data options:
|
||||
*
|
||||
* ```
|
||||
* declare interface PointOptionsObject {
|
||||
* customProperty: string;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @name Highcharts.Point#options
|
||||
* @type {Highcharts.PointOptionsObject}
|
||||
*/
|
||||
this.options = void 0;
|
||||
/**
|
||||
* The percentage for points in a stacked series or pies.
|
||||
*
|
||||
* @name Highcharts.Point#percentage
|
||||
* @type {number|undefined}
|
||||
*/
|
||||
this.percentage = void 0;
|
||||
this.selected = false;
|
||||
/**
|
||||
* The series object associated with the point.
|
||||
*
|
||||
* @name Highcharts.Point#series
|
||||
* @type {Highcharts.Series}
|
||||
*/
|
||||
this.series = void 0;
|
||||
/**
|
||||
* The total of values in either a stack for stacked series, or a pie in a
|
||||
* pie series.
|
||||
*
|
||||
* @name Highcharts.Point#total
|
||||
* @type {number|undefined}
|
||||
*/
|
||||
this.total = void 0;
|
||||
/**
|
||||
* For certain series types, like pie charts, where individual points can
|
||||
* be shown or hidden.
|
||||
*
|
||||
* @name Highcharts.Point#visible
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
this.visible = true;
|
||||
this.x = void 0;
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Animate SVG elements associated with the point.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Point#animateBeforeDestroy
|
||||
*/
|
||||
Point.prototype.animateBeforeDestroy = function () {
|
||||
var point = this, animateParams = { x: point.startXPos, opacity: 0 }, isDataLabel, graphicalProps = point.getGraphicalProps();
|
||||
graphicalProps.singular.forEach(function (prop) {
|
||||
isDataLabel = prop === 'dataLabel';
|
||||
point[prop] = point[prop].animate(isDataLabel ? {
|
||||
x: point[prop].startXPos,
|
||||
y: point[prop].startYPos,
|
||||
opacity: 0
|
||||
} : animateParams);
|
||||
});
|
||||
graphicalProps.plural.forEach(function (plural) {
|
||||
point[plural].forEach(function (item) {
|
||||
if (item.element) {
|
||||
item.animate(extend({ x: point.startXPos }, (item.startYPos ? {
|
||||
x: item.startXPos,
|
||||
y: item.startYPos
|
||||
} : {})));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Apply the options containing the x and y data and possible some extra
|
||||
* properties. Called on point init or from point.update.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Point#applyOptions
|
||||
*
|
||||
* @param {Highcharts.PointOptionsType} options
|
||||
* The point options as defined in series.data.
|
||||
*
|
||||
* @param {number} [x]
|
||||
* Optionally, the x value.
|
||||
*
|
||||
* @return {Highcharts.Point}
|
||||
* The Point instance.
|
||||
*/
|
||||
Point.prototype.applyOptions = function (options, x) {
|
||||
var point = this, series = point.series, pointValKey = series.options.pointValKey || series.pointValKey;
|
||||
options = Point.prototype.optionsToObject.call(this, options);
|
||||
// copy options directly to point
|
||||
extend(point, options);
|
||||
point.options = point.options ? extend(point.options, options) : options;
|
||||
// Since options are copied into the Point instance, some accidental
|
||||
// options must be shielded (#5681)
|
||||
if (options.group) {
|
||||
delete point.group;
|
||||
}
|
||||
if (options.dataLabels) {
|
||||
delete point.dataLabels;
|
||||
}
|
||||
/**
|
||||
* The y value of the point.
|
||||
* @name Highcharts.Point#y
|
||||
* @type {number|undefined}
|
||||
*/
|
||||
// For higher dimension series types. For instance, for ranges, point.y
|
||||
// is mapped to point.low.
|
||||
if (pointValKey) {
|
||||
point.y = Point.prototype.getNestedProperty.call(point, pointValKey);
|
||||
}
|
||||
point.isNull = pick(point.isValid && !point.isValid(), point.x === null || !isNumber(point.y)); // #3571, check for NaN
|
||||
point.formatPrefix = point.isNull ? 'null' : 'point'; // #9233, #10874
|
||||
// The point is initially selected by options (#5777)
|
||||
if (point.selected) {
|
||||
point.state = 'select';
|
||||
}
|
||||
/**
|
||||
* The x value of the point.
|
||||
* @name Highcharts.Point#x
|
||||
* @type {number}
|
||||
*/
|
||||
// If no x is set by now, get auto incremented value. All points must
|
||||
// have an x value, however the y value can be null to create a gap in
|
||||
// the series
|
||||
if ('name' in point &&
|
||||
typeof x === 'undefined' &&
|
||||
series.xAxis &&
|
||||
series.xAxis.hasNames) {
|
||||
point.x = series.xAxis.nameToX(point);
|
||||
}
|
||||
if (typeof point.x === 'undefined' && series) {
|
||||
if (typeof x === 'undefined') {
|
||||
point.x = series.autoIncrement(point);
|
||||
}
|
||||
else {
|
||||
point.x = x;
|
||||
}
|
||||
}
|
||||
return point;
|
||||
};
|
||||
/**
|
||||
* Destroy a point to clear memory. Its reference still stays in
|
||||
* `series.data`.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Point#destroy
|
||||
*/
|
||||
Point.prototype.destroy = function () {
|
||||
var point = this, series = point.series, chart = series.chart, dataSorting = series.options.dataSorting, hoverPoints = chart.hoverPoints, globalAnimation = point.series.chart.renderer.globalAnimation, animation = animObject(globalAnimation), prop;
|
||||
/**
|
||||
* Allow to call after animation.
|
||||
* @private
|
||||
*/
|
||||
function destroyPoint() {
|
||||
// Remove all events and elements
|
||||
if (point.graphic || point.dataLabel || point.dataLabels) {
|
||||
removeEvent(point);
|
||||
point.destroyElements();
|
||||
}
|
||||
for (prop in point) { // eslint-disable-line guard-for-in
|
||||
point[prop] = null;
|
||||
}
|
||||
}
|
||||
if (point.legendItem) { // pies have legend items
|
||||
chart.legend.destroyItem(point);
|
||||
}
|
||||
if (hoverPoints) {
|
||||
point.setState();
|
||||
erase(hoverPoints, point);
|
||||
if (!hoverPoints.length) {
|
||||
chart.hoverPoints = null;
|
||||
}
|
||||
}
|
||||
if (point === chart.hoverPoint) {
|
||||
point.onMouseOut();
|
||||
}
|
||||
// Remove properties after animation
|
||||
if (!dataSorting || !dataSorting.enabled) {
|
||||
destroyPoint();
|
||||
}
|
||||
else {
|
||||
this.animateBeforeDestroy();
|
||||
syncTimeout(destroyPoint, animation.duration);
|
||||
}
|
||||
chart.pointCount--;
|
||||
};
|
||||
/**
|
||||
* Destroy SVG elements associated with the point.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Point#destroyElements
|
||||
* @param {Highcharts.Dictionary<number>} [kinds]
|
||||
*/
|
||||
Point.prototype.destroyElements = function (kinds) {
|
||||
var point = this, props = point.getGraphicalProps(kinds);
|
||||
props.singular.forEach(function (prop) {
|
||||
point[prop] = point[prop].destroy();
|
||||
});
|
||||
props.plural.forEach(function (plural) {
|
||||
point[plural].forEach(function (item) {
|
||||
if (item.element) {
|
||||
item.destroy();
|
||||
}
|
||||
});
|
||||
delete point[plural];
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Fire an event on the Point object.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Point#firePointEvent
|
||||
*
|
||||
* @param {string} eventType
|
||||
* Type of the event.
|
||||
*
|
||||
* @param {Highcharts.Dictionary<any>|Event} [eventArgs]
|
||||
* Additional event arguments.
|
||||
*
|
||||
* @param {Highcharts.EventCallbackFunction<Highcharts.Point>|Function} [defaultFunction]
|
||||
* Default event handler.
|
||||
*
|
||||
* @fires Highcharts.Point#event:*
|
||||
*/
|
||||
Point.prototype.firePointEvent = function (eventType, eventArgs, defaultFunction) {
|
||||
var point = this, series = this.series, seriesOptions = series.options;
|
||||
// load event handlers on demand to save time on mouseover/out
|
||||
if (seriesOptions.point.events[eventType] ||
|
||||
(point.options &&
|
||||
point.options.events &&
|
||||
point.options.events[eventType])) {
|
||||
point.importEvents();
|
||||
}
|
||||
// add default handler if in selection mode
|
||||
if (eventType === 'click' && seriesOptions.allowPointSelect) {
|
||||
defaultFunction = function (event) {
|
||||
// Control key is for Windows, meta (= Cmd key) for Mac, Shift
|
||||
// for Opera.
|
||||
if (point.select) { // #2911
|
||||
point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
|
||||
}
|
||||
};
|
||||
}
|
||||
fireEvent(point, eventType, eventArgs, defaultFunction);
|
||||
};
|
||||
/**
|
||||
* Get the CSS class names for individual points. Used internally where the
|
||||
* returned value is set on every point.
|
||||
*
|
||||
* @function Highcharts.Point#getClassName
|
||||
*
|
||||
* @return {string}
|
||||
* The class names.
|
||||
*/
|
||||
Point.prototype.getClassName = function () {
|
||||
var point = this;
|
||||
return 'highcharts-point' +
|
||||
(point.selected ? ' highcharts-point-select' : '') +
|
||||
(point.negative ? ' highcharts-negative' : '') +
|
||||
(point.isNull ? ' highcharts-null-point' : '') +
|
||||
(typeof point.colorIndex !== 'undefined' ?
|
||||
' highcharts-color-' + point.colorIndex : '') +
|
||||
(point.options.className ? ' ' + point.options.className : '') +
|
||||
(point.zone && point.zone.className ? ' ' +
|
||||
point.zone.className.replace('highcharts-negative', '') : '');
|
||||
};
|
||||
/**
|
||||
* Get props of all existing graphical point elements.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Point#getGraphicalProps
|
||||
* @param {Highcharts.Dictionary<number>} [kinds]
|
||||
* @return {Highcharts.PointGraphicalProps}
|
||||
*/
|
||||
Point.prototype.getGraphicalProps = function (kinds) {
|
||||
var point = this, props = [], prop, i, graphicalProps = { singular: [], plural: [] };
|
||||
kinds = kinds || { graphic: 1, dataLabel: 1 };
|
||||
if (kinds.graphic) {
|
||||
props.push('graphic', 'shadowGroup');
|
||||
}
|
||||
if (kinds.dataLabel) {
|
||||
props.push('dataLabel', 'dataLabelUpper', 'connector');
|
||||
}
|
||||
i = props.length;
|
||||
while (i--) {
|
||||
prop = props[i];
|
||||
if (point[prop]) {
|
||||
graphicalProps.singular.push(prop);
|
||||
}
|
||||
}
|
||||
['dataLabel', 'connector'].forEach(function (prop) {
|
||||
var plural = prop + 's';
|
||||
if (kinds[prop] && point[plural]) {
|
||||
graphicalProps.plural.push(plural);
|
||||
}
|
||||
});
|
||||
return graphicalProps;
|
||||
};
|
||||
/**
|
||||
* Return the configuration hash needed for the data label and tooltip
|
||||
* formatters.
|
||||
*
|
||||
* @function Highcharts.Point#getLabelConfig
|
||||
*
|
||||
* @return {Highcharts.PointLabelObject}
|
||||
* Abstract object used in formatters and formats.
|
||||
*/
|
||||
Point.prototype.getLabelConfig = function () {
|
||||
return {
|
||||
x: this.category,
|
||||
y: this.y,
|
||||
color: this.color,
|
||||
colorIndex: this.colorIndex,
|
||||
key: this.name || this.category,
|
||||
series: this.series,
|
||||
point: this,
|
||||
percentage: this.percentage,
|
||||
total: this.total || this.stackTotal
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Returns the value of the point property for a given value.
|
||||
* @private
|
||||
*/
|
||||
Point.prototype.getNestedProperty = function (key) {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
if (key.indexOf('custom.') === 0) {
|
||||
return getNestedProperty(key, this.options);
|
||||
}
|
||||
return this[key];
|
||||
};
|
||||
/**
|
||||
* In a series with `zones`, return the zone that the point belongs to.
|
||||
*
|
||||
* @function Highcharts.Point#getZone
|
||||
*
|
||||
* @return {Highcharts.SeriesZonesOptionsObject}
|
||||
* The zone item.
|
||||
*/
|
||||
Point.prototype.getZone = function () {
|
||||
var series = this.series, zones = series.zones, zoneAxis = series.zoneAxis || 'y', i = 0, zone;
|
||||
zone = zones[i];
|
||||
while (this[zoneAxis] >= zone.value) {
|
||||
zone = zones[++i];
|
||||
}
|
||||
// For resetting or reusing the point (#8100)
|
||||
if (!this.nonZonedColor) {
|
||||
this.nonZonedColor = this.color;
|
||||
}
|
||||
if (zone && zone.color && !this.options.color) {
|
||||
this.color = zone.color;
|
||||
}
|
||||
else {
|
||||
this.color = this.nonZonedColor;
|
||||
}
|
||||
return zone;
|
||||
};
|
||||
/**
|
||||
* Utility to check if point has new shape type. Used in column series and
|
||||
* all others that are based on column series.
|
||||
*
|
||||
* @return boolean|undefined
|
||||
*/
|
||||
Point.prototype.hasNewShapeType = function () {
|
||||
var point = this;
|
||||
var oldShapeType = point.graphic &&
|
||||
(point.graphic.symbolName || point.graphic.element.nodeName);
|
||||
return oldShapeType !== this.shapeType;
|
||||
};
|
||||
/**
|
||||
* Initialize the point. Called internally based on the `series.data`
|
||||
* option.
|
||||
*
|
||||
* @function Highcharts.Point#init
|
||||
*
|
||||
* @param {Highcharts.Series} series
|
||||
* The series object containing this point.
|
||||
*
|
||||
* @param {Highcharts.PointOptionsType} options
|
||||
* The data in either number, array or object format.
|
||||
*
|
||||
* @param {number} [x]
|
||||
* Optionally, the X value of the point.
|
||||
*
|
||||
* @return {Highcharts.Point}
|
||||
* The Point instance.
|
||||
*
|
||||
* @fires Highcharts.Point#event:afterInit
|
||||
*/
|
||||
Point.prototype.init = function (series, options, x) {
|
||||
this.series = series;
|
||||
this.applyOptions(options, x);
|
||||
// Add a unique ID to the point if none is assigned
|
||||
this.id = defined(this.id) ? this.id : uniqueKey();
|
||||
this.resolveColor();
|
||||
series.chart.pointCount++;
|
||||
fireEvent(this, 'afterInit');
|
||||
return this;
|
||||
};
|
||||
/**
|
||||
* Transform number or array configs into objects. Also called for object
|
||||
* configs. Used internally to unify the different configuration formats for
|
||||
* points. For example, a simple number `10` in a line series will be
|
||||
* transformed to `{ y: 10 }`, and an array config like `[1, 10]` in a
|
||||
* scatter series will be transformed to `{ x: 1, y: 10 }`.
|
||||
*
|
||||
* @function Highcharts.Point#optionsToObject
|
||||
*
|
||||
* @param {Highcharts.PointOptionsType} options
|
||||
* The input option.
|
||||
*
|
||||
* @return {Highcharts.Dictionary<*>}
|
||||
* Transformed options.
|
||||
*/
|
||||
Point.prototype.optionsToObject = function (options) {
|
||||
var ret = {}, series = this.series, keys = series.options.keys, pointArrayMap = keys || series.pointArrayMap || ['y'], valueCount = pointArrayMap.length, firstItemType, i = 0, j = 0;
|
||||
if (isNumber(options) || options === null) {
|
||||
ret[pointArrayMap[0]] = options;
|
||||
}
|
||||
else if (isArray(options)) {
|
||||
// with leading x value
|
||||
if (!keys && options.length > valueCount) {
|
||||
firstItemType = typeof options[0];
|
||||
if (firstItemType === 'string') {
|
||||
ret.name = options[0];
|
||||
}
|
||||
else if (firstItemType === 'number') {
|
||||
ret.x = options[0];
|
||||
}
|
||||
i++;
|
||||
}
|
||||
while (j < valueCount) {
|
||||
// Skip undefined positions for keys
|
||||
if (!keys || typeof options[i] !== 'undefined') {
|
||||
if (pointArrayMap[j].indexOf('.') > 0) {
|
||||
// Handle nested keys, e.g. ['color.pattern.image']
|
||||
// Avoid function call unless necessary.
|
||||
Point.prototype.setNestedProperty(ret, options[i], pointArrayMap[j]);
|
||||
}
|
||||
else {
|
||||
ret[pointArrayMap[j]] = options[i];
|
||||
}
|
||||
}
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
else if (typeof options === 'object') {
|
||||
ret = options;
|
||||
// This is the fastest way to detect if there are individual point
|
||||
// dataLabels that need to be considered in drawDataLabels. These
|
||||
// can only occur in object configs.
|
||||
if (options.dataLabels) {
|
||||
series._hasPointLabels = true;
|
||||
}
|
||||
// Same approach as above for markers
|
||||
if (options.marker) {
|
||||
series._hasPointMarkers = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @function Highcharts.Point#resolveColor
|
||||
* @return {void}
|
||||
*/
|
||||
Point.prototype.resolveColor = function () {
|
||||
var series = this.series, colors, optionsChart = series.chart.options.chart, colorCount = optionsChart.colorCount, styledMode = series.chart.styledMode, colorIndex;
|
||||
// remove points nonZonedColor for later recalculation
|
||||
delete this.nonZonedColor;
|
||||
/**
|
||||
* The point's current color.
|
||||
*
|
||||
* @name Highcharts.Point#color
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined}
|
||||
*/
|
||||
if (!styledMode && !this.options.color) {
|
||||
this.color = series.color; // #3445
|
||||
}
|
||||
if (series.options.colorByPoint) {
|
||||
if (!styledMode) {
|
||||
colors = series.options.colors || series.chart.options.colors;
|
||||
this.color = this.color || colors[series.colorCounter];
|
||||
colorCount = colors.length;
|
||||
}
|
||||
colorIndex = series.colorCounter;
|
||||
series.colorCounter++;
|
||||
// loop back to zero
|
||||
if (series.colorCounter === colorCount) {
|
||||
series.colorCounter = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
colorIndex = series.colorIndex;
|
||||
}
|
||||
this.colorIndex = pick(this.colorIndex, colorIndex);
|
||||
};
|
||||
/**
|
||||
* Set a value in an object, on the property defined by key. The key
|
||||
* supports nested properties using dot notation. The function modifies the
|
||||
* input object and does not make a copy.
|
||||
*
|
||||
* @function Highcharts.Point#setNestedProperty<T>
|
||||
*
|
||||
* @param {T} object
|
||||
* The object to set the value on.
|
||||
*
|
||||
* @param {*} value
|
||||
* The value to set.
|
||||
*
|
||||
* @param {string} key
|
||||
* Key to the property to set.
|
||||
*
|
||||
* @return {T}
|
||||
* The modified object.
|
||||
*/
|
||||
Point.prototype.setNestedProperty = function (object, value, key) {
|
||||
var nestedKeys = key.split('.');
|
||||
nestedKeys.reduce(function (result, key, i, arr) {
|
||||
var isLastKey = arr.length - 1 === i;
|
||||
result[key] = (isLastKey ?
|
||||
value :
|
||||
isObject(result[key], true) ?
|
||||
result[key] :
|
||||
{});
|
||||
return result[key];
|
||||
}, object);
|
||||
return object;
|
||||
};
|
||||
/**
|
||||
* Extendable method for formatting each point's tooltip line.
|
||||
*
|
||||
* @function Highcharts.Point#tooltipFormatter
|
||||
*
|
||||
* @param {string} pointFormat
|
||||
* The point format.
|
||||
*
|
||||
* @return {string}
|
||||
* A string to be concatenated in to the common tooltip text.
|
||||
*/
|
||||
Point.prototype.tooltipFormatter = function (pointFormat) {
|
||||
// Insert options for valueDecimals, valuePrefix, and valueSuffix
|
||||
var series = this.series, seriesTooltipOptions = series.tooltipOptions, valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''), valuePrefix = seriesTooltipOptions.valuePrefix || '', valueSuffix = seriesTooltipOptions.valueSuffix || '';
|
||||
// Replace default point style with class name
|
||||
if (series.chart.styledMode) {
|
||||
pointFormat =
|
||||
series.chart.tooltip.styledModeFormat(pointFormat);
|
||||
}
|
||||
// Loop over the point array map and replace unformatted values with
|
||||
// sprintf formatting markup
|
||||
(series.pointArrayMap || ['y']).forEach(function (key) {
|
||||
key = '{point.' + key; // without the closing bracket
|
||||
if (valuePrefix || valueSuffix) {
|
||||
pointFormat = pointFormat.replace(RegExp(key + '}', 'g'), valuePrefix + key + '}' + valueSuffix);
|
||||
}
|
||||
pointFormat = pointFormat.replace(RegExp(key + '}', 'g'), key + ':,.' + valueDecimals + 'f}');
|
||||
});
|
||||
return format(pointFormat, {
|
||||
point: this,
|
||||
series: this.series
|
||||
}, series.chart);
|
||||
};
|
||||
return Point;
|
||||
}());
|
||||
H.Point = Point;
|
||||
export default Point;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* Extension to the Series object in 3D charts.
|
||||
*
|
||||
* 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;
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, pick = U.pick;
|
||||
import './Series.js';
|
||||
/* eslint-disable no-invalid-this */
|
||||
// Wrap the translate method to post-translate points into 3D perspective
|
||||
addEvent(H.Series, 'afterTranslate', function () {
|
||||
if (this.chart.is3d()) {
|
||||
this.translate3dPoints();
|
||||
}
|
||||
});
|
||||
// Translate the plotX, plotY properties and add plotZ.
|
||||
H.Series.prototype.translate3dPoints = function () {
|
||||
var series = this, chart = series.chart, zAxis = pick(series.zAxis, chart.options.zAxis[0]), rawPoints = [], rawPoint, projectedPoints, projectedPoint, zValue, i;
|
||||
for (i = 0; i < series.data.length; i++) {
|
||||
rawPoint = series.data[i];
|
||||
if (zAxis && zAxis.translate) {
|
||||
zValue = zAxis.logarithmic && zAxis.val2lin ?
|
||||
zAxis.val2lin(rawPoint.z) :
|
||||
rawPoint.z; // #4562
|
||||
rawPoint.plotZ = zAxis.translate(zValue);
|
||||
rawPoint.isInside = rawPoint.isInside ?
|
||||
(zValue >= zAxis.min &&
|
||||
zValue <= zAxis.max) :
|
||||
false;
|
||||
}
|
||||
else {
|
||||
rawPoint.plotZ = 0;
|
||||
}
|
||||
rawPoint.axisXpos = rawPoint.plotX;
|
||||
rawPoint.axisYpos = rawPoint.plotY;
|
||||
rawPoint.axisZpos = rawPoint.plotZ;
|
||||
rawPoints.push({
|
||||
x: rawPoint.plotX,
|
||||
y: rawPoint.plotY,
|
||||
z: rawPoint.plotZ
|
||||
});
|
||||
}
|
||||
projectedPoints = perspective(rawPoints, chart, true);
|
||||
for (i = 0; i < series.data.length; i++) {
|
||||
rawPoint = series.data[i];
|
||||
projectedPoint = projectedPoints[i];
|
||||
rawPoint.plotX = projectedPoint.x;
|
||||
rawPoint.plotY = projectedPoint.y;
|
||||
rawPoint.plotZ = projectedPoint.z;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,656 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Highcharts from './Globals.js';
|
||||
/**
|
||||
* Normalized interval.
|
||||
*
|
||||
* @interface Highcharts.TimeNormalizedObject
|
||||
*/ /**
|
||||
* The count.
|
||||
*
|
||||
* @name Highcharts.TimeNormalizedObject#count
|
||||
* @type {number}
|
||||
*/ /**
|
||||
* The interval in axis values (ms).
|
||||
*
|
||||
* @name Highcharts.TimeNormalizedObject#unitRange
|
||||
* @type {number}
|
||||
*/
|
||||
/**
|
||||
* Function of an additional date format specifier.
|
||||
*
|
||||
* @callback Highcharts.TimeFormatCallbackFunction
|
||||
*
|
||||
* @param {number} timestamp
|
||||
* The time to format.
|
||||
*
|
||||
* @return {string}
|
||||
* The formatted portion of the date.
|
||||
*/
|
||||
/**
|
||||
* Additonal time tick information.
|
||||
*
|
||||
* @interface Highcharts.TimeTicksInfoObject
|
||||
* @extends Highcharts.TimeNormalizedObject
|
||||
*/ /**
|
||||
* @name Highcharts.TimeTicksInfoObject#higherRanks
|
||||
* @type {Array<string>}
|
||||
*/ /**
|
||||
* @name Highcharts.TimeTicksInfoObject#totalRange
|
||||
* @type {number}
|
||||
*/
|
||||
/**
|
||||
* Time ticks.
|
||||
*
|
||||
* @interface Highcharts.AxisTickPositionsArray
|
||||
* @extends global.Array<number>
|
||||
*/ /**
|
||||
* @name Highcharts.AxisTickPositionsArray#info
|
||||
* @type {Highcharts.TimeTicksInfoObject|undefined}
|
||||
*/
|
||||
/**
|
||||
* A callback to return the time zone offset for a given datetime. It
|
||||
* takes the timestamp in terms of milliseconds since January 1 1970,
|
||||
* and returns the timezone offset in minutes. This provides a hook
|
||||
* for drawing time based charts in specific time zones using their
|
||||
* local DST crossover dates, with the help of external libraries.
|
||||
*
|
||||
* @callback Highcharts.TimezoneOffsetCallbackFunction
|
||||
*
|
||||
* @param {number} timestamp
|
||||
* Timestamp in terms of milliseconds since January 1 1970.
|
||||
*
|
||||
* @return {number}
|
||||
* Timezone offset in minutes.
|
||||
*/
|
||||
/**
|
||||
* Allows to manually load the `moment.js` library from Highcharts options
|
||||
* instead of the `window`.
|
||||
* In case of loading the library from a `script` tag,
|
||||
* this option is not needed, it will be loaded from there by default.
|
||||
*
|
||||
* @type {function}
|
||||
* @since 8.2.0
|
||||
* @apioption time.moment
|
||||
*/
|
||||
import U from './Utilities.js';
|
||||
var defined = U.defined, error = U.error, extend = U.extend, isObject = U.isObject, merge = U.merge, objectEach = U.objectEach, pad = U.pad, pick = U.pick, splat = U.splat, timeUnits = U.timeUnits;
|
||||
var H = Highcharts, win = H.win;
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* The Time class. Time settings are applied in general for each page using
|
||||
* `Highcharts.setOptions`, or individually for each Chart item through the
|
||||
* [time](https://api.highcharts.com/highcharts/time) options set.
|
||||
*
|
||||
* The Time object is available from {@link Highcharts.Chart#time},
|
||||
* which refers to `Highcharts.time` if no individual time settings are
|
||||
* applied.
|
||||
*
|
||||
* @example
|
||||
* // Apply time settings globally
|
||||
* Highcharts.setOptions({
|
||||
* time: {
|
||||
* timezone: 'Europe/London'
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* // Apply time settings by instance
|
||||
* var chart = Highcharts.chart('container', {
|
||||
* time: {
|
||||
* timezone: 'America/New_York'
|
||||
* },
|
||||
* series: [{
|
||||
* data: [1, 4, 3, 5]
|
||||
* }]
|
||||
* });
|
||||
*
|
||||
* // Use the Time object
|
||||
* console.log(
|
||||
* 'Current time in New York',
|
||||
* chart.time.dateFormat('%Y-%m-%d %H:%M:%S', Date.now())
|
||||
* );
|
||||
*
|
||||
* @since 6.0.5
|
||||
*
|
||||
* @class
|
||||
* @name Highcharts.Time
|
||||
*
|
||||
* @param {Highcharts.TimeOptions} options
|
||||
* Time options as defined in [chart.options.time](/highcharts/time).
|
||||
*/
|
||||
var Time = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function Time(options) {
|
||||
/* *
|
||||
*
|
||||
* Properties
|
||||
*
|
||||
* */
|
||||
this.options = {};
|
||||
this.useUTC = false;
|
||||
this.variableTimezone = false;
|
||||
this.Date = win.Date;
|
||||
/**
|
||||
* Get the time zone offset based on the current timezone information as
|
||||
* set in the global options.
|
||||
*
|
||||
* @function Highcharts.Time#getTimezoneOffset
|
||||
*
|
||||
* @param {number} timestamp
|
||||
* The JavaScript timestamp to inspect.
|
||||
*
|
||||
* @return {number}
|
||||
* The timezone offset in minutes compared to UTC.
|
||||
*/
|
||||
this.getTimezoneOffset = this.timezoneOffsetFunction();
|
||||
this.update(options);
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Time units used in `Time.get` and `Time.set`
|
||||
*
|
||||
* @typedef {"Date"|"Day"|"FullYear"|"Hours"|"Milliseconds"|"Minutes"|"Month"|"Seconds"} Highcharts.TimeUnitValue
|
||||
*/
|
||||
/**
|
||||
* Get the value of a date object in given units, and subject to the Time
|
||||
* object's current timezone settings. This function corresponds directly to
|
||||
* JavaScripts `Date.getXXX / Date.getUTCXXX`, so instead of calling
|
||||
* `date.getHours()` or `date.getUTCHours()` we will call
|
||||
* `time.get('Hours')`.
|
||||
*
|
||||
* @function Highcharts.Time#get
|
||||
*
|
||||
* @param {Highcharts.TimeUnitValue} unit
|
||||
* @param {Date} date
|
||||
*
|
||||
* @return {number}
|
||||
* The given time unit
|
||||
*/
|
||||
Time.prototype.get = function (unit, date) {
|
||||
if (this.variableTimezone || this.timezoneOffset) {
|
||||
var realMs = date.getTime();
|
||||
var ms = realMs - this.getTimezoneOffset(date);
|
||||
date.setTime(ms); // Temporary adjust to timezone
|
||||
var ret = date['getUTC' + unit]();
|
||||
date.setTime(realMs); // Reset
|
||||
return ret;
|
||||
}
|
||||
// UTC time with no timezone handling
|
||||
if (this.useUTC) {
|
||||
return date['getUTC' + unit]();
|
||||
}
|
||||
// Else, local time
|
||||
return date['get' + unit]();
|
||||
};
|
||||
/**
|
||||
* Set the value of a date object in given units, and subject to the Time
|
||||
* object's current timezone settings. This function corresponds directly to
|
||||
* JavaScripts `Date.setXXX / Date.setUTCXXX`, so instead of calling
|
||||
* `date.setHours(0)` or `date.setUTCHours(0)` we will call
|
||||
* `time.set('Hours', 0)`.
|
||||
*
|
||||
* @function Highcharts.Time#set
|
||||
*
|
||||
* @param {Highcharts.TimeUnitValue} unit
|
||||
* @param {Date} date
|
||||
* @param {number} value
|
||||
*
|
||||
* @return {number}
|
||||
* The epoch milliseconds of the updated date
|
||||
*/
|
||||
Time.prototype.set = function (unit, date, value) {
|
||||
// UTC time with timezone handling
|
||||
if (this.variableTimezone || this.timezoneOffset) {
|
||||
// For lower order time units, just set it directly using UTC
|
||||
// time
|
||||
if (unit === 'Milliseconds' ||
|
||||
unit === 'Seconds' ||
|
||||
unit === 'Minutes') {
|
||||
return date['setUTC' + unit](value);
|
||||
}
|
||||
// Higher order time units need to take the time zone into
|
||||
// account
|
||||
// Adjust by timezone
|
||||
var offset = this.getTimezoneOffset(date);
|
||||
var ms = date.getTime() - offset;
|
||||
date.setTime(ms);
|
||||
date['setUTC' + unit](value);
|
||||
var newOffset = this.getTimezoneOffset(date);
|
||||
ms = date.getTime() + newOffset;
|
||||
return date.setTime(ms);
|
||||
}
|
||||
// UTC time with no timezone handling
|
||||
if (this.useUTC) {
|
||||
return date['setUTC' + unit](value);
|
||||
}
|
||||
// Else, local time
|
||||
return date['set' + unit](value);
|
||||
};
|
||||
/**
|
||||
* Update the Time object with current options. It is called internally on
|
||||
* initializing Highcharts, after running `Highcharts.setOptions` and on
|
||||
* `Chart.update`.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Time#update
|
||||
*
|
||||
* @param {Highcharts.TimeOptions} options
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
Time.prototype.update = function (options) {
|
||||
var useUTC = pick(options && options.useUTC, true), time = this;
|
||||
this.options = options = merge(true, this.options || {}, options);
|
||||
// Allow using a different Date class
|
||||
this.Date = options.Date || win.Date || Date;
|
||||
this.useUTC = useUTC;
|
||||
this.timezoneOffset = (useUTC && options.timezoneOffset);
|
||||
this.getTimezoneOffset = this.timezoneOffsetFunction();
|
||||
/*
|
||||
* The time object has options allowing for variable time zones, meaning
|
||||
* the axis ticks or series data needs to consider this.
|
||||
*/
|
||||
this.variableTimezone = !!(!useUTC ||
|
||||
options.getTimezoneOffset ||
|
||||
options.timezone);
|
||||
};
|
||||
/**
|
||||
* Make a time and returns milliseconds. Interprets the inputs as UTC time,
|
||||
* local time or a specific timezone time depending on the current time
|
||||
* settings.
|
||||
*
|
||||
* @function Highcharts.Time#makeTime
|
||||
*
|
||||
* @param {number} year
|
||||
* The year
|
||||
*
|
||||
* @param {number} month
|
||||
* The month. Zero-based, so January is 0.
|
||||
*
|
||||
* @param {number} [date=1]
|
||||
* The day of the month
|
||||
*
|
||||
* @param {number} [hours=0]
|
||||
* The hour of the day, 0-23.
|
||||
*
|
||||
* @param {number} [minutes=0]
|
||||
* The minutes
|
||||
*
|
||||
* @param {number} [seconds=0]
|
||||
* The seconds
|
||||
*
|
||||
* @return {number}
|
||||
* The time in milliseconds since January 1st 1970.
|
||||
*/
|
||||
Time.prototype.makeTime = function (year, month, date, hours, minutes, seconds) {
|
||||
var d, offset, newOffset;
|
||||
if (this.useUTC) {
|
||||
d = this.Date.UTC.apply(0, arguments);
|
||||
offset = this.getTimezoneOffset(d);
|
||||
d += offset;
|
||||
newOffset = this.getTimezoneOffset(d);
|
||||
if (offset !== newOffset) {
|
||||
d += newOffset - offset;
|
||||
// A special case for transitioning from summer time to winter time.
|
||||
// When the clock is set back, the same time is repeated twice, i.e.
|
||||
// 02:30 am is repeated since the clock is set back from 3 am to
|
||||
// 2 am. We need to make the same time as local Date does.
|
||||
}
|
||||
else if (offset - 36e5 === this.getTimezoneOffset(d - 36e5) &&
|
||||
!H.isSafari) {
|
||||
d -= 36e5;
|
||||
}
|
||||
}
|
||||
else {
|
||||
d = new this.Date(year, month, pick(date, 1), pick(hours, 0), pick(minutes, 0), pick(seconds, 0)).getTime();
|
||||
}
|
||||
return d;
|
||||
};
|
||||
/**
|
||||
* Sets the getTimezoneOffset function. If the `timezone` option is set, a
|
||||
* default getTimezoneOffset function with that timezone is returned. If
|
||||
* a `getTimezoneOffset` option is defined, it is returned. If neither are
|
||||
* specified, the function using the `timezoneOffset` option or 0 offset is
|
||||
* returned.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Time#timezoneOffsetFunction
|
||||
*
|
||||
* @return {Function}
|
||||
* A getTimezoneOffset function
|
||||
*/
|
||||
Time.prototype.timezoneOffsetFunction = function () {
|
||||
var time = this, options = this.options, moment = options.moment || win.moment;
|
||||
if (!this.useUTC) {
|
||||
return function (timestamp) {
|
||||
return new Date(timestamp.toString()).getTimezoneOffset() * 60000;
|
||||
};
|
||||
}
|
||||
if (options.timezone) {
|
||||
if (!moment) {
|
||||
// getTimezoneOffset-function stays undefined because it depends
|
||||
// on Moment.js
|
||||
error(25);
|
||||
}
|
||||
else {
|
||||
return function (timestamp) {
|
||||
return -moment.tz(timestamp, options.timezone).utcOffset() * 60000;
|
||||
};
|
||||
}
|
||||
}
|
||||
// If not timezone is set, look for the getTimezoneOffset callback
|
||||
if (this.useUTC && options.getTimezoneOffset) {
|
||||
return function (timestamp) {
|
||||
return options.getTimezoneOffset(timestamp.valueOf()) * 60000;
|
||||
};
|
||||
}
|
||||
// Last, use the `timezoneOffset` option if set
|
||||
return function () {
|
||||
return (time.timezoneOffset || 0) * 60000;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Formats a JavaScript date timestamp (milliseconds since Jan 1st 1970)
|
||||
* into a human readable date string. The available format keys are listed
|
||||
* below. Additional formats can be given in the
|
||||
* {@link Highcharts.dateFormats} hook.
|
||||
*
|
||||
* Supported format keys:
|
||||
* - `%a`: Short weekday, like 'Mon'
|
||||
* - `%A`: Long weekday, like 'Monday'
|
||||
* - `%d`: Two digit day of the month, 01 to 31
|
||||
* - `%e`: Day of the month, 1 through 31
|
||||
* - `%w`: Day of the week, 0 through 6
|
||||
* - `%b`: Short month, like 'Jan'
|
||||
* - `%B`: Long month, like 'January'
|
||||
* - `%m`: Two digit month number, 01 through 12
|
||||
* - `%y`: Two digits year, like 09 for 2009
|
||||
* - `%Y`: Four digits year, like 2009
|
||||
* - `%H`: Two digits hours in 24h format, 00 through 23
|
||||
* - `%k`: Hours in 24h format, 0 through 23
|
||||
* - `%I`: Two digits hours in 12h format, 00 through 11
|
||||
* - `%l`: Hours in 12h format, 1 through 12
|
||||
* - `%M`: Two digits minutes, 00 through 59
|
||||
* - `%p`: Upper case AM or PM
|
||||
* - `%P`: Lower case AM or PM
|
||||
* - `%S`: Two digits seconds, 00 through 59
|
||||
* - `%L`: Milliseconds (naming from Ruby)
|
||||
*
|
||||
* @example
|
||||
* const time = new Highcharts.Time();
|
||||
* const s = time.dateFormat('%Y-%m-%d %H:%M:%S', Date.UTC(2020, 0, 1));
|
||||
* console.log(s); // => 2020-01-01 00:00:00
|
||||
*
|
||||
* @function Highcharts.Time#dateFormat
|
||||
*
|
||||
* @param {string} format
|
||||
* The desired format where various time representations are
|
||||
* prefixed with %.
|
||||
*
|
||||
* @param {number} timestamp
|
||||
* The JavaScript timestamp.
|
||||
*
|
||||
* @param {boolean} [capitalize=false]
|
||||
* Upper case first letter in the return.
|
||||
*
|
||||
* @return {string}
|
||||
* The formatted date.
|
||||
*/
|
||||
Time.prototype.dateFormat = function (format, timestamp, capitalize) {
|
||||
var _a;
|
||||
if (!defined(timestamp) || isNaN(timestamp)) {
|
||||
return ((_a = H.defaultOptions.lang) === null || _a === void 0 ? void 0 : _a.invalidDate) || '';
|
||||
}
|
||||
format = pick(format, '%Y-%m-%d %H:%M:%S');
|
||||
var time = this, date = new this.Date(timestamp),
|
||||
// get the basic time values
|
||||
hours = this.get('Hours', date), day = this.get('Day', date), dayOfMonth = this.get('Date', date), month = this.get('Month', date), fullYear = this.get('FullYear', date), lang = H.defaultOptions.lang, langWeekdays = lang === null || lang === void 0 ? void 0 : lang.weekdays, shortWeekdays = lang === null || lang === void 0 ? void 0 : lang.shortWeekdays,
|
||||
// List all format keys. Custom formats can be added from the
|
||||
// outside.
|
||||
replacements = extend({
|
||||
// Day
|
||||
// Short weekday, like 'Mon'
|
||||
a: shortWeekdays ?
|
||||
shortWeekdays[day] :
|
||||
langWeekdays[day].substr(0, 3),
|
||||
// Long weekday, like 'Monday'
|
||||
A: langWeekdays[day],
|
||||
// Two digit day of the month, 01 to 31
|
||||
d: pad(dayOfMonth),
|
||||
// Day of the month, 1 through 31
|
||||
e: pad(dayOfMonth, 2, ' '),
|
||||
// Day of the week, 0 through 6
|
||||
w: day,
|
||||
// Week (none implemented)
|
||||
// 'W': weekNumber(),
|
||||
// Month
|
||||
// Short month, like 'Jan'
|
||||
b: lang.shortMonths[month],
|
||||
// Long month, like 'January'
|
||||
B: lang.months[month],
|
||||
// Two digit month number, 01 through 12
|
||||
m: pad(month + 1),
|
||||
// Month number, 1 through 12 (#8150)
|
||||
o: month + 1,
|
||||
// Year
|
||||
// Two digits year, like 09 for 2009
|
||||
y: fullYear.toString().substr(2, 2),
|
||||
// Four digits year, like 2009
|
||||
Y: fullYear,
|
||||
// Time
|
||||
// Two digits hours in 24h format, 00 through 23
|
||||
H: pad(hours),
|
||||
// Hours in 24h format, 0 through 23
|
||||
k: hours,
|
||||
// Two digits hours in 12h format, 00 through 11
|
||||
I: pad((hours % 12) || 12),
|
||||
// Hours in 12h format, 1 through 12
|
||||
l: (hours % 12) || 12,
|
||||
// Two digits minutes, 00 through 59
|
||||
M: pad(this.get('Minutes', date)),
|
||||
// Upper case AM or PM
|
||||
p: hours < 12 ? 'AM' : 'PM',
|
||||
// Lower case AM or PM
|
||||
P: hours < 12 ? 'am' : 'pm',
|
||||
// Two digits seconds, 00 through 59
|
||||
S: pad(date.getSeconds()),
|
||||
// Milliseconds (naming from Ruby)
|
||||
L: pad(Math.floor(timestamp % 1000), 3)
|
||||
}, H.dateFormats);
|
||||
// Do the replaces
|
||||
objectEach(replacements, function (val, key) {
|
||||
// Regex would do it in one line, but this is faster
|
||||
while (format.indexOf('%' + key) !== -1) {
|
||||
format = format.replace('%' + key, typeof val === 'function' ? val.call(time, timestamp) : val);
|
||||
}
|
||||
});
|
||||
// Optionally capitalize the string and return
|
||||
return capitalize ?
|
||||
(format.substr(0, 1).toUpperCase() +
|
||||
format.substr(1)) :
|
||||
format;
|
||||
};
|
||||
/**
|
||||
* Resolve legacy formats of dateTimeLabelFormats (strings and arrays) into
|
||||
* an object.
|
||||
* @private
|
||||
* @param {string|Array<T>|Highcharts.Dictionary<T>} f - General format description
|
||||
* @return {Highcharts.Dictionary<T>} - The object definition
|
||||
*/
|
||||
Time.prototype.resolveDTLFormat = function (f) {
|
||||
if (!isObject(f, true)) { // check for string or array
|
||||
f = splat(f);
|
||||
return {
|
||||
main: f[0],
|
||||
from: f[1],
|
||||
to: f[2]
|
||||
};
|
||||
}
|
||||
return f;
|
||||
};
|
||||
/**
|
||||
* Return an array with time positions distributed on round time values
|
||||
* right and right after min and max. Used in datetime axes as well as for
|
||||
* grouping data on a datetime axis.
|
||||
*
|
||||
* @function Highcharts.Time#getTimeTicks
|
||||
*
|
||||
* @param {Highcharts.TimeNormalizedObject} normalizedInterval
|
||||
* The interval in axis values (ms) and the count
|
||||
*
|
||||
* @param {number} [min]
|
||||
* The minimum in axis values
|
||||
*
|
||||
* @param {number} [max]
|
||||
* The maximum in axis values
|
||||
*
|
||||
* @param {number} [startOfWeek=1]
|
||||
*
|
||||
* @return {Highcharts.AxisTickPositionsArray}
|
||||
*/
|
||||
Time.prototype.getTimeTicks = function (normalizedInterval, min, max, startOfWeek) {
|
||||
var time = this, Date = time.Date, tickPositions = [], i, higherRanks = {}, minYear, // used in months and years as a basis for Date.UTC()
|
||||
// When crossing DST, use the max. Resolves #6278.
|
||||
minDate = new Date(min), interval = normalizedInterval.unitRange, count = normalizedInterval.count || 1, variableDayLength, minDay;
|
||||
startOfWeek = pick(startOfWeek, 1);
|
||||
if (defined(min)) { // #1300
|
||||
time.set('Milliseconds', minDate, interval >= timeUnits.second ?
|
||||
0 : // #3935
|
||||
count * Math.floor(time.get('Milliseconds', minDate) / count)); // #3652, #3654
|
||||
if (interval >= timeUnits.second) { // second
|
||||
time.set('Seconds', minDate, interval >= timeUnits.minute ?
|
||||
0 : // #3935
|
||||
count * Math.floor(time.get('Seconds', minDate) / count));
|
||||
}
|
||||
if (interval >= timeUnits.minute) { // minute
|
||||
time.set('Minutes', minDate, interval >= timeUnits.hour ?
|
||||
0 :
|
||||
count * Math.floor(time.get('Minutes', minDate) / count));
|
||||
}
|
||||
if (interval >= timeUnits.hour) { // hour
|
||||
time.set('Hours', minDate, interval >= timeUnits.day ?
|
||||
0 :
|
||||
count * Math.floor(time.get('Hours', minDate) / count));
|
||||
}
|
||||
if (interval >= timeUnits.day) { // day
|
||||
time.set('Date', minDate, interval >= timeUnits.month ?
|
||||
1 :
|
||||
Math.max(1, count * Math.floor(time.get('Date', minDate) / count)));
|
||||
}
|
||||
if (interval >= timeUnits.month) { // month
|
||||
time.set('Month', minDate, interval >= timeUnits.year ? 0 :
|
||||
count * Math.floor(time.get('Month', minDate) / count));
|
||||
minYear = time.get('FullYear', minDate);
|
||||
}
|
||||
if (interval >= timeUnits.year) { // year
|
||||
minYear -= minYear % count;
|
||||
time.set('FullYear', minDate, minYear);
|
||||
}
|
||||
// week is a special case that runs outside the hierarchy
|
||||
if (interval === timeUnits.week) {
|
||||
// get start of current week, independent of count
|
||||
minDay = time.get('Day', minDate);
|
||||
time.set('Date', minDate, (time.get('Date', minDate) -
|
||||
minDay + startOfWeek +
|
||||
// We don't want to skip days that are before
|
||||
// startOfWeek (#7051)
|
||||
(minDay < startOfWeek ? -7 : 0)));
|
||||
}
|
||||
// Get basics for variable time spans
|
||||
minYear = time.get('FullYear', minDate);
|
||||
var minMonth = time.get('Month', minDate), minDateDate = time.get('Date', minDate), minHours = time.get('Hours', minDate);
|
||||
// Redefine min to the floored/rounded minimum time (#7432)
|
||||
min = minDate.getTime();
|
||||
// Handle local timezone offset
|
||||
if (time.variableTimezone) {
|
||||
// Detect whether we need to take the DST crossover into
|
||||
// consideration. If we're crossing over DST, the day length may
|
||||
// be 23h or 25h and we need to compute the exact clock time for
|
||||
// each tick instead of just adding hours. This comes at a cost,
|
||||
// so first we find out if it is needed (#4951).
|
||||
variableDayLength = (
|
||||
// Long range, assume we're crossing over.
|
||||
max - min > 4 * timeUnits.month ||
|
||||
// Short range, check if min and max are in different time
|
||||
// zones.
|
||||
time.getTimezoneOffset(min) !==
|
||||
time.getTimezoneOffset(max));
|
||||
}
|
||||
// Iterate and add tick positions at appropriate values
|
||||
var t = minDate.getTime();
|
||||
i = 1;
|
||||
while (t < max) {
|
||||
tickPositions.push(t);
|
||||
// if the interval is years, use Date.UTC to increase years
|
||||
if (interval === timeUnits.year) {
|
||||
t = time.makeTime(minYear + i * count, 0);
|
||||
// if the interval is months, use Date.UTC to increase months
|
||||
}
|
||||
else if (interval === timeUnits.month) {
|
||||
t = time.makeTime(minYear, minMonth + i * count);
|
||||
// if we're using global time, the interval is not fixed as it
|
||||
// jumps one hour at the DST crossover
|
||||
}
|
||||
else if (variableDayLength &&
|
||||
(interval === timeUnits.day || interval === timeUnits.week)) {
|
||||
t = time.makeTime(minYear, minMonth, minDateDate +
|
||||
i * count * (interval === timeUnits.day ? 1 : 7));
|
||||
}
|
||||
else if (variableDayLength &&
|
||||
interval === timeUnits.hour &&
|
||||
count > 1) {
|
||||
// make sure higher ranks are preserved across DST (#6797,
|
||||
// #7621)
|
||||
t = time.makeTime(minYear, minMonth, minDateDate, minHours + i * count);
|
||||
// else, the interval is fixed and we use simple addition
|
||||
}
|
||||
else {
|
||||
t += interval * count;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// push the last time
|
||||
tickPositions.push(t);
|
||||
// Handle higher ranks. Mark new days if the time is on midnight
|
||||
// (#950, #1649, #1760, #3349). Use a reasonable dropout threshold
|
||||
// to prevent looping over dense data grouping (#6156).
|
||||
if (interval <= timeUnits.hour && tickPositions.length < 10000) {
|
||||
tickPositions.forEach(function (t) {
|
||||
if (
|
||||
// Speed optimization, no need to run dateFormat unless
|
||||
// we're on a full or half hour
|
||||
t % 1800000 === 0 &&
|
||||
// Check for local or global midnight
|
||||
time.dateFormat('%H%M%S%L', t) === '000000000') {
|
||||
higherRanks[t] = 'day';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// record information on the chosen unit - for dynamic label formatter
|
||||
tickPositions.info = extend(normalizedInterval, {
|
||||
higherRanks: higherRanks,
|
||||
totalRange: interval * count
|
||||
});
|
||||
return tickPositions;
|
||||
};
|
||||
return Time;
|
||||
}());
|
||||
H.Time = Time;
|
||||
export default H.Time;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user