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;
|
||||
Reference in New Issue
Block a user