chore: version inicial del codigo fuente del LMS Estafeta

This commit is contained in:
2026-10-02 20:33:12 -06:00
commit c8bb6b97b9
1572 changed files with 457520 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
/* *
*
* (c) 2010-2017 Christer Vasseng, Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../Core/Globals.js';
import U from '../Core/Utilities.js';
var merge = U.merge, objectEach = U.objectEach;
/**
* @interface Highcharts.AjaxSettingsObject
*/ /**
* The payload to send.
*
* @name Highcharts.AjaxSettingsObject#data
* @type {string|Highcharts.Dictionary<any>}
*/ /**
* The data type expected.
* @name Highcharts.AjaxSettingsObject#dataType
* @type {"json"|"xml"|"text"|"octet"}
*/ /**
* Function to call on error.
* @name Highcharts.AjaxSettingsObject#error
* @type {Function}
*/ /**
* The headers; keyed on header name.
* @name Highcharts.AjaxSettingsObject#headers
* @type {Highcharts.Dictionary<string>}
*/ /**
* Function to call on success.
* @name Highcharts.AjaxSettingsObject#success
* @type {Function}
*/ /**
* The HTTP method to use. For example GET or POST.
* @name Highcharts.AjaxSettingsObject#type
* @type {string}
*/ /**
* The URL to call.
* @name Highcharts.AjaxSettingsObject#url
* @type {string}
*/
/**
* Perform an Ajax call.
*
* @function Highcharts.ajax
*
* @param {Partial<Highcharts.AjaxSettingsObject>} attr
* The Ajax settings to use.
*
* @return {false|undefined}
* Returns false, if error occured.
*/
H.ajax = function (attr) {
var options = merge(true, {
url: false,
type: 'get',
dataType: 'json',
success: false,
error: false,
data: false,
headers: {}
}, attr), headers = {
json: 'application/json',
xml: 'application/xml',
text: 'text/plain',
octet: 'application/octet-stream'
}, r = new XMLHttpRequest();
/**
* @private
* @param {XMLHttpRequest} xhr - Internal request object.
* @param {string|Error} err - Occured error.
* @return {void}
*/
function handleError(xhr, err) {
if (options.error) {
options.error(xhr, err);
}
else {
// @todo Maybe emit a highcharts error event here
}
}
if (!options.url) {
return false;
}
r.open(options.type.toUpperCase(), options.url, true);
if (!options.headers['Content-Type']) {
r.setRequestHeader('Content-Type', headers[options.dataType] || headers.text);
}
objectEach(options.headers, function (val, key) {
r.setRequestHeader(key, val);
});
// @todo lacking timeout handling
r.onreadystatechange = function () {
var res;
if (r.readyState === 4) {
if (r.status === 200) {
res = r.responseText;
if (options.dataType === 'json') {
try {
res = JSON.parse(res);
}
catch (e) {
return handleError(r, e);
}
}
return options.success && options.success(res);
}
handleError(r, r.responseText);
}
};
try {
options.data = JSON.stringify(options.data);
}
catch (e) {
// empty
}
r.send(options.data || true);
};
/**
* Get a JSON resource over XHR, also supporting CORS without preflight.
*
* @function Highcharts.getJSON
* @param {string} url
* The URL to load.
* @param {Function} success
* The success callback. For error handling, use the `Highcharts.ajax`
* function instead.
* @return {void}
*/
H.getJSON = function (url, success) {
H.ajax({
url: url,
success: success,
dataType: 'json',
headers: {
// Override the Content-Type to avoid preflight problems with CORS
// in the Highcharts demos
'Content-Type': 'text/plain'
}
});
};
var exports = {
ajax: H.ajax,
getJSON: H.getJSON
};
export default exports;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,143 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
/**
* Callback to modify annotation's possitioner controls.
*
* @callback Highcharts.AnnotationControlPointPositionerFunction
* @param {Highcharts.AnnotationControlPoint} this
* @param {Highcharts.AnnotationControllable} target
* @return {Highcharts.PositionObject}
*/
import U from '../../Core/Utilities.js';
var extend = U.extend, merge = U.merge, pick = U.pick;
import eventEmitterMixin from './Mixins/EventEmitterMixin.js';
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* A control point class which is a connection between controllable
* transform methods and a user actions.
*
* @requires modules/annotations
*
* @class
* @name Highcharts.AnnotationControlPoint
*
* @hideconstructor
*
* @param {Highcharts.Chart} chart
* A chart instance.
*
* @param {Highcharts.AnnotationControllable} target
* A controllable instance which is a target for a control point.
*
* @param {Highcharts.AnnotationControlPointOptionsObject} options
* An options object.
*
* @param {number} [index]
* Point index.
*/
var ControlPoint = /** @class */ (function () {
function ControlPoint(chart, target, options, index) {
/**
*
* Properties
*
*/
this.addEvents = eventEmitterMixin.addEvents;
this.graphic = void 0;
this.mouseMoveToRadians = eventEmitterMixin.mouseMoveToRadians;
this.mouseMoveToScale = eventEmitterMixin.mouseMoveToScale;
this.mouseMoveToTranslation = eventEmitterMixin.mouseMoveToTranslation;
this.onDrag = eventEmitterMixin.onDrag;
this.onMouseDown = eventEmitterMixin.onMouseDown;
this.onMouseUp = eventEmitterMixin.onMouseUp;
this.removeDocEvents = eventEmitterMixin.removeDocEvents;
/**
*
* Functions
*
*/
/**
* List of events for `anntation.options.events` that should not be
* added to `annotation.graphic` but to the `annotation`.
* @private
* @name Highcharts.AnnotationControlPoint#nonDOMEvents
* @type {Array<string>}
*/
this.nonDOMEvents = ['drag'];
this.chart = chart;
this.target = target;
this.options = options;
this.index = pick(options.index, index);
}
/**
* Set the visibility of the control point.
*
* @function Highcharts.AnnotationControlPoint#setVisibility
*
* @param {boolean} visible
* Visibility of the control point.
*
* @return {void}
*/
ControlPoint.prototype.setVisibility = function (visible) {
this.graphic.attr('visibility', visible ? 'visible' : 'hidden');
this.options.visible = visible;
};
/**
* Render the control point.
* @private
*/
ControlPoint.prototype.render = function () {
var chart = this.chart, options = this.options;
this.graphic = chart.renderer
.symbol(options.symbol, 0, 0, options.width, options.height)
.add(chart.controlPointsGroup)
.css(options.style);
this.setVisibility(options.visible);
// npm test -- --tests "highcharts/annotations-advanced/*"
this.addEvents();
};
/**
* Redraw the control point.
* @private
* @param {boolean} [animation]
*/
ControlPoint.prototype.redraw = function (animation) {
this.graphic[animation ? 'animate' : 'attr'](this.options.positioner.call(this, this.target));
};
/**
* Destroy the control point.
* @private
*/
ControlPoint.prototype.destroy = function () {
eventEmitterMixin.destroy.call(this);
if (this.graphic) {
this.graphic = this.graphic.destroy();
}
this.chart = null;
this.target = null;
this.options = null;
};
/**
* Update the control point.
*
* @function Highcharts.AnnotationControlPoint#update
*
* @param {Partial<Highcharts.AnnotationControlPointOptionsObject>} userOptions
* New options for the control point.
*
* @return {void}
*/
ControlPoint.prototype.update = function (userOptions) {
var chart = this.chart, target = this.target, index = this.index, options = merge(true, this.options, userOptions);
this.destroy();
this.constructor(chart, target, options, index);
this.render(chart.controlPointsGroup);
this.redraw();
};
return ControlPoint;
}());
export default ControlPoint;
@@ -0,0 +1,117 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import ControllableMixin from '../Mixins/ControllableMixin.js';
import ControllablePath from './ControllablePath.js';
import U from '../../../Core/Utilities.js';
var merge = U.merge;
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* A controllable circle class.
*
* @requires modules/annotations
*
* @private
* @class
* @name Highcharts.AnnotationControllableCircle
*
* @param {Highcharts.Annotation} annotation an annotation instance
* @param {Highcharts.AnnotationsShapeOptions} options a shape's options
* @param {number} index of the circle
*/
var ControllableCircle = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function ControllableCircle(annotation, options, index) {
/* *
*
* Properties
*
* */
this.addControlPoints = ControllableMixin.addControlPoints;
this.anchor = ControllableMixin.anchor;
this.attr = ControllableMixin.attr;
this.attrsFromOptions = ControllableMixin.attrsFromOptions;
this.destroy = ControllableMixin.destroy;
this.getPointsOptions = ControllableMixin.getPointsOptions;
this.init = ControllableMixin.init;
this.linkPoints = ControllableMixin.linkPoints;
this.point = ControllableMixin.point;
this.rotate = ControllableMixin.rotate;
this.scale = ControllableMixin.scale;
this.setControlPointsVisibility = ControllableMixin.setControlPointsVisibility;
this.shouldBeDrawn = ControllableMixin.shouldBeDrawn;
this.transform = ControllableMixin.transform;
this.transformPoint = ControllableMixin.transformPoint;
this.translatePoint = ControllableMixin.translatePoint;
this.translateShape = ControllableMixin.translateShape;
this.update = ControllableMixin.update;
/**
* @type 'circle'
*/
this.type = 'circle';
this.translate = ControllableMixin.translateShape;
this.init(annotation, options, index);
this.collection = 'shapes';
}
/* *
*
* Functions
*
* */
ControllableCircle.prototype.render = function (parent) {
var attrs = this.attrsFromOptions(this.options);
this.graphic = this.annotation.chart.renderer
.circle(0, -9e9, 0)
.attr(attrs)
.add(parent);
ControllableMixin.render.call(this);
};
ControllableCircle.prototype.redraw = function (animation) {
var position = this.anchor(this.points[0]).absolutePosition;
if (position) {
this.graphic[animation ? 'animate' : 'attr']({
x: position.x,
y: position.y,
r: this.options.r
});
}
else {
this.graphic.attr({
x: 0,
y: -9e9
});
}
this.graphic.placed = Boolean(position);
ControllableMixin.redraw.call(this, animation);
};
/**
* Set the radius.
*
* @param {number} r a radius to be set
*/
ControllableCircle.prototype.setRadius = function (r) {
this.options.r = r;
};
/* *
*
* Static Properties
*
* */
/**
* A map object which allows to map options attributes to element
* attributes.
*
* @name Highcharts.AnnotationControllableCircle.attrsMap
* @type {Highcharts.Dictionary<string>}
*/
ControllableCircle.attrsMap = merge(ControllablePath.attrsMap, { r: 'r' });
return ControllableCircle;
}());
export default ControllableCircle;
@@ -0,0 +1,111 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import ControllableLabel from './ControllableLabel.js';
import ControllableMixin from '../Mixins/ControllableMixin.js';
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* A controllable image class.
*
* @requires modules/annotations
*
* @private
* @class
* @name Highcharts.AnnotationControllableImage
*
* @param {Highcharts.Annotation} annotation
* An annotation instance.
*
* @param {Highcharts.AnnotationsShapeOptions} options
* A controllable's options.
*
* @param {number} index
* Index of the image.
*/
var ControllableImage = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function ControllableImage(annotation, options, index) {
/* *
*
* Properties
*
* */
this.addControlPoints = ControllableMixin.addControlPoints;
this.anchor = ControllableMixin.anchor;
this.attr = ControllableMixin.attr;
this.attrsFromOptions = ControllableMixin.attrsFromOptions;
this.destroy = ControllableMixin.destroy;
this.getPointsOptions = ControllableMixin.getPointsOptions;
this.init = ControllableMixin.init;
this.linkPoints = ControllableMixin.linkPoints;
this.point = ControllableMixin.point;
this.rotate = ControllableMixin.rotate;
this.scale = ControllableMixin.scale;
this.setControlPointsVisibility = ControllableMixin.setControlPointsVisibility;
this.shouldBeDrawn = ControllableMixin.shouldBeDrawn;
this.transform = ControllableMixin.transform;
this.transformPoint = ControllableMixin.transformPoint;
this.translatePoint = ControllableMixin.translatePoint;
this.translateShape = ControllableMixin.translateShape;
this.update = ControllableMixin.update;
/**
* @type 'image'
*/
this.type = 'image';
this.translate = ControllableMixin.translateShape;
this.init(annotation, options, index);
this.collection = 'shapes';
}
ControllableImage.prototype.render = function (parent) {
var attrs = this.attrsFromOptions(this.options), options = this.options;
this.graphic = this.annotation.chart.renderer
.image(options.src, 0, -9e9, options.width, options.height)
.attr(attrs)
.add(parent);
this.graphic.width = options.width;
this.graphic.height = options.height;
ControllableMixin.render.call(this);
};
ControllableImage.prototype.redraw = function (animation) {
var anchor = this.anchor(this.points[0]), position = ControllableLabel.prototype.position.call(this, anchor);
if (position) {
this.graphic[animation ? 'animate' : 'attr']({
x: position.x,
y: position.y
});
}
else {
this.graphic.attr({
x: 0,
y: -9e9
});
}
this.graphic.placed = Boolean(position);
ControllableMixin.redraw.call(this, animation);
};
/* *
*
* Static Properties
*
* */
/**
* A map object which allows to map options attributes to element attributes
*
* @name Highcharts.AnnotationControllableImage.attrsMap
* @type {Highcharts.Dictionary<string>}
*/
ControllableImage.attrsMap = {
width: 'width',
height: 'height',
zIndex: 'zIndex'
};
return ControllableImage;
}());
export default ControllableImage;
@@ -0,0 +1,378 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import ControllableMixin from '../Mixins/ControllableMixin.js';
import MockPoint from '../MockPoint.js';
import SVGRenderer from '../../../Core/Renderer/SVG/SVGRenderer.js';
import Tooltip from '../../../Core/Tooltip.js';
import U from '../../../Core/Utilities.js';
var extend = U.extend, format = U.format, isNumber = U.isNumber, pick = U.pick;
import '../../../Core/Renderer/SVG/SVGRenderer.js';
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* A controllable label class.
*
* @requires modules/annotations
*
* @private
* @class
* @name Highcharts.AnnotationControllableLabel
*
* @param {Highcharts.Annotation} annotation
* An annotation instance.
* @param {Highcharts.AnnotationsLabelOptions} options
* A label's options.
* @param {number} index
* Index of the label.
*/
var ControllableLabel = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function ControllableLabel(annotation, options, index) {
/* *
*
* Properties
*
* */
this.addControlPoints = ControllableMixin.addControlPoints;
this.attr = ControllableMixin.attr;
this.attrsFromOptions = ControllableMixin.attrsFromOptions;
this.destroy = ControllableMixin.destroy;
this.getPointsOptions = ControllableMixin.getPointsOptions;
this.init = ControllableMixin.init;
this.linkPoints = ControllableMixin.linkPoints;
this.point = ControllableMixin.point;
this.rotate = ControllableMixin.rotate;
this.scale = ControllableMixin.scale;
this.setControlPointsVisibility = ControllableMixin.setControlPointsVisibility;
this.shouldBeDrawn = ControllableMixin.shouldBeDrawn;
this.transform = ControllableMixin.transform;
this.transformPoint = ControllableMixin.transformPoint;
this.translateShape = ControllableMixin.translateShape;
this.update = ControllableMixin.update;
this.init(annotation, options, index);
this.collection = 'labels';
}
/* *
*
* Static Functions
*
* */
/**
* Returns new aligned position based alignment options and box to align to.
* It is almost a one-to-one copy from SVGElement.prototype.align
* except it does not use and mutate an element
*
* @param {Highcharts.AnnotationAlignObject} alignOptions
*
* @param {Highcharts.BBoxObject} box
*
* @return {Highcharts.PositionObject}
* Aligned position.
*/
ControllableLabel.alignedPosition = function (alignOptions, box) {
var align = alignOptions.align, vAlign = alignOptions.verticalAlign, x = (box.x || 0) + (alignOptions.x || 0), y = (box.y || 0) + (alignOptions.y || 0), alignFactor, vAlignFactor;
if (align === 'right') {
alignFactor = 1;
}
else if (align === 'center') {
alignFactor = 2;
}
if (alignFactor) {
x += (box.width - (alignOptions.width || 0)) / alignFactor;
}
if (vAlign === 'bottom') {
vAlignFactor = 1;
}
else if (vAlign === 'middle') {
vAlignFactor = 2;
}
if (vAlignFactor) {
y += (box.height - (alignOptions.height || 0)) / vAlignFactor;
}
return {
x: Math.round(x),
y: Math.round(y)
};
};
/**
* Returns new alignment options for a label if the label is outside the
* plot area. It is almost a one-to-one copy from
* Series.prototype.justifyDataLabel except it does not mutate the label and
* it works with absolute instead of relative position.
*/
ControllableLabel.justifiedOptions = function (chart, label, alignOptions, alignAttr) {
var align = alignOptions.align, verticalAlign = alignOptions.verticalAlign, padding = label.box ? 0 : (label.padding || 0), bBox = label.getBBox(), off,
//
options = {
align: align,
verticalAlign: verticalAlign,
x: alignOptions.x,
y: alignOptions.y,
width: label.width,
height: label.height
},
//
x = alignAttr.x - chart.plotLeft, y = alignAttr.y - chart.plotTop;
// Off left
off = x + padding;
if (off < 0) {
if (align === 'right') {
options.align = 'left';
}
else {
options.x = -off;
}
}
// Off right
off = x + bBox.width - padding;
if (off > chart.plotWidth) {
if (align === 'left') {
options.align = 'right';
}
else {
options.x = chart.plotWidth - off;
}
}
// Off top
off = y + padding;
if (off < 0) {
if (verticalAlign === 'bottom') {
options.verticalAlign = 'top';
}
else {
options.y = -off;
}
}
// Off bottom
off = y + bBox.height - padding;
if (off > chart.plotHeight) {
if (verticalAlign === 'top') {
options.verticalAlign = 'bottom';
}
else {
options.y = chart.plotHeight - off;
}
}
return options;
};
/* *
*
* Functions
*
* */
/**
* Translate the point of the label by deltaX and deltaY translations.
* The point is the label's anchor.
*
* @param {number} dx translation for x coordinate
* @param {number} dy translation for y coordinate
*/
ControllableLabel.prototype.translatePoint = function (dx, dy) {
ControllableMixin.translatePoint.call(this, dx, dy, 0);
};
/**
* Translate x and y position relative to the label's anchor.
*
* @param {number} dx translation for x coordinate
* @param {number} dy translation for y coordinate
*/
ControllableLabel.prototype.translate = function (dx, dy) {
var chart = this.annotation.chart,
// Annotation.options
labelOptions = this.annotation.userOptions,
// Chart.options.annotations
annotationIndex = chart.annotations.indexOf(this.annotation), chartAnnotations = chart.options.annotations, chartOptions = chartAnnotations[annotationIndex], temp;
if (chart.inverted) {
temp = dx;
dx = dy;
dy = temp;
}
// Local options:
this.options.x += dx;
this.options.y += dy;
// Options stored in chart:
chartOptions[this.collection][this.index].x = this.options.x;
chartOptions[this.collection][this.index].y = this.options.y;
labelOptions[this.collection][this.index].x = this.options.x;
labelOptions[this.collection][this.index].y = this.options.y;
};
ControllableLabel.prototype.render = function (parent) {
var options = this.options, attrs = this.attrsFromOptions(options), style = options.style;
this.graphic = this.annotation.chart.renderer
.label('', 0, -9999, // #10055
options.shape, null, null, options.useHTML, null, 'annotation-label')
.attr(attrs)
.add(parent);
if (!this.annotation.chart.styledMode) {
if (style.color === 'contrast') {
style.color = this.annotation.chart.renderer.getContrast(ControllableLabel.shapesWithoutBackground.indexOf(options.shape) > -1 ? '#FFFFFF' : options.backgroundColor);
}
this.graphic
.css(options.style)
.shadow(options.shadow);
}
if (options.className) {
this.graphic.addClass(options.className);
}
this.graphic.labelrank = options.labelrank;
ControllableMixin.render.call(this);
};
ControllableLabel.prototype.redraw = function (animation) {
var options = this.options, text = this.text || options.format || options.text, label = this.graphic, point = this.points[0], anchor, attrs;
label.attr({
text: text ?
format(text, point.getLabelConfig(), this.annotation.chart) :
options.formatter.call(point, this)
});
anchor = this.anchor(point);
attrs = this.position(anchor);
if (attrs) {
label.alignAttr = attrs;
attrs.anchorX = anchor.absolutePosition.x;
attrs.anchorY = anchor.absolutePosition.y;
label[animation ? 'animate' : 'attr'](attrs);
}
else {
label.attr({
x: 0,
y: -9999 // #10055
});
}
label.placed = !!attrs;
ControllableMixin.redraw.call(this, animation);
};
/**
* All basic shapes don't support alignTo() method except label.
* For a controllable label, we need to subtract translation from
* options.
*/
ControllableLabel.prototype.anchor = function (_point) {
var anchor = ControllableMixin.anchor.apply(this, arguments), x = this.options.x || 0, y = this.options.y || 0;
anchor.absolutePosition.x -= x;
anchor.absolutePosition.y -= y;
anchor.relativePosition.x -= x;
anchor.relativePosition.y -= y;
return anchor;
};
/**
* Returns the label position relative to its anchor.
*
* @param {Highcharts.AnnotationAnchorObject} anchor
*
* @return {Highcharts.PositionObject|null}
*/
ControllableLabel.prototype.position = function (anchor) {
var item = this.graphic, chart = this.annotation.chart, point = this.points[0], itemOptions = this.options, anchorAbsolutePosition = anchor.absolutePosition, anchorRelativePosition = anchor.relativePosition, itemPosition, alignTo, itemPosRelativeX, itemPosRelativeY, showItem = point.series.visible &&
MockPoint.prototype.isInsidePlot.call(point);
if (showItem) {
if (itemOptions.distance) {
itemPosition = Tooltip.prototype.getPosition.call({
chart: chart,
distance: pick(itemOptions.distance, 16)
}, item.width, item.height, {
plotX: anchorRelativePosition.x,
plotY: anchorRelativePosition.y,
negative: point.negative,
ttBelow: point.ttBelow,
h: (anchorRelativePosition.height || anchorRelativePosition.width)
});
}
else if (itemOptions.positioner) {
itemPosition = itemOptions.positioner.call(this);
}
else {
alignTo = {
x: anchorAbsolutePosition.x,
y: anchorAbsolutePosition.y,
width: 0,
height: 0
};
itemPosition = ControllableLabel.alignedPosition(extend(itemOptions, {
width: item.width,
height: item.height
}), alignTo);
if (this.options.overflow === 'justify') {
itemPosition = ControllableLabel.alignedPosition(ControllableLabel.justifiedOptions(chart, item, itemOptions, itemPosition), alignTo);
}
}
if (itemOptions.crop) {
itemPosRelativeX = itemPosition.x - chart.plotLeft;
itemPosRelativeY = itemPosition.y - chart.plotTop;
showItem =
chart.isInsidePlot(itemPosRelativeX, itemPosRelativeY) &&
chart.isInsidePlot(itemPosRelativeX + item.width, itemPosRelativeY + item.height);
}
}
return showItem ? itemPosition : null;
};
/* *
*
* Static Properties
*
* */
/**
* A map object which allows to map options attributes to element attributes
*
* @type {Highcharts.Dictionary<string>}
*/
ControllableLabel.attrsMap = {
backgroundColor: 'fill',
borderColor: 'stroke',
borderWidth: 'stroke-width',
zIndex: 'zIndex',
borderRadius: 'r',
padding: 'padding'
};
/**
* Shapes which do not have background - the object is used for proper
* setting of the contrast color.
*
* @type {Array<string>}
*/
ControllableLabel.shapesWithoutBackground = ['connector'];
return ControllableLabel;
}());
export default ControllableLabel;
/* ********************************************************************** */
/**
* General symbol definition for labels with connector
* @private
*/
SVGRenderer.prototype.symbols.connector = function (x, y, w, h, options) {
var anchorX = options && options.anchorX, anchorY = options && options.anchorY, path, yOffset, lateral = w / 2;
if (isNumber(anchorX) && isNumber(anchorY)) {
path = [['M', anchorX, anchorY]];
// Prefer 45 deg connectors
yOffset = y - anchorY;
if (yOffset < 0) {
yOffset = -h - yOffset;
}
if (yOffset < w) {
lateral = anchorX < x + (w / 2) ? yOffset : w - yOffset;
}
// Anchor below label
if (anchorY > y + h) {
path.push(['L', x + lateral, y + h]);
// Anchor above label
}
else if (anchorY < y) {
path.push(['L', x + lateral, y]);
// Anchor left of label
}
else if (anchorX < x) {
path.push(['L', x, y + h / 2]);
// Anchor right of label
}
else if (anchorX > x + w) {
path.push(['L', x + w, y + h / 2]);
}
}
return path || [];
};
@@ -0,0 +1,180 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import ControllableMixin from '../Mixins/ControllableMixin.js';
import H from '../../../Core/Globals.js';
import MarkerMixin from '../Mixins/MarkerMixin.js';
import U from '../../../Core/Utilities.js';
var extend = U.extend;
// See TRACKER_FILL in highcharts.src.js
var TRACKER_FILL = 'rgba(192,192,192,' + (H.svg ? 0.0001 : 0.002) + ')';
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* A controllable path class.
*
* @requires modules/annotations
*
* @private
* @class
* @name Highcharts.AnnotationControllablePath
*
* @param {Highcharts.Annotation}
* Related annotation.
*
* @param {Highcharts.AnnotationsShapeOptions} options
* A path's options object.
*
* @param {number} index
* Index of the path.
*/
var ControllablePath = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function ControllablePath(annotation, options, index) {
/* *
*
* Properties
*
* */
this.addControlPoints = ControllableMixin.addControlPoints;
this.anchor = ControllableMixin.anchor;
this.attr = ControllableMixin.attr;
this.attrsFromOptions = ControllableMixin.attrsFromOptions;
this.destroy = ControllableMixin.destroy;
this.getPointsOptions = ControllableMixin.getPointsOptions;
this.init = ControllableMixin.init;
this.linkPoints = ControllableMixin.linkPoints;
this.point = ControllableMixin.point;
this.rotate = ControllableMixin.rotate;
this.scale = ControllableMixin.scale;
this.setControlPointsVisibility = ControllableMixin.setControlPointsVisibility;
this.setMarkers = MarkerMixin.setItemMarkers;
this.transform = ControllableMixin.transform;
this.transformPoint = ControllableMixin.transformPoint;
this.translate = ControllableMixin.translate;
this.translatePoint = ControllableMixin.translatePoint;
this.translateShape = ControllableMixin.translateShape;
this.update = ControllableMixin.update;
/**
* @type 'path'
*/
this.type = 'path';
this.init(annotation, options, index);
this.collection = 'shapes';
}
/* *
*
* Functions
*
* */
/**
* Map the controllable path to 'd' path attribute.
*
* @return {Highcharts.SVGPathArray|null}
* A path's d attribute.
*/
ControllablePath.prototype.toD = function () {
var dOption = this.options.d;
if (dOption) {
return typeof dOption === 'function' ?
dOption.call(this) :
dOption;
}
var points = this.points, len = points.length, showPath = len, point = points[0], position = showPath && this.anchor(point).absolutePosition, pointIndex = 0, command, d = [];
if (position) {
d.push(['M', position.x, position.y]);
while (++pointIndex < len && showPath) {
point = points[pointIndex];
command = point.command || 'L';
position = this.anchor(point).absolutePosition;
if (command === 'M') {
d.push([command, position.x, position.y]);
}
else if (command === 'L') {
d.push([command, position.x, position.y]);
}
else if (command === 'Z') {
d.push([command]);
}
showPath = point.series.visible;
}
}
return showPath ?
this.chart.renderer.crispLine(d, this.graphic.strokeWidth()) :
null;
};
ControllablePath.prototype.shouldBeDrawn = function () {
return (ControllableMixin.shouldBeDrawn.call(this) || Boolean(this.options.d));
};
ControllablePath.prototype.render = function (parent) {
var options = this.options, attrs = this.attrsFromOptions(options);
this.graphic = this.annotation.chart.renderer
.path([['M', 0, 0]])
.attr(attrs)
.add(parent);
if (options.className) {
this.graphic.addClass(options.className);
}
this.tracker = this.annotation.chart.renderer
.path([['M', 0, 0]])
.addClass('highcharts-tracker-line')
.attr({
zIndex: 2
})
.add(parent);
if (!this.annotation.chart.styledMode) {
this.tracker.attr({
'stroke-linejoin': 'round',
stroke: TRACKER_FILL,
fill: TRACKER_FILL,
'stroke-width': this.graphic.strokeWidth() +
options.snap * 2
});
}
ControllableMixin.render.call(this);
extend(this.graphic, {
markerStartSetter: MarkerMixin.markerStartSetter,
markerEndSetter: MarkerMixin.markerEndSetter
});
this.setMarkers(this);
};
ControllablePath.prototype.redraw = function (animation) {
var d = this.toD(), action = animation ? 'animate' : 'attr';
if (d) {
this.graphic[action]({ d: d });
this.tracker[action]({ d: d });
}
else {
this.graphic.attr({ d: 'M 0 ' + -9e9 });
this.tracker.attr({ d: 'M 0 ' + -9e9 });
}
this.graphic.placed = this.tracker.placed = Boolean(d);
ControllableMixin.redraw.call(this, animation);
};
/* *
*
* Static Properties
*
* */
/**
* A map object which allows to map options attributes to element attributes
*
* @name Highcharts.AnnotationControllablePath.attrsMap
* @type {Highcharts.Dictionary<string>}
*/
ControllablePath.attrsMap = {
dashStyle: 'dashstyle',
strokeWidth: 'stroke-width',
stroke: 'stroke',
fill: 'fill',
zIndex: 'zIndex'
};
return ControllablePath;
}());
export default ControllablePath;
@@ -0,0 +1,121 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
import ControllableMixin from '../Mixins/ControllableMixin.js';
import ControllablePath from './ControllablePath.js';
import U from '../../../Core/Utilities.js';
var merge = U.merge;
/**
* @typedef {Annotation.ControllablePath.AttrsMap}
* Annotation.ControllableRect.AttrsMap
* @property {string} width=width
* @property {string} height=height
*/
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* A controllable rect class.
*
* @requires modules/annotations
*
* @private
* @class
* @name Highcharts.AnnotationControllableRect
*
* @param {Highcharts.Annotation} annotation
* An annotation instance.
*
* @param {Highcharts.AnnotationsShapeOptions} options
* A rect's options.
*
* @param {number} index
* Index of the rectangle
*/
var ControllableRect = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function ControllableRect(annotation, options, index) {
/* *
*
* Properties
*
* */
this.addControlPoints = ControllableMixin.addControlPoints;
this.anchor = ControllableMixin.anchor;
this.attr = ControllableMixin.attr;
this.attrsFromOptions = ControllableMixin.attrsFromOptions;
this.destroy = ControllableMixin.destroy;
this.getPointsOptions = ControllableMixin.getPointsOptions;
this.init = ControllableMixin.init;
this.linkPoints = ControllableMixin.linkPoints;
this.point = ControllableMixin.point;
this.rotate = ControllableMixin.rotate;
this.scale = ControllableMixin.scale;
this.setControlPointsVisibility = ControllableMixin.setControlPointsVisibility;
this.shouldBeDrawn = ControllableMixin.shouldBeDrawn;
this.transform = ControllableMixin.transform;
this.transformPoint = ControllableMixin.transformPoint;
this.translatePoint = ControllableMixin.translatePoint;
this.translateShape = ControllableMixin.translateShape;
this.update = ControllableMixin.update;
/**
* @type 'rect'
*/
this.type = 'rect';
this.translate = ControllableMixin.translateShape;
this.init(annotation, options, index);
this.collection = 'shapes';
}
/* *
*
* Functions
*
* */
ControllableRect.prototype.render = function (parent) {
var attrs = this.attrsFromOptions(this.options);
this.graphic = this.annotation.chart.renderer
.rect(0, -9e9, 0, 0)
.attr(attrs)
.add(parent);
ControllableMixin.render.call(this);
};
ControllableRect.prototype.redraw = function (animation) {
var position = this.anchor(this.points[0]).absolutePosition;
if (position) {
this.graphic[animation ? 'animate' : 'attr']({
x: position.x,
y: position.y,
width: this.options.width,
height: this.options.height
});
}
else {
this.attr({
x: 0,
y: -9e9
});
}
this.graphic.placed = Boolean(position);
ControllableMixin.redraw.call(this, animation);
};
/* *
*
* Static Properties
*
* */
/**
* A map object which allows to map options attributes to element attributes
*
* @type {Annotation.ControllableRect.AttrsMap}
*/
ControllableRect.attrsMap = merge(ControllablePath.attrsMap, {
width: 'width',
height: 'height'
});
return ControllableRect;
}());
export default ControllableRect;
@@ -0,0 +1,369 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import ControlPoint from '../ControlPoint.js';
import MockPoint from '../MockPoint.js';
import Tooltip from '../../../Core/Tooltip.js';
import U from '../../../Core/Utilities.js';
var isObject = U.isObject, isString = U.isString, merge = U.merge, splat = U.splat;
/**
* An object which denots a controllable's anchor positions - relative and
* absolute.
*
* @private
* @interface Highcharts.AnnotationAnchorObject
*/ /**
* Relative to the plot area position
* @name Highcharts.AnnotationAnchorObject#relativePosition
* @type {Highcharts.BBoxObject}
*/ /**
* Absolute position
* @name Highcharts.AnnotationAnchorObject#absolutePosition
* @type {Highcharts.BBoxObject}
*/
/**
* @interface Highcharts.AnnotationControllable
*/ /**
* @name Highcharts.AnnotationControllable#annotation
* @type {Highcharts.Annotation}
*/ /**
* @name Highcharts.AnnotationControllable#chart
* @type {Highcharts.Chart}
*/ /**
* @name Highcharts.AnnotationControllable#collection
* @type {string}
*/ /**
* @private
* @name Highcharts.AnnotationControllable#controlPoints
* @type {Array<Highcharts.AnnotationControlPoint>}
*/ /**
* @name Highcharts.AnnotationControllable#points
* @type {Array<Highcharts.Point>}
*/
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* It provides methods for handling points, control points
* and points transformations.
*
* @private
* @mixin
* @name Highcharts.AnnotationControllableMixin
*/
var controllableMixin = {
/**
* Init the controllable
*/
init: function (annotation, options, index) {
this.annotation = annotation;
this.chart = annotation.chart;
this.options = options;
this.points = [];
this.controlPoints = [];
this.index = index;
this.linkPoints();
this.addControlPoints();
},
/**
* Redirect attr usage on the controllable graphic element.
*/
attr: function () {
this.graphic.attr.apply(this.graphic, arguments);
},
/**
* Get the controllable's points options.
*
* @return {Array<Highcharts.PointOptionsObject>}
* An array of points' options.
*/
getPointsOptions: function () {
var options = this.options;
return (options.points || (options.point && splat(options.point)));
},
/**
* Utility function for mapping item's options
* to element's attribute
*
* @param {Highcharts.AnnotationsLabelsOptions|Highcharts.AnnotationsShapesOptions} options
*
* @return {Highcharts.SVGAttributes}
* Mapped options.
*/
attrsFromOptions: function (options) {
var map = this.constructor.attrsMap, attrs = {}, key, mappedKey, styledMode = this.chart.styledMode;
for (key in options) { // eslint-disable-line guard-for-in
mappedKey = map[key];
if (mappedKey &&
(!styledMode ||
['fill', 'stroke', 'stroke-width']
.indexOf(mappedKey) === -1)) {
attrs[mappedKey] = options[key];
}
}
return attrs;
},
/**
* Returns object which denotes anchor position - relative and absolute.
*
* @param {Highcharts.AnnotationPointType} point
* A point like object.
*
* @return {Highcharts.AnnotationAnchorObject} a controllable anchor
*/
anchor: function (point) {
var plotBox = point.series.getPlotBox(), box = point.mock ?
point.toAnchor() :
Tooltip.prototype.getAnchor.call({
chart: point.series.chart
}, point), anchor = {
x: box[0] + (this.options.x || 0),
y: box[1] + (this.options.y || 0),
height: box[2] || 0,
width: box[3] || 0
};
return {
relativePosition: anchor,
absolutePosition: merge(anchor, {
x: anchor.x + plotBox.translateX,
y: anchor.y + plotBox.translateY
})
};
},
/**
* Map point's options to a point-like object.
*
* @param {string|Function|Highcharts.AnnotationMockPointOptionsObject|Highcharts.AnnotationPointType} pointOptions
* Point's options.
*
* @param {Highcharts.AnnotationPointType} point
* A point-like instance.
*
* @return {Highcharts.AnnotationPointType|null}
* if the point is found/set returns this point, otherwise null
*/
point: function (pointOptions, point) {
if (pointOptions && pointOptions.series) {
return pointOptions;
}
if (!point || point.series === null) {
if (isObject(pointOptions)) {
point = new MockPoint(this.chart, this, pointOptions);
}
else if (isString(pointOptions)) {
point = this.chart.get(pointOptions) || null;
}
else if (typeof pointOptions === 'function') {
var pointConfig = pointOptions.call(point, this);
point = pointConfig.series ?
pointConfig :
new MockPoint(this.chart, this, pointOptions);
}
}
return point;
},
/**
* Find point-like objects based on points options.
*
* @return {Array<Annotation.PointLike>} an array of point-like objects
*/
linkPoints: function () {
var pointsOptions = this.getPointsOptions(), points = this.points, len = (pointsOptions && pointsOptions.length) || 0, i, point;
for (i = 0; i < len; i++) {
point = this.point(pointsOptions[i], points[i]);
if (!point) {
points.length = 0;
return;
}
if (point.mock) {
point.refresh();
}
points[i] = point;
}
return points;
},
/**
* Add control points to a controllable.
*/
addControlPoints: function () {
var controlPointsOptions = this.options.controlPoints;
(controlPointsOptions || []).forEach(function (controlPointOptions, i) {
var options = merge(this.options.controlPointOptions, controlPointOptions);
if (!options.index) {
options.index = i;
}
controlPointsOptions[i] = options;
this.controlPoints.push(new ControlPoint(this.chart, this, options));
}, this);
},
/**
* Check if a controllable should be rendered/redrawn.
*
* @return {boolean}
* Whether a controllable should be drawn.
*/
shouldBeDrawn: function () {
return Boolean(this.points.length);
},
/**
* Render a controllable.
*/
render: function (_parentGroup) {
this.controlPoints.forEach(function (controlPoint) {
controlPoint.render();
});
},
/**
* Redraw a controllable.
*
* @param {boolean} [animation]
*/
redraw: function (animation) {
this.controlPoints.forEach(function (controlPoint) {
controlPoint.redraw(animation);
});
},
/**
* Transform a controllable with a specific transformation.
*
* @param {string} transformation a transformation name
* @param {number|null} cx origin x transformation
* @param {number|null} cy origin y transformation
* @param {number} p1 param for the transformation
* @param {number} [p2] param for the transformation
*/
transform: function (transformation, cx, cy, p1, p2) {
if (this.chart.inverted) {
var temp = cx;
cx = cy;
cy = temp;
}
this.points.forEach(function (point, i) {
this.transformPoint(transformation, cx, cy, p1, p2, i);
}, this);
},
/**
* Transform a point with a specific transformation
* If a transformed point is a real point it is replaced with
* the mock point.
*
* @param {string} transformation a transformation name
* @param {number|null} cx origin x transformation
* @param {number|null} cy origin y transformation
* @param {number} p1 param for the transformation
* @param {number|undefined} p2 param for the transformation
* @param {number} i index of the point
*/
transformPoint: function (transformation, cx, cy, p1, p2, i) {
var point = this.points[i];
if (!point.mock) {
point = this.points[i] = MockPoint.fromPoint(point);
}
point[transformation](cx, cy, p1, p2);
},
/**
* Translate a controllable.
*
* @param {number} dx translation for x coordinate
* @param {number} dy translation for y coordinate
**/
translate: function (dx, dy) {
this.transform('translate', null, null, dx, dy);
},
/**
* Translate a specific point within a controllable.
*
* @param {number} dx translation for x coordinate
* @param {number} dy translation for y coordinate
* @param {number} i index of the point
**/
translatePoint: function (dx, dy, i) {
this.transformPoint('translate', null, null, dx, dy, i);
},
/**
* Translate shape within controllable item.
* Replaces `controllable.translate` method.
*
* @param {number} dx translation for x coordinate
* @param {number} dy translation for y coordinate
*/
translateShape: function (dx, dy) {
var chart = this.annotation.chart,
// Annotation.options
shapeOptions = this.annotation.userOptions,
// Chart.options.annotations
annotationIndex = chart.annotations.indexOf(this.annotation), chartOptions = chart.options.annotations[annotationIndex];
this.translatePoint(dx, dy, 0);
// Options stored in:
// - chart (for exporting)
// - current config (for redraws)
chartOptions[this.collection][this.index].point = this.options.point;
shapeOptions[this.collection][this.index].point = this.options.point;
},
/**
* Rotate a controllable.
*
* @param {number} cx origin x rotation
* @param {number} cy origin y rotation
* @param {number} radians
**/
rotate: function (cx, cy, radians) {
this.transform('rotate', cx, cy, radians);
},
/**
* Scale a controllable.
*
* @param {number} cx origin x rotation
* @param {number} cy origin y rotation
* @param {number} sx scale factor x
* @param {number} sy scale factor y
*/
scale: function (cx, cy, sx, sy) {
this.transform('scale', cx, cy, sx, sy);
},
/**
* Set control points' visibility.
*
* @param {boolean} visible
*/
setControlPointsVisibility: function (visible) {
this.controlPoints.forEach(function (controlPoint) {
controlPoint.setVisibility(visible);
});
},
/**
* Destroy a controllable.
*/
destroy: function () {
if (this.graphic) {
this.graphic = this.graphic.destroy();
}
if (this.tracker) {
this.tracker = this.tracker.destroy();
}
this.controlPoints.forEach(function (controlPoint) {
controlPoint.destroy();
});
this.chart = null;
this.points = null;
this.controlPoints = null;
this.options = null;
if (this.annotation) {
this.annotation = null;
}
},
/**
* Update a controllable.
*
* @param {Object} newOptions
*/
update: function (newOptions) {
var annotation = this.annotation, options = merge(true, this.options, newOptions), parentGroup = this.graphic.parentGroup;
this.destroy();
this.constructor(annotation, options);
this.render(parentGroup);
this.redraw();
}
};
export default controllableMixin;
@@ -0,0 +1,212 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
import H from '../../../Core/Globals.js';
import U from '../../../Core/Utilities.js';
var addEvent = U.addEvent, fireEvent = U.fireEvent, objectEach = U.objectEach, pick = U.pick, removeEvent = U.removeEvent;
/* eslint-disable valid-jsdoc */
/**
* It provides methods for:
* - adding and handling DOM events and a drag event,
* - mapping a mouse move event to the distance between two following events.
* The units of the distance are specific to a transformation,
* e.g. for rotation they are radians, for scaling they are scale factors.
*
* @private
* @mixin
* @memberOf Annotation
*/
var eventEmitterMixin = {
/**
* Add emitter events.
*/
addEvents: function () {
var emitter = this, addMouseDownEvent = function (element) {
addEvent(element, H.isTouchDevice ? 'touchstart' : 'mousedown', function (e) {
emitter.onMouseDown(e);
});
};
addMouseDownEvent(this.graphic.element);
(emitter.labels || []).forEach(function (label) {
if (label.options.useHTML && label.graphic.text) {
// Mousedown event bound to HTML element (#13070).
addMouseDownEvent(label.graphic.text.element);
}
});
objectEach(emitter.options.events, function (event, type) {
var eventHandler = function (e) {
if (type !== 'click' || !emitter.cancelClick) {
event.call(emitter, emitter.chart.pointer.normalize(e), emitter.target);
}
};
if ((emitter.nonDOMEvents || []).indexOf(type) === -1) {
emitter.graphic.on(type, eventHandler);
}
else {
addEvent(emitter, type, eventHandler);
}
});
if (emitter.options.draggable) {
addEvent(emitter, 'drag', emitter.onDrag);
if (!emitter.graphic.renderer.styledMode) {
var cssPointer_1 = {
cursor: {
x: 'ew-resize',
y: 'ns-resize',
xy: 'move'
}[emitter.options.draggable]
};
emitter.graphic.css(cssPointer_1);
(emitter.labels || []).forEach(function (label) {
if (label.options.useHTML && label.graphic.text) {
label.graphic.text.css(cssPointer_1);
}
});
}
}
if (!emitter.isUpdating) {
fireEvent(emitter, 'add');
}
},
/**
* Remove emitter document events.
*/
removeDocEvents: function () {
if (this.removeDrag) {
this.removeDrag = this.removeDrag();
}
if (this.removeMouseUp) {
this.removeMouseUp = this.removeMouseUp();
}
},
/**
* Mouse down handler.
*/
onMouseDown: function (e) {
var emitter = this, pointer = emitter.chart.pointer, prevChartX, prevChartY;
if (e.preventDefault) {
e.preventDefault();
}
// On right click, do nothing:
if (e.button === 2) {
return;
}
e = pointer.normalize(e);
prevChartX = e.chartX;
prevChartY = e.chartY;
emitter.cancelClick = false;
emitter.chart.hasDraggedAnnotation = true;
emitter.removeDrag = addEvent(H.doc, H.isTouchDevice ? 'touchmove' : 'mousemove', function (e) {
emitter.hasDragged = true;
e = pointer.normalize(e);
e.prevChartX = prevChartX;
e.prevChartY = prevChartY;
fireEvent(emitter, 'drag', e);
prevChartX = e.chartX;
prevChartY = e.chartY;
});
emitter.removeMouseUp = addEvent(H.doc, H.isTouchDevice ? 'touchend' : 'mouseup', function (e) {
emitter.cancelClick = emitter.hasDragged;
emitter.hasDragged = false;
emitter.chart.hasDraggedAnnotation = false;
// ControlPoints vs Annotation:
fireEvent(pick(emitter.target, emitter), 'afterUpdate');
emitter.onMouseUp(e);
});
},
/**
* Mouse up handler.
*/
onMouseUp: function (_e) {
var chart = this.chart, annotation = this.target || this, annotationsOptions = chart.options.annotations, index = chart.annotations.indexOf(annotation);
this.removeDocEvents();
annotationsOptions[index] = annotation.options;
},
/**
* Drag and drop event. All basic annotations should share this
* capability as well as the extended ones.
*/
onDrag: function (e) {
if (this.chart.isInsidePlot(e.chartX - this.chart.plotLeft, e.chartY - this.chart.plotTop)) {
var translation = this.mouseMoveToTranslation(e);
if (this.options.draggable === 'x') {
translation.y = 0;
}
if (this.options.draggable === 'y') {
translation.x = 0;
}
if (this.points.length) {
this.translate(translation.x, translation.y);
}
else {
this.shapes.forEach(function (shape) {
shape.translate(translation.x, translation.y);
});
this.labels.forEach(function (label) {
label.translate(translation.x, translation.y);
});
}
this.redraw(false);
}
},
/**
* Map mouse move event to the radians.
*/
mouseMoveToRadians: function (e, cx, cy) {
var prevDy = e.prevChartY - cy, prevDx = e.prevChartX - cx, dy = e.chartY - cy, dx = e.chartX - cx, temp;
if (this.chart.inverted) {
temp = prevDx;
prevDx = prevDy;
prevDy = temp;
temp = dx;
dx = dy;
dy = temp;
}
return Math.atan2(dy, dx) - Math.atan2(prevDy, prevDx);
},
/**
* Map mouse move event to the distance between two following events.
*/
mouseMoveToTranslation: function (e) {
var dx = e.chartX - e.prevChartX, dy = e.chartY - e.prevChartY, temp;
if (this.chart.inverted) {
temp = dy;
dy = dx;
dx = temp;
}
return {
x: dx,
y: dy
};
},
/**
* Map mouse move to the scale factors.
*
* @param {Object} e event
* @param {number} cx center x
* @param {number} cy center y
**/
mouseMoveToScale: function (e, cx, cy) {
var prevDx = e.prevChartX - cx, prevDy = e.prevChartY - cy, dx = e.chartX - cx, dy = e.chartY - cy, sx = (dx || 1) / (prevDx || 1), sy = (dy || 1) / (prevDy || 1), temp;
if (this.chart.inverted) {
temp = sy;
sy = sx;
sx = temp;
}
return {
x: sx,
y: sy
};
},
/**
* Destroy the event emitter.
*/
destroy: function () {
this.removeDocEvents();
removeEvent(this);
this.hcEvents = null;
}
};
export default eventEmitterMixin;
@@ -0,0 +1,159 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Chart from '../../../Core/Chart/Chart.js';
import SVGRenderer from '../../../Core/Renderer/SVG/SVGRenderer.js';
import U from '../../../Core/Utilities.js';
var addEvent = U.addEvent, defined = U.defined, merge = U.merge, objectEach = U.objectEach, uniqueKey = U.uniqueKey;
/**
* Options for configuring markers for annotations.
*
* An example of the arrow marker:
* <pre>
* {
* arrow: {
* id: 'arrow',
* tagName: 'marker',
* refY: 5,
* refX: 5,
* markerWidth: 10,
* markerHeight: 10,
* children: [{
* tagName: 'path',
* attrs: {
* d: 'M 0 0 L 10 5 L 0 10 Z',
* strokeWidth: 0
* }
* }]
* }
* }
* </pre>
*
* @sample highcharts/annotations/custom-markers/
* Define a custom marker for annotations
*
* @sample highcharts/css/annotations-markers/
* Define markers in a styled mode
*
* @type {Highcharts.Dictionary<Highcharts.SVGDefinitionObject>}
* @since 6.0.0
* @optionparent defs
*/
var defaultMarkers = {
/**
* @type {Highcharts.SVGDefinitionObject}
*/
arrow: {
tagName: 'marker',
render: false,
id: 'arrow',
refY: 5,
refX: 9,
markerWidth: 10,
markerHeight: 10,
/**
* @type {Array<Highcharts.DefsOptions>}
*/
children: [{
tagName: 'path',
d: 'M 0 0 L 10 5 L 0 10 Z',
strokeWidth: 0
}]
},
/**
* @type {Highcharts.SVGDefinitionObject}
*/
'reverse-arrow': {
tagName: 'marker',
render: false,
id: 'reverse-arrow',
refY: 5,
refX: 1,
markerWidth: 10,
markerHeight: 10,
children: [{
tagName: 'path',
// reverse triangle (used as an arrow)
d: 'M 0 5 L 10 0 L 10 10 Z',
strokeWidth: 0
}]
}
};
SVGRenderer.prototype.addMarker = function (id, markerOptions) {
var options = { id: id };
var attrs = {
stroke: markerOptions.color || 'none',
fill: markerOptions.color || 'rgba(0, 0, 0, 0.75)'
};
options.children = markerOptions.children.map(function (child) {
return merge(attrs, child);
});
var marker = this.definition(merge(true, {
markerWidth: 20,
markerHeight: 20,
refX: 0,
refY: 0,
orient: 'auto'
}, markerOptions, options));
marker.id = id;
return marker;
};
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* @private
*/
function createMarkerSetter(markerType) {
return function (value) {
this.attr(markerType, 'url(#' + value + ')');
};
}
/**
* @private
* @mixin
* @name Highcharts.AnnotaitonMarkerMixin
*/
var markerMixin = {
markerEndSetter: createMarkerSetter('marker-end'),
markerStartSetter: createMarkerSetter('marker-start'),
/**
* Set markers.
* @private
* @param {Highcharts.AnnotationControllablePath} item
*/
setItemMarkers: function (item) {
var itemOptions = item.options, chart = item.chart, defs = chart.options.defs, fill = itemOptions.fill, color = defined(fill) && fill !== 'none' ?
fill :
itemOptions.stroke, setMarker = function (markerType) {
var markerId = itemOptions[markerType], def, predefinedMarker, key, marker;
if (markerId) {
for (key in defs) { // eslint-disable-line guard-for-in
def = defs[key];
if (markerId === def.id &&
def.tagName === 'marker') {
predefinedMarker = def;
break;
}
}
if (predefinedMarker) {
marker = item[markerType] = chart.renderer
.addMarker((itemOptions.id || uniqueKey()) + '-' +
predefinedMarker.id, merge(predefinedMarker, { color: color }));
item.attr(markerType, marker.attr('id'));
}
}
};
['markerStart', 'markerEnd'].forEach(setMarker);
}
};
addEvent(Chart, 'afterGetContainer', function () {
this.options.defs = merge(defaultMarkers, this.options.defs || {});
objectEach(this.options.defs, function (def) {
if (def.tagName === 'marker' && def.render !== false) {
this.renderer.addMarker(def.id, def);
}
}, this);
});
export default markerMixin;
@@ -0,0 +1,426 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
import H from '../../Core/Globals.js';
/**
* @private
* @interface Highcharts.AnnotationMockLabelOptionsObject
*/ /**
* Point instance of the point.
* @name Highcharts.AnnotationMockLabelOptionsObject#point
* @type {Highcharts.AnnotationMockPoint}
*/ /**
* X value translated to x axis scale.
* @name Highcharts.AnnotationMockLabelOptionsObject#x
* @type {number|null}
*/ /**
* Y value translated to y axis scale.
* @name Highcharts.AnnotationMockLabelOptionsObject#y
* @type {number|null}
*/
/**
* A mock series instance imitating a real series from a real point.
* @private
* @interface Highcharts.AnnotationMockSeries
*/ /**
* Whether a series is visible.
* @name Highcharts.AnnotationMockSeries#visible
* @type {boolean}
*/ /**
* A chart instance.
* @name Highcharts.AnnotationMockSeries#chart
* @type {Highcharts.Chart}
*/ /**
* @name Highcharts.AnnotationMockSeries#getPlotBox
* @type {Function}
*/
/**
* Indicates if this is a mock point for an annotation.
* @name Highcharts.Point#mock
* @type {boolean|undefined}
*/
import U from '../../Core/Utilities.js';
var defined = U.defined, extend = U.extend, fireEvent = U.fireEvent;
import '../../Core/Axis/Axis.js';
import '../../Core/Series/Series.js';
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* A trimmed point object which imitates {@link Highchart.Point} class. It is
* created when there is a need of pointing to some chart's position using axis
* values or pixel values
*
* @requires modules/annotations
*
* @private
* @class
* @name Highcharts.AnnotationMockPoint
*
* @hideconstructor
*
* @param {Highcharts.Chart} chart
* The chart instance.
*
* @param {Highcharts.AnnotationControllable|null} target
* The related controllable.
*
* @param {Highcharts.AnnotationMockPointOptionsObject|Function} options
* The options object.
*/
var MockPoint = /** @class */ (function () {
function MockPoint(chart, target, options) {
this.isInside = void 0;
this.plotX = void 0;
this.plotY = void 0;
this.x = void 0;
this.y = void 0;
/* *
*
* Functions
*
* */
/**
* A flag indicating that a point is not the real one.
*
* @type {boolean}
* @default true
*/
this.mock = true;
/**
* A mock series instance imitating a real series from a real point.
*
* @name Annotation.AnnotationMockPoint#series
* @type {Highcharts.AnnotationMockSeries}
*/
this.series = {
visible: true,
chart: chart,
getPlotBox: H.Series.prototype.getPlotBox
};
/**
* @name Annotation.AnnotationMockPoint#target
* @type {Highcharts.AnnotationControllable|null}
*/
this.target = target || null;
/**
* Options for the mock point.
*
* @name Annotation.AnnotationMockPoint#options
* @type {Highcharts.AnnotationsMockPointOptionsObject}
*/
this.options = options;
/**
* If an xAxis is set it represents the point's value in terms of the
* xAxis.
*
* @name Annotation.AnnotationMockPoint#x
* @type {number|undefined}
*/
/**
* If an yAxis is set it represents the point's value in terms of the
* yAxis.
*
* @name Annotation.AnnotationMockPoint#y
* @type {number|undefined}
*/
/**
* It represents the point's pixel x coordinate relative to its plot
* box.
*
* @name Annotation.AnnotationMockPoint#plotX
* @type {number|undefined}
*/
/**
* It represents the point's pixel y position relative to its plot box.
*
* @name Annotation.AnnotationMockPoint#plotY
* @type {number|undefined}
*/
/**
* Whether the point is inside the plot box.
*
* @name Annotation.AnnotationMockPoint#isInside
* @type {boolean|undefined}
*/
this.applyOptions(this.getOptions());
}
/**
* Create a mock point from a real Highcharts point.
*
* @private
* @static
*
* @param {Highcharts.Point} point
*
* @return {Highcharts.AnnotationMockPoint}
* A mock point instance.
*/
MockPoint.fromPoint = function (point) {
return new MockPoint(point.series.chart, null, {
x: point.x,
y: point.y,
xAxis: point.series.xAxis,
yAxis: point.series.yAxis
});
};
/**
* Get the pixel position from the point like object.
*
* @private
* @static
*
* @param {Highcharts.AnnotationPointType} point
*
* @param {boolean} [paneCoordinates]
* whether the pixel position should be relative
*
* @return {Highcharts.PositionObject} pixel position
*/
MockPoint.pointToPixels = function (point, paneCoordinates) {
var series = point.series, chart = series.chart, x = point.plotX, y = point.plotY, plotBox;
if (chart.inverted) {
if (point.mock) {
x = point.plotY;
y = point.plotX;
}
else {
x = chart.plotWidth - point.plotY;
y = chart.plotHeight - point.plotX;
}
}
if (series && !paneCoordinates) {
plotBox = series.getPlotBox();
x += plotBox.translateX;
y += plotBox.translateY;
}
return {
x: x,
y: y
};
};
/**
* Get fresh mock point options from the point like object.
*
* @private
* @static
*
* @param {Highcharts.AnnotationPointType} point
*
* @return {Highcharts.AnnotationMockPointOptionsObject}
* A mock point's options.
*/
MockPoint.pointToOptions = function (point) {
return {
x: point.x,
y: point.y,
xAxis: point.series.xAxis,
yAxis: point.series.yAxis
};
};
/**
* Check if the point has dynamic options.
* @private
* @return {boolean}
* A positive flag if the point has dynamic options.
*/
MockPoint.prototype.hasDynamicOptions = function () {
return typeof this.options === 'function';
};
/**
* Get the point's options.
* @private
* @return {Highcharts.AnnotationMockPointOptionsObject}
* The mock point's options.
*/
MockPoint.prototype.getOptions = function () {
return this.hasDynamicOptions() ?
this.options(this.target) :
this.options;
};
/**
* Apply options for the point.
* @private
* @param {Highcharts.AnnotationMockPointOptionsObject} options
*/
MockPoint.prototype.applyOptions = function (options) {
this.command = options.command;
this.setAxis(options, 'x');
this.setAxis(options, 'y');
this.refresh();
};
/**
* Set x or y axis.
* @private
* @param {Highcharts.AnnotationMockPointOptionsObject} options
* @param {string} xOrY
* 'x' or 'y' string literal
*/
MockPoint.prototype.setAxis = function (options, xOrY) {
var axisName = (xOrY + 'Axis'), axisOptions = options[axisName], chart = this.series.chart;
this.series[axisName] =
axisOptions instanceof H.Axis ?
axisOptions :
defined(axisOptions) ?
(chart[axisName][axisOptions] ||
chart.get(axisOptions)) :
null;
};
/**
* Transform the mock point to an anchor (relative position on the chart).
* @private
* @return {Array<number>}
* A quadruple of numbers which denotes x, y, width and height of the box
**/
MockPoint.prototype.toAnchor = function () {
var anchor = [this.plotX, this.plotY, 0, 0];
if (this.series.chart.inverted) {
anchor[0] = this.plotY;
anchor[1] = this.plotX;
}
return anchor;
};
/**
* Returns a label config object - the same as
* Highcharts.Point.prototype.getLabelConfig
* @private
* @return {Highcharts.AnnotationMockLabelOptionsObject} the point's label config
*/
MockPoint.prototype.getLabelConfig = function () {
return {
x: this.x,
y: this.y,
point: this
};
};
/**
* Check if the point is inside its pane.
* @private
* @return {boolean} A flag indicating whether the point is inside the pane.
*/
MockPoint.prototype.isInsidePlot = function () {
var plotX = this.plotX, plotY = this.plotY, xAxis = this.series.xAxis, yAxis = this.series.yAxis, e = {
x: plotX,
y: plotY,
isInsidePlot: true
};
if (xAxis) {
e.isInsidePlot = defined(plotX) && plotX >= 0 && plotX <= xAxis.len;
}
if (yAxis) {
e.isInsidePlot =
e.isInsidePlot &&
defined(plotY) &&
plotY >= 0 && plotY <= yAxis.len;
}
fireEvent(this.series.chart, 'afterIsInsidePlot', e);
return e.isInsidePlot;
};
/**
* Refresh point values and coordinates based on its options.
* @private
*/
MockPoint.prototype.refresh = function () {
var series = this.series, xAxis = series.xAxis, yAxis = series.yAxis, options = this.getOptions();
if (xAxis) {
this.x = options.x;
this.plotX = xAxis.toPixels(options.x, true);
}
else {
this.x = null;
this.plotX = options.x;
}
if (yAxis) {
this.y = options.y;
this.plotY = yAxis.toPixels(options.y, true);
}
else {
this.y = null;
this.plotY = options.y;
}
this.isInside = this.isInsidePlot();
};
/**
* Translate the point.
*
* @private
*
* @param {number|undefined} cx
* Origin x transformation.
*
* @param {number|undefined} cy
* Origin y transformation.
*
* @param {number} dx
* Translation for x coordinate.
*
* @param {number} dy
* Translation for y coordinate.
**/
MockPoint.prototype.translate = function (_cx, _cy, dx, dy) {
if (!this.hasDynamicOptions()) {
this.plotX += dx;
this.plotY += dy;
this.refreshOptions();
}
};
/**
* Scale the point.
*
* @private
*
* @param {number} cx
* Origin x transformation.
*
* @param {number} cy
* Origin y transformation.
*
* @param {number} sx
* Scale factor x.
*
* @param {number} sy
* Scale factor y.
*/
MockPoint.prototype.scale = function (cx, cy, sx, sy) {
if (!this.hasDynamicOptions()) {
var x = this.plotX * sx, y = this.plotY * sy, tx = (1 - sx) * cx, ty = (1 - sy) * cy;
this.plotX = tx + x;
this.plotY = ty + y;
this.refreshOptions();
}
};
/**
* Rotate the point.
* @private
* @param {number} cx origin x rotation
* @param {number} cy origin y rotation
* @param {number} radians
*/
MockPoint.prototype.rotate = function (cx, cy, radians) {
if (!this.hasDynamicOptions()) {
var cos = Math.cos(radians), sin = Math.sin(radians), x = this.plotX, y = this.plotY, tx, ty;
x -= cx;
y -= cy;
tx = x * cos - y * sin;
ty = x * sin + y * cos;
this.plotX = tx + cx;
this.plotY = ty + cy;
this.refreshOptions();
}
};
/**
* Refresh point options based on its plot coordinates.
* @private
*/
MockPoint.prototype.refreshOptions = function () {
var series = this.series, xAxis = series.xAxis, yAxis = series.yAxis;
this.x = this.options.x = xAxis ?
this.options.x = xAxis.toValue(this.plotX, true) :
this.plotX;
this.y = this.options.y = yAxis ?
yAxis.toValue(this.plotY, true) :
this.plotY;
};
return MockPoint;
}());
export default MockPoint;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,733 @@
/* *
*
* Popup generator for Stock tools
*
* (c) 2009-2017 Sebastian Bochan
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../../Core/Globals.js';
import NavigationBindings from './NavigationBindings.js';
import Pointer from '../../Core/Pointer.js';
import U from '../../Core/Utilities.js';
var addEvent = U.addEvent, createElement = U.createElement, defined = U.defined, getOptions = U.getOptions, isArray = U.isArray, isObject = U.isObject, isString = U.isString, objectEach = U.objectEach, pick = U.pick, wrap = U.wrap;
var indexFilter = /\d/g, PREFIX = 'highcharts-', DIV = 'div', INPUT = 'input', LABEL = 'label', BUTTON = 'button', SELECT = 'select', OPTION = 'option', SPAN = 'span', UL = 'ul', LI = 'li', H3 = 'h3';
/* eslint-disable no-invalid-this, valid-jsdoc */
// onContainerMouseDown blocks internal popup events, due to e.preventDefault.
// Related issue #4606
wrap(Pointer.prototype, 'onContainerMouseDown', function (proceed, e) {
var popupClass = e.target && e.target.className;
// elements is not in popup
if (!(isString(popupClass) &&
popupClass.indexOf(PREFIX + 'popup-field') >= 0)) {
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
}
});
H.Popup = function (parentDiv, iconsURL) {
this.init(parentDiv, iconsURL);
};
H.Popup.prototype = {
/**
* Initialize the popup. Create base div and add close button.
* @private
* @param {Highcharts.HTMLDOMElement} parentDiv
* Container where popup should be placed
* @param {string} iconsURL
* Icon URL
*/
init: function (parentDiv, iconsURL) {
// create popup div
this.container = createElement(DIV, {
className: PREFIX + 'popup'
}, null, parentDiv);
this.lang = this.getLangpack();
this.iconsURL = iconsURL;
// add close button
this.addCloseBtn();
},
/**
* Create HTML element and attach click event (close popup).
* @private
*/
addCloseBtn: function () {
var _self = this, closeBtn;
// create close popup btn
closeBtn = createElement(DIV, {
className: PREFIX + 'popup-close'
}, null, this.container);
closeBtn.style['background-image'] = 'url(' +
this.iconsURL + 'close.svg)';
['click', 'touchstart'].forEach(function (eventName) {
addEvent(closeBtn, eventName, function () {
_self.closePopup();
});
});
},
/**
* Create two columns (divs) in HTML.
* @private
* @param {Highcharts.HTMLDOMElement} container
* Container of columns
* @return {Highcharts.Dictionary<Highcharts.HTMLDOMElement>}
* Reference to two HTML columns (lhsCol, rhsCol)
*/
addColsContainer: function (container) {
var rhsCol, lhsCol;
// left column
lhsCol = createElement(DIV, {
className: PREFIX + 'popup-lhs-col'
}, null, container);
// right column
rhsCol = createElement(DIV, {
className: PREFIX + 'popup-rhs-col'
}, null, container);
// wrapper content
createElement(DIV, {
className: PREFIX + 'popup-rhs-col-wrapper'
}, null, rhsCol);
return {
lhsCol: lhsCol,
rhsCol: rhsCol
};
},
/**
* Create input with label.
* @private
* @param {string} option
* Chain of fields i.e params.styles.fontSize
* @param {string} type
* Indicator type
* @param {Highhcharts.HTMLDOMElement}
* Container where elements should be added
* @param {string} value
* Default value of input i.e period value is 14, extracted from
* defaultOptions (ADD mode) or series options (EDIT mode)
*/
addInput: function (option, type, parentDiv, value) {
var optionParamList = option.split('.'), optionName = optionParamList[optionParamList.length - 1], lang = this.lang, inputName = PREFIX + type + '-' + optionName;
if (!inputName.match(indexFilter)) {
// add label
createElement(LABEL, {
innerHTML: lang[optionName] || optionName,
htmlFor: inputName
}, null, parentDiv);
}
// add input
createElement(INPUT, {
name: inputName,
value: value[0],
type: value[1],
className: PREFIX + 'popup-field'
}, null, parentDiv).setAttribute(PREFIX + 'data-name', option);
},
/**
* Create button.
* @private
* @param {Highcharts.HTMLDOMElement} parentDiv
* Container where elements should be added
* @param {string} label
* Text placed as button label
* @param {string} type
* add | edit | remove
* @param {Function} callback
* On click callback
* @param {Highcharts.HTMLDOMElement} fieldsDiv
* Container where inputs are generated
* @return {Highcharts.HTMLDOMElement}
* HTML button
*/
addButton: function (parentDiv, label, type, callback, fieldsDiv) {
var _self = this, closePopup = this.closePopup, getFields = this.getFields, button;
button = createElement(BUTTON, {
innerHTML: label
}, null, parentDiv);
['click', 'touchstart'].forEach(function (eventName) {
addEvent(button, eventName, function () {
closePopup.call(_self);
return callback(getFields(fieldsDiv, type));
});
});
return button;
},
/**
* Get values from all inputs and create JSON.
* @private
* @param {Highcharts.HTMLDOMElement} - container where inputs are created
* @param {string} - add | edit | remove
* @return {Highcharts.PopupFieldsObject} - fields
*/
getFields: function (parentDiv, type) {
var inputList = parentDiv.querySelectorAll('input'), optionSeries = '#' + PREFIX + 'select-series > option:checked', optionVolume = '#' + PREFIX + 'select-volume > option:checked', linkedTo = parentDiv.querySelectorAll(optionSeries)[0], volumeTo = parentDiv.querySelectorAll(optionVolume)[0], seriesId, param, fieldsOutput;
fieldsOutput = {
actionType: type,
linkedTo: linkedTo && linkedTo.getAttribute('value'),
fields: {}
};
[].forEach.call(inputList, function (input) {
param = input.getAttribute(PREFIX + 'data-name');
seriesId = input.getAttribute(PREFIX + 'data-series-id');
// params
if (seriesId) {
fieldsOutput.seriesId = input.value;
}
else if (param) {
fieldsOutput.fields[param] = input.value;
}
else {
// type like sma / ema
fieldsOutput.type = input.value;
}
});
if (volumeTo) {
fieldsOutput.fields['params.volumeSeriesID'] = volumeTo.getAttribute('value');
}
return fieldsOutput;
},
/**
* Reset content of the current popup and show.
* @private
*/
showPopup: function () {
var popupDiv = this.container, toolbarClass = PREFIX + 'annotation-toolbar', popupCloseBtn = popupDiv
.querySelectorAll('.' + PREFIX + 'popup-close')[0];
// reset content
popupDiv.innerHTML = '';
// reset toolbar styles if exists
if (popupDiv.className.indexOf(toolbarClass) >= 0) {
popupDiv.classList.remove(toolbarClass);
// reset toolbar inline styles
popupDiv.removeAttribute('style');
}
// add close button
popupDiv.appendChild(popupCloseBtn);
popupDiv.style.display = 'block';
},
/**
* Hide popup.
* @private
*/
closePopup: function () {
this.popup.container.style.display = 'none';
},
/**
* Create content and show popup.
* @private
* @param {string} - type of popup i.e indicators
* @param {Highcharts.Chart} - chart
* @param {Highcharts.AnnotationsOptions} - options
* @param {Function} - on click callback
*/
showForm: function (type, chart, options, callback) {
this.popup = chart.navigationBindings.popup;
// show blank popup
this.showPopup();
// indicator form
if (type === 'indicators') {
this.indicators.addForm.call(this, chart, options, callback);
}
// annotation small toolbar
if (type === 'annotation-toolbar') {
this.annotations.addToolbar.call(this, chart, options, callback);
}
// annotation edit form
if (type === 'annotation-edit') {
this.annotations.addForm.call(this, chart, options, callback);
}
// flags form - add / edit
if (type === 'flag') {
this.annotations.addForm.call(this, chart, options, callback, true);
}
},
/**
* Return lang definitions for popup.
* @private
* @return {Highcharts.Dictionary<string>} - elements translations.
*/
getLangpack: function () {
return getOptions().lang.navigation.popup;
},
annotations: {
/**
* Create annotation simple form. It contains two buttons
* (edit / remove) and text label.
* @private
* @param {Highcharts.Chart} - chart
* @param {Highcharts.AnnotationsOptions} - options
* @param {Function} - on click callback
*/
addToolbar: function (chart, options, callback) {
var _self = this, lang = this.lang, popupDiv = this.popup.container, showForm = this.showForm, toolbarClass = PREFIX + 'annotation-toolbar', button;
// set small size
if (popupDiv.className.indexOf(toolbarClass) === -1) {
popupDiv.className += ' ' + toolbarClass;
}
// set position
popupDiv.style.top = chart.plotTop + 10 + 'px';
// create label
createElement(SPAN, {
innerHTML: pick(
// Advanced annotations:
lang[options.langKey] || options.langKey,
// Basic shapes:
options.shapes && options.shapes[0].type)
}, null, popupDiv);
// add buttons
button = this.addButton(popupDiv, lang.removeButton || 'remove', 'remove', callback, popupDiv);
button.className += ' ' + PREFIX + 'annotation-remove-button';
button.style['background-image'] = 'url(' +
this.iconsURL + 'destroy.svg)';
button = this.addButton(popupDiv, lang.editButton || 'edit', 'edit', function () {
showForm.call(_self, 'annotation-edit', chart, options, callback);
}, popupDiv);
button.className += ' ' + PREFIX + 'annotation-edit-button';
button.style['background-image'] = 'url(' +
this.iconsURL + 'edit.svg)';
},
/**
* Create annotation simple form.
* It contains fields with param names.
* @private
* @param {Highcharts.Chart} chart
* Chart
* @param {Object} options
* Options
* @param {Function} callback
* On click callback
* @param {boolean} [isInit]
* If it is a form declared for init annotation
*/
addForm: function (chart, options, callback, isInit) {
var popupDiv = this.popup.container, lang = this.lang, bottomRow, lhsCol;
// create title of annotations
lhsCol = createElement('h2', {
innerHTML: lang[options.langKey] || options.langKey,
className: PREFIX + 'popup-main-title'
}, null, popupDiv);
// left column
lhsCol = createElement(DIV, {
className: PREFIX + 'popup-lhs-col ' + PREFIX + 'popup-lhs-full'
}, null, popupDiv);
bottomRow = createElement(DIV, {
className: PREFIX + 'popup-bottom-row'
}, null, popupDiv);
this.annotations.addFormFields.call(this, lhsCol, chart, '', options, [], true);
this.addButton(bottomRow, isInit ?
(lang.addButton || 'add') :
(lang.saveButton || 'save'), isInit ? 'add' : 'save', callback, popupDiv);
},
/**
* Create annotation's form fields.
* @private
* @param {Highcharts.HTMLDOMElement} parentDiv
* Div where inputs are placed
* @param {Highcharts.Chart} chart
* Chart
* @param {string} parentNode
* Name of parent to create chain of names
* @param {Highcharts.AnnotationsOptions} options
* Options
* @param {Array<unknown>} storage
* Array where all items are stored
* @param {boolean} [isRoot]
* Recursive flag for root
*/
addFormFields: function (parentDiv, chart, parentNode, options, storage, isRoot) {
var _self = this, addFormFields = this.annotations.addFormFields, addInput = this.addInput, lang = this.lang, parentFullName, titleName;
objectEach(options, function (value, option) {
// create name like params.styles.fontSize
parentFullName = parentNode !== '' ?
parentNode + '.' + option : option;
if (isObject(value)) {
if (
// value is object of options
!isArray(value) ||
// array of objects with params. i.e labels in Fibonacci
(isArray(value) && isObject(value[0]))) {
titleName = lang[option] || option;
if (!titleName.match(indexFilter)) {
storage.push([
true,
titleName,
parentDiv
]);
}
addFormFields.call(_self, parentDiv, chart, parentFullName, value, storage, false);
}
else {
storage.push([
_self,
parentFullName,
'annotation',
parentDiv,
value
]);
}
}
});
if (isRoot) {
storage = storage.sort(function (a) {
return a[1].match(/format/g) ? -1 : 1;
});
storage.forEach(function (genInput) {
if (genInput[0] === true) {
createElement(SPAN, {
className: PREFIX + 'annotation-title',
innerHTML: genInput[1]
}, null, genInput[2]);
}
else {
addInput.apply(genInput[0], genInput.splice(1));
}
});
}
}
},
indicators: {
/**
* Create indicator's form. It contains two tabs (ADD and EDIT) with
* content.
* @private
*/
addForm: function (chart, _options, callback) {
var tabsContainers, indicators = this.indicators, lang = this.lang, buttonParentDiv;
// add tabs
this.tabs.init.call(this, chart);
// get all tabs content divs
tabsContainers = this.popup.container
.querySelectorAll('.' + PREFIX + 'tab-item-content');
// ADD tab
this.addColsContainer(tabsContainers[0]);
indicators.addIndicatorList.call(this, chart, tabsContainers[0], 'add');
buttonParentDiv = tabsContainers[0]
.querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0];
this.addButton(buttonParentDiv, lang.addButton || 'add', 'add', callback, buttonParentDiv);
// EDIT tab
this.addColsContainer(tabsContainers[1]);
indicators.addIndicatorList.call(this, chart, tabsContainers[1], 'edit');
buttonParentDiv = tabsContainers[1]
.querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0];
this.addButton(buttonParentDiv, lang.saveButton || 'save', 'edit', callback, buttonParentDiv);
this.addButton(buttonParentDiv, lang.removeButton || 'remove', 'remove', callback, buttonParentDiv);
},
/**
* Create HTML list of all indicators (ADD mode) or added indicators
* (EDIT mode).
* @private
*/
addIndicatorList: function (chart, parentDiv, listType) {
var _self = this, lhsCol = parentDiv.querySelectorAll('.' + PREFIX + 'popup-lhs-col')[0], rhsCol = parentDiv.querySelectorAll('.' + PREFIX + 'popup-rhs-col')[0], isEdit = listType === 'edit', series = (isEdit ?
chart.series : // EDIT mode
chart.options.plotOptions // ADD mode
), addFormFields = this.indicators.addFormFields, rhsColWrapper, indicatorList, item;
// create wrapper for list
indicatorList = createElement(UL, {
className: PREFIX + 'indicator-list'
}, null, lhsCol);
rhsColWrapper = rhsCol
.querySelectorAll('.' + PREFIX + 'popup-rhs-col-wrapper')[0];
objectEach(series, function (serie, value) {
var seriesOptions = serie.options;
if (serie.params ||
seriesOptions && seriesOptions.params) {
var indicatorNameType = _self.indicators.getNameType(serie, value), indicatorType = indicatorNameType.type;
item = createElement(LI, {
className: PREFIX + 'indicator-list',
innerHTML: indicatorNameType.name
}, null, indicatorList);
['click', 'touchstart'].forEach(function (eventName) {
addEvent(item, eventName, function () {
addFormFields.call(_self, chart, isEdit ? serie : series[indicatorType], indicatorNameType.type, rhsColWrapper);
// add hidden input with series.id
if (isEdit && serie.options) {
createElement(INPUT, {
type: 'hidden',
name: PREFIX + 'id-' + indicatorType,
value: serie.options.id
}, null, rhsColWrapper)
.setAttribute(PREFIX + 'data-series-id', serie.options.id);
}
});
});
}
});
// select first item from the list
if (indicatorList.childNodes.length > 0) {
indicatorList.childNodes[0].click();
}
},
/**
* Extract full name and type of requested indicator.
* @private
* @param {Highcharts.Series} series
* Series which name is needed. (EDIT mode - defaultOptions.series, ADD
* mode - indicator series).
* @param {string} - indicator type like: sma, ema, etc.
* @return {Object} - series name and type like: sma, ema, etc.
*/
getNameType: function (series, type) {
var options = series.options, seriesTypes = H.seriesTypes,
// add mode
seriesName = seriesTypes[type] &&
seriesTypes[type].prototype.nameBase || type.toUpperCase(), seriesType = type;
// edit
if (options && options.type) {
seriesType = series.options.type;
seriesName = series.name;
}
return {
name: seriesName,
type: seriesType
};
},
/**
* List all series with unique ID. Its mandatory for indicators to set
* correct linking.
* @private
* @param {string} type
* Indicator type like: sma, ema, etc.
* @param {string} optionName
* Type of select i.e series or volume.
* @param {Highcharts.Chart} chart
* Chart
* @param {Highcharts.HTMLDOMElement} parentDiv
* Element where created HTML list is added
* @param {string} selectedOption
* optional param for default value in dropdown
*/
listAllSeries: function (type, optionName, chart, parentDiv, selectedOption) {
var selectName = PREFIX + optionName + '-type-' + type, lang = this.lang, selectBox, seriesOptions;
createElement(LABEL, {
innerHTML: lang[optionName] || optionName,
htmlFor: selectName
}, null, parentDiv);
// select type
selectBox = createElement(SELECT, {
name: selectName,
className: PREFIX + 'popup-field'
}, null, parentDiv);
selectBox.setAttribute('id', PREFIX + 'select-' + optionName);
// list all series which have id - mandatory for creating indicator
chart.series.forEach(function (serie) {
seriesOptions = serie.options;
if (!seriesOptions.params &&
seriesOptions.id &&
seriesOptions.id !== PREFIX + 'navigator-series') {
createElement(OPTION, {
innerHTML: seriesOptions.name || seriesOptions.id,
value: seriesOptions.id
}, null, selectBox);
}
});
if (defined(selectedOption)) {
selectBox.value = selectedOption;
}
},
/**
* Create typical inputs for chosen indicator. Fields are extracted from
* defaultOptions (ADD mode) or current indicator (ADD mode). Two extra
* fields are added:
* - hidden input - contains indicator type (required for callback)
* - select - list of series which can be linked with indicator
* @private
* @param {Highcharts.Chart} chart
* Chart
* @param {Highcharts.Series} series
* Indicator
* @param {string} seriesType
* Indicator type like: sma, ema, etc.
* @param {Highcharts.HTMLDOMElement} rhsColWrapper
* Element where created HTML list is added
*/
addFormFields: function (chart, series, seriesType, rhsColWrapper) {
var fields = series.params || series.options.params, getNameType = this.indicators.getNameType;
// reset current content
rhsColWrapper.innerHTML = '';
// create title (indicator name in the right column)
createElement(H3, {
className: PREFIX + 'indicator-title',
innerHTML: getNameType(series, seriesType).name
}, null, rhsColWrapper);
// input type
createElement(INPUT, {
type: 'hidden',
name: PREFIX + 'type-' + seriesType,
value: seriesType
}, null, rhsColWrapper);
// list all series with id
this.indicators.listAllSeries.call(this, seriesType, 'series', chart, rhsColWrapper, series.linkedParent && fields.volumeSeriesID);
if (fields.volumeSeriesID) {
this.indicators.listAllSeries.call(this, seriesType, 'volume', chart, rhsColWrapper, series.linkedParent && series.linkedParent.options.id);
}
// add param fields
this.indicators.addParamInputs.call(this, chart, 'params', fields, seriesType, rhsColWrapper);
},
/**
* Recurent function which lists all fields, from params object and
* create them as inputs. Each input has unique `data-name` attribute,
* which keeps chain of fields i.e params.styles.fontSize.
* @private
* @param {Highcharts.Chart} chart
* Chart
* @param {string} parentNode
* Name of parent to create chain of names
* @param {Highcharts.PopupFieldsDictionary<string>} fields
* Params which are based for input create
* @param {string} type
* Indicator type like: sma, ema, etc.
* @param {Highcharts.HTMLDOMElement} parentDiv
* Element where created HTML list is added
*/
addParamInputs: function (chart, parentNode, fields, type, parentDiv) {
var _self = this, addParamInputs = this.indicators.addParamInputs, addInput = this.addInput, parentFullName;
objectEach(fields, function (value, fieldName) {
// create name like params.styles.fontSize
parentFullName = parentNode + '.' + fieldName;
if (isObject(value)) {
addParamInputs.call(_self, chart, parentFullName, value, type, parentDiv);
}
else if (
// skip volume field which is created by addFormFields
parentFullName !== 'params.volumeSeriesID') {
addInput.call(_self, parentFullName, type, parentDiv, [value, 'text'] // all inputs are text type
);
}
});
},
/**
* Get amount of indicators added to chart.
* @private
* @return {number} - Amount of indicators
*/
getAmount: function () {
var series = this.series, counter = 0;
series.forEach(function (serie) {
var seriesOptions = serie.options;
if (serie.params ||
seriesOptions && seriesOptions.params) {
counter++;
}
});
return counter;
}
},
tabs: {
/**
* Init tabs. Create tab menu items, tabs containers
* @private
* @param {Highcharts.Chart} chart
* Reference to current chart
*/
init: function (chart) {
var tabs = this.tabs, indicatorsCount = this.indicators.getAmount.call(chart), firstTab; // run by default
// create menu items
firstTab = tabs.addMenuItem.call(this, 'add');
tabs.addMenuItem.call(this, 'edit', indicatorsCount);
// create tabs containers
tabs.addContentItem.call(this, 'add');
tabs.addContentItem.call(this, 'edit');
tabs.switchTabs.call(this, indicatorsCount);
// activate first tab
tabs.selectTab.call(this, firstTab, 0);
},
/**
* Create tab menu item
* @private
* @param {string} tabName
* `add` or `edit`
* @param {number} [disableTab]
* Disable tab when 0
* @return {Highcharts.HTMLDOMElement}
* Created HTML tab-menu element
*/
addMenuItem: function (tabName, disableTab) {
var popupDiv = this.popup.container, className = PREFIX + 'tab-item', lang = this.lang, menuItem;
if (disableTab === 0) {
className += ' ' + PREFIX + 'tab-disabled';
}
// tab 1
menuItem = createElement(SPAN, {
innerHTML: lang[tabName + 'Button'] || tabName,
className: className
}, null, popupDiv);
menuItem.setAttribute(PREFIX + 'data-tab-type', tabName);
return menuItem;
},
/**
* Create tab content
* @private
* @return {HTMLDOMElement} - created HTML tab-content element
*/
addContentItem: function () {
var popupDiv = this.popup.container;
return createElement(DIV, {
className: PREFIX + 'tab-item-content'
}, null, popupDiv);
},
/**
* Add click event to each tab
* @private
* @param {number} disableTab
* Disable tab when 0
*/
switchTabs: function (disableTab) {
var _self = this, popupDiv = this.popup.container, tabs = popupDiv.querySelectorAll('.' + PREFIX + 'tab-item'), dataParam;
tabs.forEach(function (tab, i) {
dataParam = tab.getAttribute(PREFIX + 'data-tab-type');
if (dataParam === 'edit' && disableTab === 0) {
return;
}
['click', 'touchstart'].forEach(function (eventName) {
addEvent(tab, eventName, function () {
// reset class on other elements
_self.tabs.deselectAll.call(_self);
_self.tabs.selectTab.call(_self, this, i);
});
});
});
},
/**
* Set tab as visible
* @private
* @param {globals.Element} - current tab
* @param {number} - Index of tab in menu
*/
selectTab: function (tab, index) {
var allTabs = this.popup.container
.querySelectorAll('.' + PREFIX + 'tab-item-content');
tab.className += ' ' + PREFIX + 'tab-item-active';
allTabs[index].className += ' ' + PREFIX + 'tab-item-show';
},
/**
* Set all tabs as invisible.
* @private
*/
deselectAll: function () {
var popupDiv = this.popup.container, tabs = popupDiv
.querySelectorAll('.' + PREFIX + 'tab-item'), tabsContent = popupDiv
.querySelectorAll('.' + PREFIX + 'tab-item-content'), i;
for (i = 0; i < tabs.length; i++) {
tabs[i].classList.remove(PREFIX + 'tab-item-active');
tabsContent[i].classList.remove(PREFIX + 'tab-item-show');
}
}
}
};
addEvent(NavigationBindings, 'showPopup', function (config) {
if (!this.popup) {
// Add popup to main container
this.popup = new H.Popup(this.chart.container, (this.chart.options.navigation.iconsURL ||
(this.chart.options.stockTools &&
this.chart.options.stockTools.gui.iconsURL) ||
'https://code.highcharts.com/8.2.0/gfx/stock-icons/'));
}
this.popup.showForm(config.formType, this.chart, config.options, config.onSubmit);
});
addEvent(NavigationBindings, 'closePopup', function () {
if (this.popup) {
this.popup.closePopup();
}
});
@@ -0,0 +1,178 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import Annotation from '../Annotations.js';
import MockPoint from '../MockPoint.js';
import U from '../../../Core/Utilities.js';
var merge = U.merge;
/* eslint-disable no-invalid-this */
var BasicAnnotation = /** @class */ (function (_super) {
__extends(BasicAnnotation, _super);
/* *
*
* Constructors
*
* */
function BasicAnnotation(chart, options) {
return _super.call(this, chart, options) || this;
}
/* *
*
* Functions
*
* */
BasicAnnotation.prototype.addControlPoints = function () {
var options = this.options, controlPoints = BasicAnnotation.basicControlPoints, annotationType = this.basicType, optionsGroup = options.labels || options.shapes;
optionsGroup.forEach(function (group) {
group.controlPoints = controlPoints[annotationType];
});
};
BasicAnnotation.prototype.init = function () {
var options = this.options;
if (options.shapes) {
delete options.labelOptions;
if (options.shapes[0].type === 'circle') {
this.basicType = 'circle';
}
else {
this.basicType = 'rectangle';
}
}
else {
delete options.shapes;
this.basicType = 'label';
}
Annotation.prototype.init.apply(this, arguments);
};
/* *
*
* Static Properties
*
* */
BasicAnnotation.basicControlPoints = {
label: [{
symbol: 'triangle-down',
positioner: function (target) {
if (!target.graphic.placed) {
return {
x: 0,
y: -9e7
};
}
var xy = MockPoint
.pointToPixels(target.points[0]);
return {
x: xy.x - this.graphic.width / 2,
y: xy.y - this.graphic.height / 2
};
},
// TRANSLATE POINT/ANCHOR
events: {
drag: function (e, target) {
var xy = this.mouseMoveToTranslation(e);
target.translatePoint(xy.x, xy.y);
target.annotation.userOptions.labels[0].point =
target.options.point;
target.redraw(false);
}
}
}, {
symbol: 'square',
positioner: function (target) {
if (!target.graphic.placed) {
return {
x: 0,
y: -9e7
};
}
return {
x: target.graphic.alignAttr.x -
this.graphic.width / 2,
y: target.graphic.alignAttr.y -
this.graphic.height / 2
};
},
// TRANSLATE POSITION WITHOUT CHANGING THE
// ANCHOR
events: {
drag: function (e, target) {
var xy = this.mouseMoveToTranslation(e);
target.translate(xy.x, xy.y);
target.annotation.userOptions.labels[0].point =
target.options.point;
target.redraw(false);
}
}
}],
rectangle: [{
positioner: function (annotation) {
var xy = MockPoint
.pointToPixels(annotation.points[2]);
return {
x: xy.x - 4,
y: xy.y - 4
};
},
events: {
drag: function (e, target) {
var annotation = target.annotation, coords = this.chart.pointer.getCoordinates(e), x = coords.xAxis[0].value, y = coords.yAxis[0].value, points = target.options.points;
// Top right point
points[1].x = x;
// Bottom right point (cursor position)
points[2].x = x;
points[2].y = y;
// Bottom left
points[3].y = y;
annotation.userOptions.shapes[0].points =
target.options.points;
annotation.redraw(false);
}
}
}],
circle: [{
positioner: function (target) {
var xy = MockPoint.pointToPixels(target.points[0]), r = target.options.r;
return {
x: xy.x + r * Math.cos(Math.PI / 4) -
this.graphic.width / 2,
y: xy.y + r * Math.sin(Math.PI / 4) -
this.graphic.height / 2
};
},
events: {
// TRANSFORM RADIUS ACCORDING TO Y
// TRANSLATION
drag: function (e, target) {
var annotation = target.annotation, position = this.mouseMoveToTranslation(e);
target.setRadius(Math.max(target.options.r +
position.y /
Math.sin(Math.PI / 4), 5));
annotation.userOptions.shapes[0].r = target.options.r;
annotation.userOptions.shapes[0].point =
target.options.point;
target.redraw(false);
}
}
}]
};
return BasicAnnotation;
}(Annotation));
BasicAnnotation.prototype.defaultOptions = merge(Annotation.prototype.defaultOptions, {});
Annotation.types.basicAnnotation = BasicAnnotation;
export default BasicAnnotation;
@@ -0,0 +1,171 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import Annotation from '../Annotations.js';
import ControlPoint from '../ControlPoint.js';
import MockPoint from '../MockPoint.js';
import U from '../../../Core/Utilities.js';
var merge = U.merge;
/* eslint-disable no-invalid-this, valid-jsdoc */
var CrookedLine = /** @class */ (function (_super) {
__extends(CrookedLine, _super);
/* *
*
* Constructors
*
* */
function CrookedLine(chart, options) {
return _super.call(this, chart, options) || this;
}
/* *
*
* Functions
*
* */
/**
* Overrides default setter to get axes from typeOptions.
* @private
*/
CrookedLine.prototype.setClipAxes = function () {
this.clipXAxis = this.chart.xAxis[this.options.typeOptions.xAxis];
this.clipYAxis = this.chart.yAxis[this.options.typeOptions.yAxis];
};
CrookedLine.prototype.getPointsOptions = function () {
var typeOptions = this.options.typeOptions;
return (typeOptions.points || []).map(function (pointOptions) {
pointOptions.xAxis = typeOptions.xAxis;
pointOptions.yAxis = typeOptions.yAxis;
return pointOptions;
});
};
CrookedLine.prototype.getControlPointsOptions = function () {
return this.getPointsOptions();
};
CrookedLine.prototype.addControlPoints = function () {
this.getControlPointsOptions().forEach(function (pointOptions, i) {
var controlPoint = new ControlPoint(this.chart, this, merge(this.options.controlPointOptions, pointOptions.controlPoint), i);
this.controlPoints.push(controlPoint);
pointOptions.controlPoint = controlPoint.options;
}, this);
};
CrookedLine.prototype.addShapes = function () {
var typeOptions = this.options.typeOptions, shape = this.initShape(merge(typeOptions.line, {
type: 'path',
points: this.points.map(function (_point, i) {
return function (target) {
return target.annotation.points[i];
};
})
}), false);
typeOptions.line = shape.options;
};
return CrookedLine;
}(Annotation));
CrookedLine.prototype.defaultOptions = merge(Annotation.prototype.defaultOptions,
/**
* A crooked line annotation.
*
* @sample highcharts/annotations-advanced/crooked-line/
* Crooked line
*
* @product highstock
* @optionparent annotations.crookedLine
*/
{
/**
* @extends annotations.labelOptions
* @apioption annotations.crookedLine.labelOptions
*/
/**
* @extends annotations.shapeOptions
* @apioption annotations.crookedLine.shapeOptions
*/
/**
* Additional options for an annotation with the type.
*/
typeOptions: {
/**
* This number defines which xAxis the point is connected to.
* It refers to either the axis id or the index of the axis
* in the xAxis array.
*/
xAxis: 0,
/**
* This number defines which yAxis the point is connected to.
* It refers to either the axis id or the index of the axis
* in the xAxis array.
*/
yAxis: 0,
/**
* @type {Array<*>}
* @apioption annotations.crookedLine.typeOptions.points
*/
/**
* The x position of the point.
*
* @type {number}
* @apioption annotations.crookedLine.typeOptions.points.x
*/
/**
* The y position of the point.
*
* @type {number}
* @apioption annotations.crookedLine.typeOptions.points.y
*/
/**
* @type {number}
* @excluding positioner, events
* @apioption annotations.crookedLine.typeOptions.points.controlPoint
*/
/**
* Line options.
*
* @excluding height, point, points, r, type, width
*/
line: {
fill: 'none'
}
},
/**
* @excluding positioner, events
*/
controlPointOptions: {
positioner: function (target) {
var graphic = this.graphic, xy = MockPoint.pointToPixels(target.points[this.index]);
return {
x: xy.x - graphic.width / 2,
y: xy.y - graphic.height / 2
};
},
events: {
drag: function (e, target) {
if (target.chart.isInsidePlot(e.chartX - target.chart.plotLeft, e.chartY - target.chart.plotTop)) {
var translation = this.mouseMoveToTranslation(e);
target.translatePoint(translation.x, translation.y, this.index);
// Update options:
target.options.typeOptions.points[this.index].x = target.points[this.index].x;
target.options.typeOptions.points[this.index].y = target.points[this.index].y;
target.redraw(false);
}
}
}
}
});
Annotation.types.crookedLine = CrookedLine;
export default CrookedLine;
@@ -0,0 +1,85 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import Annotation from '../Annotations.js';
import CrookedLine from './CrookedLine.js';
import U from '../../../Core/Utilities.js';
var merge = U.merge;
/* eslint-disable no-invalid-this, valid-jsdoc */
var ElliottWave = /** @class */ (function (_super) {
__extends(ElliottWave, _super);
function ElliottWave(chart, options) {
return _super.call(this, chart, options) || this;
}
/* *
*
* Functions
*
* */
ElliottWave.prototype.addLabels = function () {
this.getPointsOptions().forEach(function (point, i) {
var typeOptions = this.options.typeOptions, label = this.initLabel(merge(point.label, {
text: typeOptions.labels[i],
point: function (target) {
return target.annotation.points[i];
}
}), false);
point.label = label.options;
}, this);
};
return ElliottWave;
}(CrookedLine));
ElliottWave.prototype.defaultOptions = merge(CrookedLine.prototype.defaultOptions,
/**
* An elliott wave annotation.
*
* @sample highcharts/annotations-advanced/elliott-wave/
* Elliott wave
*
* @extends annotations.crookedLine
* @product highstock
* @optionparent annotations.elliottWave
*/
{
typeOptions: {
/**
* @extends annotations.crookedLine.labelOptions
* @apioption annotations.elliottWave.typeOptions.points.label
*/
/**
* @ignore-options
*/
labels: ['(0)', '(A)', '(B)', '(C)', '(D)', '(E)'],
line: {
strokeWidth: 1
}
},
labelOptions: {
align: 'center',
allowOverlap: true,
crop: true,
overflow: 'none',
type: 'rect',
backgroundColor: 'none',
borderWidth: 0,
y: -5
}
});
Annotation.types.elliottWave = ElliottWave;
export default ElliottWave;
@@ -0,0 +1,194 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import Annotation from '../Annotations.js';
import MockPoint from '../MockPoint.js';
import Tunnel from './Tunnel.js';
import U from '../../../Core/Utilities.js';
var merge = U.merge;
/* eslint-disable no-invalid-this, valid-jsdoc */
var createPathDGenerator = function (retracementIndex, isBackground) {
return function () {
var annotation = this.annotation, leftTop = this.anchor(annotation.startRetracements[retracementIndex]).absolutePosition, rightTop = this.anchor(annotation.endRetracements[retracementIndex]).absolutePosition, d = [
['M', Math.round(leftTop.x), Math.round(leftTop.y)],
['L', Math.round(rightTop.x), Math.round(rightTop.y)]
], rightBottom, leftBottom;
if (isBackground) {
rightBottom = this.anchor(annotation.endRetracements[retracementIndex - 1]).absolutePosition;
leftBottom = this.anchor(annotation.startRetracements[retracementIndex - 1]).absolutePosition;
d.push(['L', Math.round(rightBottom.x), Math.round(rightBottom.y)], ['L', Math.round(leftBottom.x), Math.round(leftBottom.y)]);
}
return d;
};
};
var Fibonacci = /** @class */ (function (_super) {
__extends(Fibonacci, _super);
/* *
*
* Constructors
*
* */
function Fibonacci(chart, options) {
return _super.call(this, chart, options) || this;
}
/* *
*
* Functions
*
* */
Fibonacci.prototype.linkPoints = function () {
_super.prototype.linkPoints.call(this);
this.linkRetracementsPoints();
return;
};
Fibonacci.prototype.linkRetracementsPoints = function () {
var points = this.points, startDiff = points[0].y - points[3].y, endDiff = points[1].y - points[2].y, startX = points[0].x, endX = points[1].x;
Fibonacci.levels.forEach(function (level, i) {
var startRetracement = points[0].y - startDiff * level, endRetracement = points[1].y - endDiff * level;
this.startRetracements = this.startRetracements || [];
this.endRetracements = this.endRetracements || [];
this.linkRetracementPoint(i, startX, startRetracement, this.startRetracements);
this.linkRetracementPoint(i, endX, endRetracement, this.endRetracements);
}, this);
};
Fibonacci.prototype.linkRetracementPoint = function (pointIndex, x, y, retracements) {
var point = retracements[pointIndex], typeOptions = this.options.typeOptions;
if (!point) {
retracements[pointIndex] = new MockPoint(this.chart, this, {
x: x,
y: y,
xAxis: typeOptions.xAxis,
yAxis: typeOptions.yAxis
});
}
else {
point.options.x = x;
point.options.y = y;
point.refresh();
}
};
Fibonacci.prototype.addShapes = function () {
Fibonacci.levels.forEach(function (_level, i) {
this.initShape({
type: 'path',
d: createPathDGenerator(i)
}, false);
if (i > 0) {
this.initShape({
type: 'path',
fill: this.options.typeOptions.backgroundColors[i - 1],
strokeWidth: 0,
d: createPathDGenerator(i, true)
});
}
}, this);
};
Fibonacci.prototype.addLabels = function () {
Fibonacci.levels.forEach(function (level, i) {
var options = this.options.typeOptions, label = this.initLabel(merge(options.labels[i], {
point: function (target) {
var point = MockPoint.pointToOptions(target.annotation.startRetracements[i]);
return point;
},
text: level.toString()
}));
options.labels[i] = label.options;
}, this);
};
/* *
*
* Static properties
*
* */
Fibonacci.levels = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
return Fibonacci;
}(Tunnel));
Fibonacci.prototype.defaultOptions = merge(Tunnel.prototype.defaultOptions,
/**
* A fibonacci annotation.
*
* @sample highcharts/annotations-advanced/fibonacci/
* Fibonacci
*
* @extends annotations.crookedLine
* @product highstock
* @optionparent annotations.fibonacci
*/
{
typeOptions: {
/**
* The height of the fibonacci in terms of yAxis.
*/
height: 2,
/**
* An array of background colors:
* Default to:
* ```
* [
* 'rgba(130, 170, 255, 0.4)',
* 'rgba(139, 191, 216, 0.4)',
* 'rgba(150, 216, 192, 0.4)',
* 'rgba(156, 229, 161, 0.4)',
* 'rgba(162, 241, 130, 0.4)',
* 'rgba(169, 255, 101, 0.4)'
* ]
* ```
*/
backgroundColors: [
'rgba(130, 170, 255, 0.4)',
'rgba(139, 191, 216, 0.4)',
'rgba(150, 216, 192, 0.4)',
'rgba(156, 229, 161, 0.4)',
'rgba(162, 241, 130, 0.4)',
'rgba(169, 255, 101, 0.4)'
],
/**
* The color of line.
*/
lineColor: 'grey',
/**
* An array of colors for the lines.
*/
lineColors: [],
/**
* An array with options for the labels.
*
* @type {Array<*>}
* @extends annotations.crookedLine.labelOptions
* @apioption annotations.fibonacci.typeOptions.labels
*/
labels: []
},
labelOptions: {
allowOverlap: true,
align: 'right',
backgroundColor: 'none',
borderWidth: 0,
crop: false,
overflow: 'none',
shape: 'rect',
style: {
color: 'grey'
},
verticalAlign: 'middle',
y: 0
}
});
Annotation.types.fibonacci = Fibonacci;
export default Fibonacci;
@@ -0,0 +1,147 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import Annotation from '../Annotations.js';
import CrookedLine from './CrookedLine.js';
import MockPoint from '../MockPoint.js';
import U from '../../../Core/Utilities.js';
var merge = U.merge;
/* eslint-disable no-invalid-this, valid-jsdoc */
var InfinityLine = /** @class */ (function (_super) {
__extends(InfinityLine, _super);
/* *
*
* Constructors
*
* */
function InfinityLine(chart, options) {
return _super.call(this, chart, options) || this;
}
/* *
*
* Static Functions
*
* */
InfinityLine.edgePoint = function (startIndex, endIndex) {
return function (target) {
var annotation = target.annotation, points = annotation.points, type = annotation.options.typeOptions.type;
if (type === 'horizontalLine') {
// Horizontal line has only one point,
// make a copy of it:
points = [
points[0],
new MockPoint(annotation.chart, points[0].target, {
x: points[0].x + 1,
y: points[0].y,
xAxis: points[0].options.xAxis,
yAxis: points[0].options.yAxis
})
];
}
else if (type === 'verticalLine') {
// The same for verticalLine type:
points = [
points[0],
new MockPoint(annotation.chart, points[0].target, {
x: points[0].x,
y: points[0].y + 1,
xAxis: points[0].options.xAxis,
yAxis: points[0].options.yAxis
})
];
}
return InfinityLine.findEdgePoint(points[startIndex], points[endIndex]);
};
};
InfinityLine.findEdgeCoordinate = function (firstPoint, secondPoint, xOrY, edgePointFirstCoordinate) {
var xOrYOpposite = xOrY === 'x' ? 'y' : 'x';
// solves equation for x or y
// y - y1 = (y2 - y1) / (x2 - x1) * (x - x1)
return ((secondPoint[xOrY] - firstPoint[xOrY]) *
(edgePointFirstCoordinate - firstPoint[xOrYOpposite]) /
(secondPoint[xOrYOpposite] - firstPoint[xOrYOpposite]) +
firstPoint[xOrY]);
};
InfinityLine.findEdgePoint = function (firstPoint, secondPoint) {
var xAxis = firstPoint.series.xAxis, yAxis = secondPoint.series.yAxis, firstPointPixels = MockPoint.pointToPixels(firstPoint), secondPointPixels = MockPoint.pointToPixels(secondPoint), deltaX = secondPointPixels.x - firstPointPixels.x, deltaY = secondPointPixels.y - firstPointPixels.y, xAxisMin = xAxis.left, xAxisMax = xAxisMin + xAxis.width, yAxisMin = yAxis.top, yAxisMax = yAxisMin + yAxis.height, xLimit = deltaX < 0 ? xAxisMin : xAxisMax, yLimit = deltaY < 0 ? yAxisMin : yAxisMax, edgePoint = {
x: deltaX === 0 ? firstPointPixels.x : xLimit,
y: deltaY === 0 ? firstPointPixels.y : yLimit
}, edgePointX, edgePointY, swap;
if (deltaX !== 0 && deltaY !== 0) {
edgePointY = InfinityLine.findEdgeCoordinate(firstPointPixels, secondPointPixels, 'y', xLimit);
edgePointX = InfinityLine.findEdgeCoordinate(firstPointPixels, secondPointPixels, 'x', yLimit);
if (edgePointY >= yAxisMin && edgePointY <= yAxisMax) {
edgePoint.x = xLimit;
edgePoint.y = edgePointY;
}
else {
edgePoint.x = edgePointX;
edgePoint.y = yLimit;
}
}
edgePoint.x -= xAxisMin;
edgePoint.y -= yAxisMin;
if (firstPoint.series.chart.inverted) {
swap = edgePoint.x;
edgePoint.x = edgePoint.y;
edgePoint.y = swap;
}
return edgePoint;
};
/* *
*
* Functions
*
* */
InfinityLine.prototype.addShapes = function () {
var typeOptions = this.options.typeOptions, points = [
this.points[0],
InfinityLine.endEdgePoint
];
if (typeOptions.type.match(/Line/g)) {
points[0] = InfinityLine.startEdgePoint;
}
var line = this.initShape(merge(typeOptions.line, {
type: 'path',
points: points
}), false);
typeOptions.line = line.options;
};
/**
*
* Static Properties
*
*/
InfinityLine.endEdgePoint = InfinityLine.edgePoint(0, 1);
InfinityLine.startEdgePoint = InfinityLine.edgePoint(1, 0);
return InfinityLine;
}(CrookedLine));
InfinityLine.prototype.defaultOptions = merge(CrookedLine.prototype.defaultOptions, {});
/**
* An infinity line annotation.
*
* @sample highcharts/annotations-advanced/infinity-line/
* Infinity Line
*
* @extends annotations.crookedLine
* @product highstock
* @apioption annotations.infinityLine
*/
Annotation.types.infinityLine = InfinityLine;
export default InfinityLine;
@@ -0,0 +1,767 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import Annotation from '../Annotations.js';
import ControlPoint from '../ControlPoint.js';
import U from '../../../Core/Utilities.js';
var extend = U.extend, isNumber = U.isNumber, merge = U.merge;
/* eslint-disable no-invalid-this, valid-jsdoc */
var Measure = /** @class */ (function (_super) {
__extends(Measure, _super);
/* *
*
* Constructors
*
* */
function Measure(chart, userOptions) {
return _super.call(this, chart, userOptions) || this;
}
/* *
*
* Functions
*
* */
/**
* Init annotation object.
* @private
*/
Measure.prototype.init = function (annotationOrChart, userOptions, index) {
Annotation.prototype.init.call(this, annotationOrChart, userOptions, index);
this.offsetX = 0;
this.offsetY = 0;
this.resizeX = 0;
this.resizeY = 0;
Measure.calculations.init.call(this);
this.addValues();
this.addShapes();
};
/**
* Overrides default setter to get axes from typeOptions.
* @private
*/
Measure.prototype.setClipAxes = function () {
this.clipXAxis = this.chart.xAxis[this.options.typeOptions.xAxis];
this.clipYAxis = this.chart.yAxis[this.options.typeOptions.yAxis];
};
/**
* Get measure points configuration objects.
* @private
* @return {Array<Highcharts.AnnotationMockPointOptionsObject>}
*/
Measure.prototype.pointsOptions = function () {
return this.options.points;
};
/**
* Get points configuration objects for shapes.
* @private
* @return {Array<Highcharts.AnnotationMockPointOptionsObject>}
*/
Measure.prototype.shapePointsOptions = function () {
var options = this.options.typeOptions, xAxis = options.xAxis, yAxis = options.yAxis;
return [
{
x: this.xAxisMin,
y: this.yAxisMin,
xAxis: xAxis,
yAxis: yAxis
},
{
x: this.xAxisMax,
y: this.yAxisMin,
xAxis: xAxis,
yAxis: yAxis
},
{
x: this.xAxisMax,
y: this.yAxisMax,
xAxis: xAxis,
yAxis: yAxis
},
{
x: this.xAxisMin,
y: this.yAxisMax,
xAxis: xAxis,
yAxis: yAxis
}
];
};
Measure.prototype.addControlPoints = function () {
var selectType = this.options.typeOptions.selectType, controlPoint;
controlPoint = new ControlPoint(this.chart, this, this.options.controlPointOptions, 0);
this.controlPoints.push(controlPoint);
// add extra controlPoint for horizontal and vertical range
if (selectType !== 'xy') {
controlPoint = new ControlPoint(this.chart, this, this.options.controlPointOptions, 1);
this.controlPoints.push(controlPoint);
}
};
/**
* Add label with calculated values (min, max, average, bins).
* @private
* @param {boolean} [resize]
* The flag for resize shape
*/
Measure.prototype.addValues = function (resize) {
var typeOptions = this.options.typeOptions, formatter = typeOptions.label.formatter;
// set xAxisMin, xAxisMax, yAxisMin, yAxisMax
Measure.calculations.recalculate.call(this, resize);
if (!typeOptions.label.enabled) {
return;
}
if (this.labels.length > 0) {
this.labels[0].text = (formatter && formatter.call(this)) ||
Measure.calculations.defaultFormatter.call(this);
}
else {
this.initLabel(extend({
shape: 'rect',
backgroundColor: 'none',
color: 'black',
borderWidth: 0,
dashStyle: 'dash',
overflow: 'none',
align: 'left',
vertical: 'top',
crop: true,
point: function (target) {
var annotation = target.annotation, chart = annotation.chart, inverted = chart.inverted, xAxis = chart.xAxis[typeOptions.xAxis], yAxis = chart.yAxis[typeOptions.yAxis], top = chart.plotTop, left = chart.plotLeft;
return {
x: (inverted ? top : 10) +
xAxis.toPixels(annotation.xAxisMin, !inverted),
y: (inverted ? -left + 10 : top) +
yAxis.toPixels(annotation.yAxisMin)
};
},
text: (formatter && formatter.call(this)) ||
Measure.calculations.defaultFormatter.call(this)
}, typeOptions.label));
}
};
/**
* Crosshair, background (rect).
* @private
*/
Measure.prototype.addShapes = function () {
this.addCrosshairs();
this.addBackground();
};
/**
* Add background shape.
* @private
*/
Measure.prototype.addBackground = function () {
var shapePoints = this.shapePointsOptions();
if (typeof shapePoints[0].x === 'undefined') {
return;
}
this.initShape(extend({
type: 'path',
points: this.shapePointsOptions()
}, this.options.typeOptions.background), false);
};
/**
* Add internal crosshair shapes (on top and bottom).
* @private
*/
Measure.prototype.addCrosshairs = function () {
var chart = this.chart, options = this.options.typeOptions, point = this.options.typeOptions.point, xAxis = chart.xAxis[options.xAxis], yAxis = chart.yAxis[options.yAxis], inverted = chart.inverted, xAxisMin = xAxis.toPixels(this.xAxisMin), xAxisMax = xAxis.toPixels(this.xAxisMax), yAxisMin = yAxis.toPixels(this.yAxisMin), yAxisMax = yAxis.toPixels(this.yAxisMax), defaultOptions = {
point: point,
type: 'path'
}, pathH = [], pathV = [], crosshairOptionsX, crosshairOptionsY, temp;
if (inverted) {
temp = xAxisMin;
xAxisMin = yAxisMin;
yAxisMin = temp;
temp = xAxisMax;
xAxisMax = yAxisMax;
yAxisMax = temp;
}
// horizontal line
if (options.crosshairX.enabled) {
pathH = [[
'M',
xAxisMin,
yAxisMin + ((yAxisMax - yAxisMin) / 2)
], [
'L',
xAxisMax,
yAxisMin + ((yAxisMax - yAxisMin) / 2)
]];
}
// vertical line
if (options.crosshairY.enabled) {
pathV = [[
'M',
xAxisMin + ((xAxisMax - xAxisMin) / 2),
yAxisMin
], [
'L',
xAxisMin + ((xAxisMax - xAxisMin) / 2),
yAxisMax
]];
}
// Update existed crosshair
if (this.shapes.length > 0) {
this.shapes[0].options.d = pathH;
this.shapes[1].options.d = pathV;
}
else {
// Add new crosshairs
crosshairOptionsX = merge(defaultOptions, options.crosshairX);
crosshairOptionsY = merge(defaultOptions, options.crosshairY);
this.initShape(extend({
d: pathH
}, crosshairOptionsX), false);
this.initShape(extend({
d: pathV
}, crosshairOptionsY), false);
}
};
Measure.prototype.onDrag = function (e) {
var translation = this.mouseMoveToTranslation(e), selectType = this.options.typeOptions.selectType, x = selectType === 'y' ? 0 : translation.x, y = selectType === 'x' ? 0 : translation.y;
this.translate(x, y);
this.offsetX += x;
this.offsetY += y;
// animation, resize, setStartPoints
this.redraw(false, false, true);
};
/**
* Translate start or end ("left" or "right") side of the measure.
* Update start points (startXMin, startXMax, startYMin, startYMax)
* @private
* @param {number} dx - the amount of x translation
* @param {number} dy - the amount of y translation
* @param {number} cpIndex - index of control point
* @param {Highcharts.AnnotationDraggableValue} selectType - x / y / xy
*/
Measure.prototype.resize = function (dx, dy, cpIndex, selectType) {
// background shape
var bckShape = this.shapes[2];
if (selectType === 'x') {
if (cpIndex === 0) {
bckShape.translatePoint(dx, 0, 0);
bckShape.translatePoint(dx, dy, 3);
}
else {
bckShape.translatePoint(dx, 0, 1);
bckShape.translatePoint(dx, dy, 2);
}
}
else if (selectType === 'y') {
if (cpIndex === 0) {
bckShape.translatePoint(0, dy, 0);
bckShape.translatePoint(0, dy, 1);
}
else {
bckShape.translatePoint(0, dy, 2);
bckShape.translatePoint(0, dy, 3);
}
}
else {
bckShape.translatePoint(dx, 0, 1);
bckShape.translatePoint(dx, dy, 2);
bckShape.translatePoint(0, dy, 3);
}
Measure.calculations.updateStartPoints
.call(this, false, true, cpIndex, dx, dy);
this.options.typeOptions.background.height = Math.abs(this.startYMax - this.startYMin);
this.options.typeOptions.background.width = Math.abs(this.startXMax - this.startXMin);
};
/**
* Redraw event which render elements and update start points if needed.
* @private
* @param {boolean} animation
* @param {boolean} [resize] - flag if resized
* @param {boolean} [setStartPoints] - update position of start points
*/
Measure.prototype.redraw = function (animation, resize, setStartPoints) {
this.linkPoints();
if (!this.graphic) {
this.render();
}
if (setStartPoints) {
Measure.calculations.updateStartPoints.call(this, true, false);
}
// #11174 - clipBox was not recalculate during resize / redraw
if (this.clipRect) {
this.clipRect.animate(this.getClipBox());
}
this.addValues(resize);
this.addCrosshairs();
this.redrawItems(this.shapes, animation);
this.redrawItems(this.labels, animation);
// redraw control point to run positioner
this.controlPoints.forEach(function (controlPoint) {
controlPoint.redraw();
});
};
Measure.prototype.translate = function (dx, dy) {
this.shapes.forEach(function (item) {
item.translate(dx, dy);
});
this.options.typeOptions.point.x = this.startXMin;
this.options.typeOptions.point.y = this.startYMin;
};
/* *
*
* Static Functions
*
* */
Measure.calculations = {
/**
* Set starting points
* @private
*/
init: function () {
var options = this.options.typeOptions, chart = this.chart, getPointPos = Measure.calculations.getPointPos, inverted = chart.inverted, xAxis = chart.xAxis[options.xAxis], yAxis = chart.yAxis[options.yAxis], bck = options.background, width = inverted ? bck.height : bck.width, height = inverted ? bck.width : bck.height, selectType = options.selectType, top = inverted ? xAxis.left : yAxis.top, // #13664
left = inverted ? yAxis.top : xAxis.left; // #13664
this.startXMin = options.point.x;
this.startYMin = options.point.y;
if (isNumber(width)) {
this.startXMax = this.startXMin + width;
}
else {
this.startXMax = getPointPos(xAxis, this.startXMin, parseFloat(width));
}
if (isNumber(height)) {
this.startYMax = this.startYMin - height;
}
else {
this.startYMax = getPointPos(yAxis, this.startYMin, parseFloat(height));
}
// x / y selection type
if (selectType === 'x') {
this.startYMin = yAxis.toValue(top);
this.startYMax = yAxis.toValue(top + yAxis.len);
}
else if (selectType === 'y') {
this.startXMin = xAxis.toValue(left);
this.startXMax = xAxis.toValue(left + xAxis.len);
}
},
/**
* Set current xAxisMin, xAxisMax, yAxisMin, yAxisMax.
* Calculations of measure values (min, max, average, bins).
* @private
* @param {boolean} [resize]
* Flag if shape is resized.
*/
recalculate: function (resize) {
var calc = Measure.calculations, options = this.options.typeOptions, xAxis = this.chart.xAxis[options.xAxis], yAxis = this.chart.yAxis[options.yAxis], getPointPos = Measure.calculations.getPointPos, offsetX = this.offsetX, offsetY = this.offsetY;
this.xAxisMin = getPointPos(xAxis, this.startXMin, offsetX);
this.xAxisMax = getPointPos(xAxis, this.startXMax, offsetX);
this.yAxisMin = getPointPos(yAxis, this.startYMin, offsetY);
this.yAxisMax = getPointPos(yAxis, this.startYMax, offsetY);
this.min = calc.min.call(this);
this.max = calc.max.call(this);
this.average = calc.average.call(this);
this.bins = calc.bins.call(this);
if (resize) {
this.resize(0, 0);
}
},
/**
* Set current xAxisMin, xAxisMax, yAxisMin, yAxisMax.
* Calculations of measure values (min, max, average, bins).
* @private
* @param {Highcharts.Axis} axis
* X or y axis reference
* @param {number} value
* Point's value (x or y)
* @param {number} offset
* Amount of pixels
*/
getPointPos: function (axis, value, offset) {
return axis.toValue(axis.toPixels(value) + offset);
},
/**
* Update position of start points
* (startXMin, startXMax, startYMin, startYMax)
* @private
* @param {boolean} redraw
* Flag if shape is redraw
* @param {boolean} resize
* Flag if shape is resized
* @param {number} cpIndex
* Index of controlPoint
*/
updateStartPoints: function (redraw, resize, cpIndex, dx, dy) {
var options = this.options.typeOptions, selectType = options.selectType, xAxis = this.chart.xAxis[options.xAxis], yAxis = this.chart.yAxis[options.yAxis], getPointPos = Measure.calculations.getPointPos, startXMin = this.startXMin, startXMax = this.startXMax, startYMin = this.startYMin, startYMax = this.startYMax, offsetX = this.offsetX, offsetY = this.offsetY;
if (resize) {
if (selectType === 'x') {
if (cpIndex === 0) {
this.startXMin = getPointPos(xAxis, startXMin, dx);
}
else {
this.startXMax = getPointPos(xAxis, startXMax, dx);
}
}
else if (selectType === 'y') {
if (cpIndex === 0) {
this.startYMin = getPointPos(yAxis, startYMin, dy);
}
else {
this.startYMax = getPointPos(yAxis, startYMax, dy);
}
}
else {
this.startXMax = getPointPos(xAxis, startXMax, dx);
this.startYMax = getPointPos(yAxis, startYMax, dy);
}
}
if (redraw) {
this.startXMin = getPointPos(xAxis, startXMin, offsetX);
this.startXMax = getPointPos(xAxis, startXMax, offsetX);
this.startYMin = getPointPos(yAxis, startYMin, offsetY);
this.startYMax = getPointPos(yAxis, startYMax, offsetY);
this.offsetX = 0;
this.offsetY = 0;
}
},
/**
* Default formatter of label's content
* @private
*/
defaultFormatter: function () {
return 'Min: ' + this.min +
'<br>Max: ' + this.max +
'<br>Average: ' + this.average +
'<br>Bins: ' + this.bins;
},
/**
* Set values for xAxisMin, xAxisMax, yAxisMin, yAxisMax, also
* when chart is inverted
* @private
*/
getExtremes: function (xAxisMin, xAxisMax, yAxisMin, yAxisMax) {
return {
xAxisMin: Math.min(xAxisMax, xAxisMin),
xAxisMax: Math.max(xAxisMax, xAxisMin),
yAxisMin: Math.min(yAxisMax, yAxisMin),
yAxisMax: Math.max(yAxisMax, yAxisMin)
};
},
/**
* Definitions of calculations (min, max, average, bins)
* @private
*/
min: function () {
var min = Infinity, series = this.chart.series, ext = Measure.calculations.getExtremes(this.xAxisMin, this.xAxisMax, this.yAxisMin, this.yAxisMax), isCalculated = false; // to avoid Infinity in formatter
series.forEach(function (serie) {
if (serie.visible &&
serie.options.id !== 'highcharts-navigator-series') {
serie.points.forEach(function (point) {
if (!point.isNull &&
point.y < min &&
point.x > ext.xAxisMin &&
point.x <= ext.xAxisMax &&
point.y > ext.yAxisMin &&
point.y <= ext.yAxisMax) {
min = point.y;
isCalculated = true;
}
});
}
});
if (!isCalculated) {
min = '';
}
return min;
},
max: function () {
var max = -Infinity, series = this.chart.series, ext = Measure.calculations.getExtremes(this.xAxisMin, this.xAxisMax, this.yAxisMin, this.yAxisMax), isCalculated = false; // to avoid Infinity in formatter
series.forEach(function (serie) {
if (serie.visible &&
serie.options.id !== 'highcharts-navigator-series') {
serie.points.forEach(function (point) {
if (!point.isNull &&
point.y > max &&
point.x > ext.xAxisMin &&
point.x <= ext.xAxisMax &&
point.y > ext.yAxisMin &&
point.y <= ext.yAxisMax) {
max = point.y;
isCalculated = true;
}
});
}
});
if (!isCalculated) {
max = '';
}
return max;
},
average: function () {
var average = '';
if (this.max !== '' && this.min !== '') {
average = (this.max + this.min) / 2;
}
return average;
},
bins: function () {
var bins = 0, series = this.chart.series, ext = Measure.calculations.getExtremes(this.xAxisMin, this.xAxisMax, this.yAxisMin, this.yAxisMax), isCalculated = false; // to avoid Infinity in formatter
series.forEach(function (serie) {
if (serie.visible &&
serie.options.id !== 'highcharts-navigator-series') {
serie.points.forEach(function (point) {
if (!point.isNull &&
point.x > ext.xAxisMin &&
point.x <= ext.xAxisMax &&
point.y > ext.yAxisMin &&
point.y <= ext.yAxisMax) {
bins++;
isCalculated = true;
}
});
}
});
if (!isCalculated) {
bins = '';
}
return bins;
}
};
return Measure;
}(Annotation));
Measure.prototype.defaultOptions = merge(Annotation.prototype.defaultOptions,
/**
* A measure annotation.
*
* @extends annotations.crookedLine
* @excluding labels, labelOptions, shapes, shapeOptions
* @sample highcharts/annotations-advanced/measure/
* Measure
* @product highstock
* @optionparent annotations.measure
*/
{
typeOptions: {
/**
* Decides in what dimensions the user can resize by dragging the
* mouse. Can be one of x, y or xy.
*/
selectType: 'xy',
/**
* This number defines which xAxis the point is connected to.
* It refers to either the axis id or the index of the axis
* in the xAxis array.
*/
xAxis: 0,
/**
* This number defines which yAxis the point is connected to.
* It refers to either the axis id or the index of the axis
* in the yAxis array.
*/
yAxis: 0,
background: {
/**
* The color of the rectangle.
*/
fill: 'rgba(130, 170, 255, 0.4)',
/**
* The width of border.
*/
strokeWidth: 0,
/**
* The color of border.
*/
stroke: void 0
},
/**
* Configure a crosshair that is horizontally placed in middle of
* rectangle.
*
*/
crosshairX: {
/**
* Enable or disable the horizontal crosshair.
*
*/
enabled: true,
/**
* The Z index of the crosshair in annotation.
*/
zIndex: 6,
/**
* The dash or dot style of the crosshair's line. For possible
* values, see
* [this demonstration](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/).
*
* @type {Highcharts.DashStyleValue}
* @default Dash
*/
dashStyle: 'Dash',
/**
* The marker-end defines the arrowhead that will be drawn
* at the final vertex of the given crosshair's path.
*
* @type {string}
* @default arrow
*/
markerEnd: 'arrow'
},
/**
* Configure a crosshair that is vertically placed in middle of
* rectangle.
*/
crosshairY: {
/**
* Enable or disable the vertical crosshair.
*
*/
enabled: true,
/**
* The Z index of the crosshair in annotation.
*/
zIndex: 6,
/**
* The dash or dot style of the crosshair's line. For possible
* values, see [this demonstration](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/).
*
* @type {Highcharts.DashStyleValue}
* @default Dash
* @apioption annotations.measure.typeOptions.crosshairY.dashStyle
*
*/
dashStyle: 'Dash',
/**
* The marker-end defines the arrowhead that will be drawn
* at the final vertex of the given crosshair's path.
*
* @type {string}
* @default arrow
* @validvalue ["none", "arrow"]
*
*/
markerEnd: 'arrow'
},
label: {
/**
* Enable or disable the label text (min, max, average,
* bins values).
*
* Defaults to true.
*/
enabled: true,
/**
* CSS styles for the measure label.
*
* @type {Highcharts.CSSObject}
* @default {"color": "#666666", "fontSize": "11px"}
*/
style: {
fontSize: '11px',
color: '#666666'
},
/**
* Formatter function for the label text.
*
* Available data are:
*
* <table>
*
* <tbody>
*
* <tr>
*
* <td>`this.min`</td>
*
* <td>The mininimum value of the points in the selected
* range.</td>
*
* </tr>
*
* <tr>
*
* <td>`this.max`</td>
*
* <td>The maximum value of the points in the selected
* range.</td>
*
* </tr>
*
* <tr>
*
* <td>`this.average`</td>
*
* <td>The average value of the points in the selected
* range.</td>
*
* </tr>
*
* <tr>
*
* <td>`this.bins`</td>
*
* <td>The amount of the points in the selected range.</td>
*
* </tr>
*
* </table>
*
* @type {function}
*
*/
formatter: void 0
}
},
controlPointOptions: {
positioner: function (target) {
var cpIndex = this.index, chart = target.chart, options = target.options, typeOptions = options.typeOptions, selectType = typeOptions.selectType, controlPointOptions = options.controlPointOptions, inverted = chart.inverted, xAxis = chart.xAxis[typeOptions.xAxis], yAxis = chart.yAxis[typeOptions.yAxis], targetX = target.xAxisMax, targetY = target.yAxisMax, ext = Measure.calculations.getExtremes(target.xAxisMin, target.xAxisMax, target.yAxisMin, target.yAxisMax), x, y;
if (selectType === 'x') {
targetY = (ext.yAxisMax - ext.yAxisMin) / 2;
// first control point
if (cpIndex === 0) {
targetX = target.xAxisMin;
}
}
if (selectType === 'y') {
targetX = ext.xAxisMin +
((ext.xAxisMax - ext.xAxisMin) / 2);
// first control point
if (cpIndex === 0) {
targetY = target.yAxisMin;
}
}
if (inverted) {
x = yAxis.toPixels(targetY);
y = xAxis.toPixels(targetX);
}
else {
x = xAxis.toPixels(targetX);
y = yAxis.toPixels(targetY);
}
return {
x: x - (controlPointOptions.width / 2),
y: y - (controlPointOptions.height / 2)
};
},
events: {
drag: function (e, target) {
var translation = this.mouseMoveToTranslation(e), selectType = target.options.typeOptions.selectType, index = this.index, x = selectType === 'y' ? 0 : translation.x, y = selectType === 'x' ? 0 : translation.y;
target.resize(x, y, index, selectType);
target.resizeX += x;
target.resizeY += y;
target.redraw(false, true);
}
}
}
});
Annotation.types.measure = Measure;
export default Measure;
@@ -0,0 +1,183 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import Annotation from '../Annotations.js';
import InfinityLine from './InfinityLine.js';
import MockPoint from '../MockPoint.js';
import U from '../../../Core/Utilities.js';
var merge = U.merge;
/* eslint-disable no-invalid-this, valid-jsdoc */
var Pitchfork = /** @class */ (function (_super) {
__extends(Pitchfork, _super);
/* *
*
* Constructors
*
* */
function Pitchfork(chart, options) {
return _super.call(this, chart, options) || this;
}
/* *
*
* Static Functions
*
* */
Pitchfork.outerLineEdgePoint = function (firstPointIndex) {
return function (target) {
var annotation = target.annotation, points = annotation.points;
return Pitchfork.findEdgePoint(points[firstPointIndex], points[0], new MockPoint(annotation.chart, target, annotation.midPointOptions()));
};
};
Pitchfork.findEdgePoint = function (point, firstAnglePoint, secondAnglePoint) {
var angle = Math.atan2(secondAnglePoint.plotY - firstAnglePoint.plotY, secondAnglePoint.plotX - firstAnglePoint.plotX), distance = 1e7;
return {
x: point.plotX + distance * Math.cos(angle),
y: point.plotY + distance * Math.sin(angle)
};
};
Pitchfork.middleLineEdgePoint = function (target) {
var annotation = target.annotation, points = annotation.points;
return InfinityLine.findEdgePoint(points[0], new MockPoint(annotation.chart, target, annotation.midPointOptions()));
};
/* *
*
* Functions
*
* */
Pitchfork.prototype.midPointOptions = function () {
var points = this.points;
return {
x: (points[1].x + points[2].x) / 2,
y: (points[1].y + points[2].y) / 2,
xAxis: points[0].series.xAxis,
yAxis: points[0].series.yAxis
};
};
Pitchfork.prototype.addShapes = function () {
this.addLines();
this.addBackgrounds();
};
Pitchfork.prototype.addLines = function () {
this.initShape({
type: 'path',
points: [
this.points[0],
Pitchfork.middleLineEdgePoint
]
}, false);
this.initShape({
type: 'path',
points: [
this.points[1],
Pitchfork.topLineEdgePoint
]
}, false);
this.initShape({
type: 'path',
points: [
this.points[2],
Pitchfork.bottomLineEdgePoint
]
}, false);
};
Pitchfork.prototype.addBackgrounds = function () {
var shapes = this.shapes, typeOptions = this.options.typeOptions;
var innerBackground = this.initShape(merge(typeOptions.innerBackground, {
type: 'path',
points: [
function (target) {
var annotation = target.annotation, points = annotation.points, midPointOptions = annotation.midPointOptions();
return {
x: (points[1].x + midPointOptions.x) / 2,
y: (points[1].y + midPointOptions.y) / 2,
xAxis: midPointOptions.xAxis,
yAxis: midPointOptions.yAxis
};
},
shapes[1].points[1],
shapes[2].points[1],
function (target) {
var annotation = target.annotation, points = annotation.points, midPointOptions = annotation.midPointOptions();
return {
x: (midPointOptions.x + points[2].x) / 2,
y: (midPointOptions.y + points[2].y) / 2,
xAxis: midPointOptions.xAxis,
yAxis: midPointOptions.yAxis
};
}
]
}));
var outerBackground = this.initShape(merge(typeOptions.outerBackground, {
type: 'path',
points: [
this.points[1],
shapes[1].points[1],
shapes[2].points[1],
this.points[2]
]
}));
typeOptions.innerBackground = innerBackground.options;
typeOptions.outerBackground = outerBackground.options;
};
/**
*
* Static Properties
*
*/
Pitchfork.topLineEdgePoint = Pitchfork.outerLineEdgePoint(1);
Pitchfork.bottomLineEdgePoint = Pitchfork.outerLineEdgePoint(0);
return Pitchfork;
}(InfinityLine));
Pitchfork.prototype.defaultOptions = merge(InfinityLine.prototype.defaultOptions,
/**
* A pitchfork annotation.
*
* @sample highcharts/annotations-advanced/pitchfork/
* Pitchfork
*
* @extends annotations.infinityLine
* @product highstock
* @optionparent annotations.pitchfork
*/
{
typeOptions: {
/**
* Inner background options.
*
* @extends annotations.crookedLine.shapeOptions
* @excluding height, r, type, width
*/
innerBackground: {
fill: 'rgba(130, 170, 255, 0.4)',
strokeWidth: 0
},
/**
* Outer background options.
*
* @extends annotations.crookedLine.shapeOptions
* @excluding height, r, type, width
*/
outerBackground: {
fill: 'rgba(156, 229, 161, 0.4)',
strokeWidth: 0
}
}
});
Annotation.types.pitchfork = Pitchfork;
export default Pitchfork;
@@ -0,0 +1,194 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import Annotation from '../Annotations.js';
import ControlPoint from '../ControlPoint.js';
import CrookedLine from './CrookedLine.js';
import MockPoint from '../MockPoint.js';
import U from '../../../Core/Utilities.js';
var merge = U.merge;
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* @private
*/
function getSecondCoordinate(p1, p2, x) {
return (p2.y - p1.y) / (p2.x - p1.x) * (x - p1.x) + p1.y;
}
var Tunnel = /** @class */ (function (_super) {
__extends(Tunnel, _super);
/* *
*
* Constructors
*
* */
function Tunnel(chart, options) {
return _super.call(this, chart, options) || this;
}
/* *
*
* Functions
*
* */
Tunnel.prototype.getPointsOptions = function () {
var pointsOptions = CrookedLine.prototype.getPointsOptions.call(this);
pointsOptions[2] = this.heightPointOptions(pointsOptions[1]);
pointsOptions[3] = this.heightPointOptions(pointsOptions[0]);
return pointsOptions;
};
Tunnel.prototype.getControlPointsOptions = function () {
return this.getPointsOptions().slice(0, 2);
};
Tunnel.prototype.heightPointOptions = function (pointOptions) {
var heightPointOptions = merge(pointOptions), typeOptions = this.options.typeOptions;
heightPointOptions.y += typeOptions.height;
return heightPointOptions;
};
Tunnel.prototype.addControlPoints = function () {
CrookedLine.prototype.addControlPoints.call(this);
var options = this.options, typeOptions = options.typeOptions, controlPoint = new ControlPoint(this.chart, this, merge(options.controlPointOptions, typeOptions.heightControlPoint), 2);
this.controlPoints.push(controlPoint);
typeOptions.heightControlPoint = controlPoint.options;
};
Tunnel.prototype.addShapes = function () {
this.addLine();
this.addBackground();
};
Tunnel.prototype.addLine = function () {
var line = this.initShape(merge(this.options.typeOptions.line, {
type: 'path',
points: [
this.points[0],
this.points[1],
function (target) {
var pointOptions = MockPoint.pointToOptions(target.annotation.points[2]);
pointOptions.command = 'M';
return pointOptions;
},
this.points[3]
]
}), false);
this.options.typeOptions.line = line.options;
};
Tunnel.prototype.addBackground = function () {
var background = this.initShape(merge(this.options.typeOptions.background, {
type: 'path',
points: this.points.slice()
}));
this.options.typeOptions.background = background.options;
};
/**
* Translate start or end ("left" or "right") side of the tunnel.
* @private
* @param {number} dx - the amount of x translation
* @param {number} dy - the amount of y translation
* @param {boolean} [end] - whether to translate start or end side
*/
Tunnel.prototype.translateSide = function (dx, dy, end) {
var topIndex = Number(end), bottomIndex = topIndex === 0 ? 3 : 2;
this.translatePoint(dx, dy, topIndex);
this.translatePoint(dx, dy, bottomIndex);
};
/**
* Translate height of the tunnel.
* @private
* @param {number} dh - the amount of height translation
*/
Tunnel.prototype.translateHeight = function (dh) {
this.translatePoint(0, dh, 2);
this.translatePoint(0, dh, 3);
this.options.typeOptions.height = this.points[3].y - this.points[0].y;
};
return Tunnel;
}(CrookedLine));
Tunnel.prototype.defaultOptions = merge(CrookedLine.prototype.defaultOptions,
/**
* A tunnel annotation.
*
* @extends annotations.crookedLine
* @sample highcharts/annotations-advanced/tunnel/
* Tunnel
* @product highstock
* @optionparent annotations.tunnel
*/
{
typeOptions: {
xAxis: 0,
yAxis: 0,
/**
* Background options.
*
* @type {Object}
* @excluding height, point, points, r, type, width, markerEnd,
* markerStart
*/
background: {
fill: 'rgba(130, 170, 255, 0.4)',
strokeWidth: 0
},
line: {
strokeWidth: 1
},
/**
* The height of the annotation in terms of yAxis.
*/
height: -2,
/**
* Options for the control point which controls
* the annotation's height.
*
* @extends annotations.crookedLine.controlPointOptions
* @excluding positioner, events
*/
heightControlPoint: {
positioner: function (target) {
var startXY = MockPoint.pointToPixels(target.points[2]), endXY = MockPoint.pointToPixels(target.points[3]), x = (startXY.x + endXY.x) / 2;
return {
x: x - this.graphic.width / 2,
y: getSecondCoordinate(startXY, endXY, x) -
this.graphic.height / 2
};
},
events: {
drag: function (e, target) {
if (target.chart.isInsidePlot(e.chartX - target.chart.plotLeft, e.chartY - target.chart.plotTop)) {
target.translateHeight(this.mouseMoveToTranslation(e).y);
target.redraw(false);
}
}
}
}
},
/**
* @extends annotations.crookedLine.controlPointOptions
* @excluding positioner, events
*/
controlPointOptions: {
events: {
drag: function (e, target) {
if (target.chart.isInsidePlot(e.chartX - target.chart.plotLeft, e.chartY - target.chart.plotTop)) {
var translation = this.mouseMoveToTranslation(e);
target.translateSide(translation.x, translation.y, this.index);
target.redraw(false);
}
}
}
}
});
Annotation.types.tunnel = Tunnel;
export default Tunnel;
@@ -0,0 +1,147 @@
/* *
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import Annotation from '../Annotations.js';
import MockPoint from '../MockPoint.js';
import U from '../../../Core/Utilities.js';
var merge = U.merge;
/* eslint-disable no-invalid-this, valid-jsdoc */
var VerticalLine = /** @class */ (function (_super) {
__extends(VerticalLine, _super);
/* *
*
* Constructors
*
* */
function VerticalLine(chart, userOptions) {
return _super.call(this, chart, userOptions) || this;
}
/* *
*
* Static Functions
*
* */
VerticalLine.connectorFirstPoint = function (target) {
var annotation = target.annotation, point = annotation.points[0], xy = MockPoint.pointToPixels(point, true), y = xy.y, offset = annotation.options.typeOptions.label.offset;
if (annotation.chart.inverted) {
y = xy.x;
}
return {
x: point.x,
xAxis: point.series.xAxis,
y: y + offset
};
};
VerticalLine.connectorSecondPoint = function (target) {
var annotation = target.annotation, typeOptions = annotation.options.typeOptions, point = annotation.points[0], yOffset = typeOptions.yOffset, xy = MockPoint.pointToPixels(point, true), y = xy[annotation.chart.inverted ? 'x' : 'y'];
if (typeOptions.label.offset < 0) {
yOffset *= -1;
}
return {
x: point.x,
xAxis: point.series.xAxis,
y: y + yOffset
};
};
/* *
*
* Functions
*
* */
VerticalLine.prototype.getPointsOptions = function () {
return [this.options.typeOptions.point];
};
VerticalLine.prototype.addShapes = function () {
var typeOptions = this.options.typeOptions, connector = this.initShape(merge(typeOptions.connector, {
type: 'path',
points: [
VerticalLine.connectorFirstPoint,
VerticalLine.connectorSecondPoint
]
}), false);
typeOptions.connector = connector.options;
};
VerticalLine.prototype.addLabels = function () {
var typeOptions = this.options.typeOptions, labelOptions = typeOptions.label, x = 0, y = labelOptions.offset, verticalAlign = labelOptions.offset < 0 ? 'bottom' : 'top', align = 'center';
if (this.chart.inverted) {
x = labelOptions.offset;
y = 0;
verticalAlign = 'middle';
align = labelOptions.offset < 0 ? 'right' : 'left';
}
var label = this.initLabel(merge(labelOptions, {
verticalAlign: verticalAlign,
align: align,
x: x,
y: y
}));
typeOptions.label = label.options;
};
return VerticalLine;
}(Annotation));
VerticalLine.prototype.defaultOptions = merge(Annotation.prototype.defaultOptions,
/**
* A vertical line annotation.
*
* @sample highcharts/annotations-advanced/vertical-line/
* Vertical line
*
* @extends annotations.crookedLine
* @excluding labels, shapes, controlPointOptions
* @product highstock
* @optionparent annotations.verticalLine
*/
{
typeOptions: {
/**
* @ignore
*/
yOffset: 10,
/**
* Label options.
*
* @extends annotations.crookedLine.labelOptions
*/
label: {
offset: -40,
point: function (target) {
return target.annotation.points[0];
},
allowOverlap: true,
backgroundColor: 'none',
borderWidth: 0,
crop: true,
overflow: 'none',
shape: 'rect',
text: '{y:.2f}'
},
/**
* Connector options.
*
* @extends annotations.crookedLine.shapeOptions
* @excluding height, r, type, width
*/
connector: {
strokeWidth: 1,
markerEnd: 'arrow'
}
}
});
Annotation.types.verticalLine = VerticalLine;
export default VerticalLine;
@@ -0,0 +1,192 @@
/* *
*
* (c) 2017 Highsoft AS
* Authors: Lars A. V. Cabrera
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import SVGRenderer from '../Core/Renderer/SVG/SVGRenderer.js';
/**
* Creates an arrow symbol. Like a triangle, except not filled.
* ```
* o
* o
* o
* o
* o
* o
* o
* ```
*
* @private
* @function
*
* @param {number} x
* x position of the arrow
*
* @param {number} y
* y position of the arrow
*
* @param {number} w
* width of the arrow
*
* @param {number} h
* height of the arrow
*
* @return {Highcharts.SVGPathArray}
* Path array
*/
SVGRenderer.prototype.symbols.arrow = function (x, y, w, h) {
return [
['M', x, y + h / 2],
['L', x + w, y],
['L', x, y + h / 2],
['L', x + w, y + h]
];
};
/**
* Creates a half-width arrow symbol. Like a triangle, except not filled.
* ```
* o
* o
* o
* o
* o
* ```
*
* @private
* @function
*
* @param {number} x
* x position of the arrow
*
* @param {number} y
* y position of the arrow
*
* @param {number} w
* width of the arrow
*
* @param {number} h
* height of the arrow
*
* @return {Highcharts.SVGPathArray}
* Path array
*/
SVGRenderer.prototype.symbols['arrow-half'] = function (x, y, w, h) {
return SVGRenderer.prototype.symbols.arrow(x, y, w / 2, h);
};
/**
* Creates a left-oriented triangle.
* ```
* o
* ooooooo
* ooooooooooooo
* ooooooo
* o
* ```
*
* @private
* @function
*
* @param {number} x
* x position of the triangle
*
* @param {number} y
* y position of the triangle
*
* @param {number} w
* width of the triangle
*
* @param {number} h
* height of the triangle
*
* @return {Highcharts.SVGPathArray}
* Path array
*/
SVGRenderer.prototype.symbols['triangle-left'] = function (x, y, w, h) {
return [
['M', x + w, y],
['L', x, y + h / 2],
['L', x + w, y + h],
['Z']
];
};
/**
* Alias function for triangle-left.
*
* @private
* @function
*
* @param {number} x
* x position of the arrow
*
* @param {number} y
* y position of the arrow
*
* @param {number} w
* width of the arrow
*
* @param {number} h
* height of the arrow
*
* @return {Highcharts.SVGPathArray}
* Path array
*/
SVGRenderer.prototype.symbols['arrow-filled'] = SVGRenderer.prototype.symbols['triangle-left'];
/**
* Creates a half-width, left-oriented triangle.
* ```
* o
* oooo
* ooooooo
* oooo
* o
* ```
*
* @private
* @function
*
* @param {number} x
* x position of the triangle
*
* @param {number} y
* y position of the triangle
*
* @param {number} w
* width of the triangle
*
* @param {number} h
* height of the triangle
*
* @return {Highcharts.SVGPathArray}
* Path array
*/
SVGRenderer.prototype.symbols['triangle-left-half'] = function (x, y, w, h) {
return SVGRenderer.prototype.symbols['triangle-left'](x, y, w / 2, h);
};
/**
* Alias function for triangle-left-half.
*
* @private
* @function
*
* @param {number} x
* x position of the arrow
*
* @param {number} y
* y position of the arrow
*
* @param {number} w
* width of the arrow
*
* @param {number} h
* height of the arrow
*
* @return {Highcharts.SVGPathArray}
* Path array
*/
SVGRenderer.prototype.symbols['arrow-filled-half'] = SVGRenderer.prototype.symbols['triangle-left-half'];
@@ -0,0 +1,34 @@
/* *
*
* Copyright (c) 2019-2020 Highsoft AS
*
* Boost module: stripped-down renderer for higher performance
*
* License: highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../../Core/Globals.js';
import butils from './BoostUtils.js';
import init from './BoostInit.js';
import './BoostOverrides.js';
import './NamedColors.js';
import U from '../../Core/Utilities.js';
var error = U.error;
// These need to be fixed when we support named imports
var hasWebGLSupport = butils.hasWebGLSupport;
if (!hasWebGLSupport()) {
if (typeof H.initCanvasBoost !== 'undefined') {
// Fallback to canvas boost
H.initCanvasBoost();
}
else {
error(26);
}
}
else {
// WebGL support is alright, and we're good to go.
init();
}
@@ -0,0 +1,147 @@
/* *
*
* Copyright (c) 2019-2020 Highsoft AS
*
* Boost module: stripped-down renderer for higher performance
*
* License: highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import GLRenderer from './WGLRenderer.js';
import H from '../../Core/Globals.js';
var doc = H.doc;
import U from '../../Core/Utilities.js';
var error = U.error;
import '../../Core/Series/Series.js';
var mainCanvas = doc.createElement('canvas');
/**
* Create a canvas + context and attach it to the target
*
* @private
* @function createAndAttachRenderer
*
* @param {Highcharts.Chart} chart
* the chart
*
* @param {Highcharts.Series} series
* the series
*
* @return {Highcharts.BoostGLRenderer}
* the canvas renderer
*/
function createAndAttachRenderer(chart, series) {
var width = chart.chartWidth, height = chart.chartHeight, target = chart, targetGroup = chart.seriesGroup || series.group, alpha = 1, foSupported = doc.implementation.hasFeature('www.http://w3.org/TR/SVG11/feature#Extensibility', '1.1');
if (chart.isChartSeriesBoosting()) {
target = chart;
}
else {
target = series;
}
// Support for foreignObject is flimsy as best.
// IE does not support it, and Chrome has a bug which messes up
// the canvas draw order.
// As such, we force the Image fallback for now, but leaving the
// actual Canvas path in-place in case this changes in the future.
foSupported = false;
if (!target.renderTarget) {
target.canvas = mainCanvas;
// Fall back to image tag if foreignObject isn't supported,
// or if we're exporting.
if (chart.renderer.forExport || !foSupported) {
target.renderTarget = chart.renderer.image('', 0, 0, width, height)
.addClass('highcharts-boost-canvas')
.add(targetGroup);
target.boostClear = function () {
target.renderTarget.attr({ href: '' });
};
target.boostCopy = function () {
target.boostResizeTarget();
target.renderTarget.attr({
href: target.canvas.toDataURL('image/png')
});
};
}
else {
target.renderTargetFo = chart.renderer
.createElement('foreignObject')
.add(targetGroup);
target.renderTarget = doc.createElement('canvas');
target.renderTargetCtx =
target.renderTarget.getContext('2d');
target.renderTargetFo.element.appendChild(target.renderTarget);
target.boostClear = function () {
target.renderTarget.width =
target.canvas.width;
target.renderTarget.height =
target.canvas.height;
};
target.boostCopy = function () {
target.renderTarget.width =
target.canvas.width;
target.renderTarget.height =
target.canvas.height;
target.renderTargetCtx
.drawImage(target.canvas, 0, 0);
};
}
target.boostResizeTarget = function () {
width = chart.chartWidth;
height = chart.chartHeight;
(target.renderTargetFo || target.renderTarget)
.attr({
x: 0,
y: 0,
width: width,
height: height
})
.css({
pointerEvents: 'none',
mixedBlendMode: 'normal',
opacity: alpha
});
if (target instanceof H.Chart) {
target.markerGroup.translate(chart.plotLeft, chart.plotTop);
}
};
target.boostClipRect = chart.renderer.clipRect();
(target.renderTargetFo || target.renderTarget)
.clip(target.boostClipRect);
if (target instanceof H.Chart) {
target.markerGroup = target.renderer.g().add(targetGroup);
target.markerGroup.translate(series.xAxis.pos, series.yAxis.pos);
}
}
target.canvas.width = width;
target.canvas.height = height;
target.boostClipRect.attr(chart.getBoostClipRect(target));
target.boostResizeTarget();
target.boostClear();
if (!target.ogl) {
target.ogl = GLRenderer(function () {
if (target.ogl.settings.debug.timeBufferCopy) {
console.time('buffer copy'); // eslint-disable-line no-console
}
target.boostCopy();
if (target.ogl.settings.debug.timeBufferCopy) {
console.timeEnd('buffer copy'); // eslint-disable-line no-console
}
});
if (!target.ogl.init(target.canvas)) {
// The OGL renderer couldn't be inited.
// This likely means a shader error as we wouldn't get to this point
// if there was no WebGL support.
error('[highcharts boost] - unable to init WebGL renderer');
}
// target.ogl.clear();
target.ogl.setOptions(chart.options.boost || {});
if (target instanceof H.Chart) {
target.ogl.allocateBuffer(chart);
}
}
target.ogl.setSize(width, height);
return target.ogl;
}
export default createAndAttachRenderer;
@@ -0,0 +1,293 @@
/* *
*
* Copyright (c) 2019-2020 Highsoft AS
*
* Boost module: stripped-down renderer for higher performance
*
* License: highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Chart from '../../Core/Chart/Chart.js';
import H from '../../Core/Globals.js';
import U from '../../Core/Utilities.js';
var addEvent = U.addEvent, extend = U.extend, fireEvent = U.fireEvent, wrap = U.wrap;
import '../../Core/Series/Series.js';
import butils from './BoostUtils.js';
import createAndAttachRenderer from './BoostAttach.js';
var Series = H.Series, seriesTypes = H.seriesTypes, noop = function () { }, eachAsync = butils.eachAsync, pointDrawHandler = butils.pointDrawHandler, allocateIfNotSeriesBoosting = butils.allocateIfNotSeriesBoosting, renderIfNotSeriesBoosting = butils.renderIfNotSeriesBoosting, shouldForceChartSeriesBoosting = butils.shouldForceChartSeriesBoosting, index;
/* eslint-disable valid-jsdoc */
/**
* Initialize the boot module.
*
* @private
* @return {void}
*/
function init() {
extend(Series.prototype, {
/**
* @private
* @function Highcharts.Series#renderCanvas
*/
renderCanvas: function () {
var series = this, options = series.options || {}, renderer = false, chart = series.chart, xAxis = this.xAxis, yAxis = this.yAxis, xData = options.xData || series.processedXData, yData = options.yData || series.processedYData, rawData = options.data, xExtremes = xAxis.getExtremes(), xMin = xExtremes.min, xMax = xExtremes.max, yExtremes = yAxis.getExtremes(), yMin = yExtremes.min, yMax = yExtremes.max, pointTaken = {}, lastClientX, sampling = !!series.sampling, points, enableMouseTracking = options.enableMouseTracking !== false, threshold = options.threshold, yBottom = yAxis.getThreshold(threshold), isRange = series.pointArrayMap &&
series.pointArrayMap.join(',') === 'low,high', isStacked = !!options.stacking, cropStart = series.cropStart || 0, requireSorting = series.requireSorting, useRaw = !xData, minVal, maxVal, minI, maxI, boostOptions, compareX = options.findNearestPointBy === 'x', xDataFull = (this.xData ||
this.options.xData ||
this.processedXData ||
false), addKDPoint = function (clientX, plotY, i) {
// We need to do ceil on the clientX to make things
// snap to pixel values. The renderer will frequently
// draw stuff on "sub-pixels".
clientX = Math.ceil(clientX);
// Shaves off about 60ms compared to repeated concatenation
index = compareX ? clientX : clientX + ',' + plotY;
// The k-d tree requires series points.
// Reduce the amount of points, since the time to build the
// tree increases exponentially.
if (enableMouseTracking && !pointTaken[index]) {
pointTaken[index] = true;
if (chart.inverted) {
clientX = xAxis.len - clientX;
plotY = yAxis.len - plotY;
}
points.push({
x: xDataFull ? xDataFull[cropStart + i] : false,
clientX: clientX,
plotX: clientX,
plotY: plotY,
i: cropStart + i
});
}
};
// Get or create the renderer
renderer = createAndAttachRenderer(chart, series);
chart.isBoosting = true;
boostOptions = renderer.settings;
if (!this.visible) {
return;
}
// If we are zooming out from SVG mode, destroy the graphics
if (this.points || this.graph) {
this.destroyGraphics();
}
// If we're rendering per. series we should create the marker groups
// as usual.
if (!chart.isChartSeriesBoosting()) {
// If all series were boosting, but are not anymore
// restore private markerGroup
if (this.markerGroup === chart.markerGroup) {
this.markerGroup = void 0;
}
this.markerGroup = series.plotGroup('markerGroup', 'markers', true, 1, chart.seriesGroup);
}
else {
// If series has a private markeGroup, remove that
// and use common markerGroup
if (this.markerGroup &&
this.markerGroup !== chart.markerGroup) {
this.markerGroup.destroy();
}
// Use a single group for the markers
this.markerGroup = chart.markerGroup;
// When switching from chart boosting mode, destroy redundant
// series boosting targets
if (this.renderTarget) {
this.renderTarget = this.renderTarget.destroy();
}
}
points = this.points = [];
// Do not start building while drawing
series.buildKDTree = noop;
if (renderer) {
allocateIfNotSeriesBoosting(renderer, this);
renderer.pushSeries(series);
// Perform the actual renderer if we're on series level
renderIfNotSeriesBoosting(renderer, this, chart);
}
/**
* This builds the KD-tree
* @private
*/
function processPoint(d, i) {
var x, y, clientX, plotY, isNull, low = false, chartDestroyed = typeof chart.index === 'undefined', isYInside = true;
if (!chartDestroyed) {
if (useRaw) {
x = d[0];
y = d[1];
}
else {
x = d;
y = yData[i];
}
// Resolve low and high for range series
if (isRange) {
if (useRaw) {
y = d.slice(1, 3);
}
low = y[0];
y = y[1];
}
else if (isStacked) {
x = d.x;
y = d.stackY;
low = y - d.y;
}
isNull = y === null;
// Optimize for scatter zooming
if (!requireSorting) {
isYInside = y >= yMin && y <= yMax;
}
if (!isNull && x >= xMin && x <= xMax && isYInside) {
clientX = xAxis.toPixels(x, true);
if (sampling) {
if (typeof minI === 'undefined' ||
clientX === lastClientX) {
if (!isRange) {
low = y;
}
if (typeof maxI === 'undefined' ||
y > maxVal) {
maxVal = y;
maxI = i;
}
if (typeof minI === 'undefined' ||
low < minVal) {
minVal = low;
minI = i;
}
}
// Add points and reset
if (clientX !== lastClientX) {
// maxI is number too:
if (typeof minI !== 'undefined') {
plotY =
yAxis.toPixels(maxVal, true);
yBottom =
yAxis.toPixels(minVal, true);
addKDPoint(clientX, plotY, maxI);
if (yBottom !== plotY) {
addKDPoint(clientX, yBottom, minI);
}
}
minI = maxI = void 0;
lastClientX = clientX;
}
}
else {
plotY = Math.ceil(yAxis.toPixels(y, true));
addKDPoint(clientX, plotY, i);
}
}
}
return !chartDestroyed;
}
/**
* @private
*/
function doneProcessing() {
fireEvent(series, 'renderedCanvas');
// Go back to prototype, ready to build
delete series.buildKDTree;
series.buildKDTree();
if (boostOptions.debug.timeKDTree) {
console.timeEnd('kd tree building'); // eslint-disable-line no-console
}
}
// Loop over the points to build the k-d tree - skip this if
// exporting
if (!chart.renderer.forExport) {
if (boostOptions.debug.timeKDTree) {
console.time('kd tree building'); // eslint-disable-line no-console
}
eachAsync(isStacked ? series.data : (xData || rawData), processPoint, doneProcessing);
}
}
});
/*
* We need to handle heatmaps separatly, since we can't perform the
* size/color calculations in the shader easily.
*
* This likely needs future optimization.
*/
['heatmap', 'treemap'].forEach(function (t) {
if (seriesTypes[t]) {
wrap(seriesTypes[t].prototype, 'drawPoints', pointDrawHandler);
}
});
/* eslint-disable no-invalid-this */
if (seriesTypes.bubble) {
// By default, the bubble series does not use the KD-tree, so force it
// to.
delete seriesTypes.bubble.prototype.buildKDTree;
// seriesTypes.bubble.prototype.directTouch = false;
// Needed for markers to work correctly
wrap(seriesTypes.bubble.prototype, 'markerAttribs', function (proceed) {
if (this.isSeriesBoosting) {
return false;
}
return proceed.apply(this, [].slice.call(arguments, 1));
});
}
seriesTypes.scatter.prototype.fill = true;
extend(seriesTypes.area.prototype, {
fill: true,
fillOpacity: true,
sampling: true
});
extend(seriesTypes.column.prototype, {
fill: true,
sampling: true
});
// Take care of the canvas blitting
Chart.prototype.callbacks.push(function (chart) {
/**
* Convert chart-level canvas to image.
* @private
*/
function canvasToSVG() {
if (chart.ogl && chart.isChartSeriesBoosting()) {
chart.ogl.render(chart);
}
}
/**
* Clear chart-level canvas.
* @private
*/
function preRender() {
// Reset force state
chart.boostForceChartBoost = void 0;
chart.boostForceChartBoost = shouldForceChartSeriesBoosting(chart);
chart.isBoosting = false;
if (!chart.isChartSeriesBoosting() && chart.didBoost) {
chart.didBoost = false;
}
// Clear the canvas
if (chart.boostClear) {
chart.boostClear();
}
if (chart.canvas && chart.ogl && chart.isChartSeriesBoosting()) {
chart.didBoost = true;
// Allocate
chart.ogl.allocateBuffer(chart);
}
// see #6518 + #6739
if (chart.markerGroup &&
chart.xAxis &&
chart.xAxis.length > 0 &&
chart.yAxis &&
chart.yAxis.length > 0) {
chart.markerGroup.translate(chart.xAxis[0].pos, chart.yAxis[0].pos);
}
}
addEvent(chart, 'predraw', preRender);
addEvent(chart, 'render', canvasToSVG);
// addEvent(chart, 'zoom', function () {
// chart.boostForceChartBoost =
// shouldForceChartSeriesBoosting(chart);
// });
});
/* eslint-enable no-invalid-this */
}
export default init;
@@ -0,0 +1,178 @@
/* *
*
* Copyright (c) 2019-2020 Highsoft AS
*
* Boost module: stripped-down renderer for higher performance
*
* License: highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import '../../Core/Globals.js';
/**
* @requires modules/boost
* @apioption boost
*/
/**
* Set the series threshold for when the boost should kick in globally.
*
* Setting to e.g. 20 will cause the whole chart to enter boost mode
* if there are 20 or more series active. When the chart is in boost mode,
* every series in it will be rendered to a common canvas. This offers
* a significant speed improvment in charts with a very high
* amount of series.
*
* @type {number|null}
* @default null
* @apioption boost.seriesThreshold
*/
/**
* Enable or disable boost on a chart.
*
* @type {boolean}
* @default true
* @apioption boost.enabled
*/
/**
* Debugging options for boost.
* Useful for benchmarking, and general timing.
*
* @apioption boost.debug
*/
/**
* Time the series rendering.
*
* This outputs the time spent on actual rendering in the console when
* set to true.
*
* @type {boolean}
* @default false
* @apioption boost.debug.timeRendering
*/
/**
* Time the series processing.
*
* This outputs the time spent on transforming the series data to
* vertex buffers when set to true.
*
* @type {boolean}
* @default false
* @apioption boost.debug.timeSeriesProcessing
*/
/**
* Time the the WebGL setup.
*
* This outputs the time spent on setting up the WebGL context,
* creating shaders, and textures.
*
* @type {boolean}
* @default false
* @apioption boost.debug.timeSetup
*/
/**
* Time the building of the k-d tree.
*
* This outputs the time spent building the k-d tree used for
* markers etc.
*
* Note that the k-d tree is built async, and runs post-rendering.
* Following, it does not affect the performance of the rendering itself.
*
* @type {boolean}
* @default false
* @apioption boost.debug.timeKDTree
*/
/**
* Show the number of points skipped through culling.
*
* When set to true, the number of points skipped in series processing
* is outputted. Points are skipped if they are closer than 1 pixel from
* each other.
*
* @type {boolean}
* @default false
* @apioption boost.debug.showSkipSummary
*/
/**
* Time the WebGL to SVG buffer copy
*
* After rendering, the result is copied to an image which is injected
* into the SVG.
*
* If this property is set to true, the time it takes for the buffer copy
* to complete is outputted.
*
* @type {boolean}
* @default false
* @apioption boost.debug.timeBufferCopy
*/
/**
* Enable or disable GPU translations. GPU translations are faster than doing
* the translation in JavaScript.
*
* This option may cause rendering issues with certain datasets.
* Namely, if your dataset has large numbers with small increments (such as
* timestamps), it won't work correctly. This is due to floating point
* precission.
*
* @type {boolean}
* @default false
* @apioption boost.useGPUTranslations
*/
/**
* Enable or disable pre-allocation of vertex buffers.
*
* Enabling this will make it so that the binary data arrays required for
* storing the series data will be allocated prior to transforming the data
* to a WebGL-compatible format.
*
* This saves a copy operation on the order of O(n) and so is significantly more
* performant. However, this is currently an experimental option, and may cause
* visual artifacts with some datasets.
*
* As such, care should be taken when using this setting to make sure that
* it doesn't cause any rendering glitches with the given use-case.
*
* @type {boolean}
* @default false
* @apioption boost.usePreallocated
*/
/**
* Set the point threshold for when a series should enter boost mode.
*
* Setting it to e.g. 2000 will cause the series to enter boost mode when there
* are 2000 or more points in the series.
*
* To disable boosting on the series, set the `boostThreshold` to 0. Setting it
* to 1 will force boosting.
*
* Note that the [cropThreshold](plotOptions.series.cropThreshold) also affects
* this setting. When zooming in on a series that has fewer points than the
* `cropThreshold`, all points are rendered although outside the visible plot
* area, and the `boostThreshold` won't take effect.
*
* @type {number}
* @default 5000
* @requires modules/boost
* @apioption plotOptions.series.boostThreshold
*/
/**
* If set to true, the whole chart will be boosted if one of the series
* crosses its threshold, and all the series can be boosted.
*
* @type {boolean}
* @default true
* @apioption boost.allowForce
*/
/**
* Sets the color blending in the boost module.
*
* @type {string}
* @default undefined
* @validvalue ["add", "multiply", "darken"]
* @requires modules/boost
* @apioption plotOptions.series.boostBlending
*/
''; // adds doclets above to transpiled file
@@ -0,0 +1,368 @@
/* *
*
* Copyright (c) 2019-2020 Highsoft AS
*
* Boost module: stripped-down renderer for higher performance
*
* License: highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Chart from '../../Core/Chart/Chart.js';
import H from '../../Core/Globals.js';
import Point from '../../Core/Series/Point.js';
import U from '../../Core/Utilities.js';
var addEvent = U.addEvent, error = U.error, getOptions = U.getOptions, isArray = U.isArray, isNumber = U.isNumber, pick = U.pick, wrap = U.wrap;
import '../../Core/Series/Series.js';
import '../../Core/Options.js';
import '../../Core/Interaction.js';
import butils from './BoostUtils.js';
import boostable from './Boostables.js';
import boostableMap from './BoostableMap.js';
var boostEnabled = butils.boostEnabled, shouldForceChartSeriesBoosting = butils.shouldForceChartSeriesBoosting, Series = H.Series, seriesTypes = H.seriesTypes, plotOptions = getOptions().plotOptions;
/**
* Returns true if the chart is in series boost mode.
*
* @function Highcharts.Chart#isChartSeriesBoosting
*
* @param {Highcharts.Chart} chart
* the chart to check
*
* @return {boolean}
* true if the chart is in series boost mode
*/
Chart.prototype.isChartSeriesBoosting = function () {
var isSeriesBoosting, threshold = pick(this.options.boost && this.options.boost.seriesThreshold, 50);
isSeriesBoosting = threshold <= this.series.length ||
shouldForceChartSeriesBoosting(this);
return isSeriesBoosting;
};
/* eslint-disable valid-jsdoc */
/**
* Get the clip rectangle for a target, either a series or the chart. For the
* chart, we need to consider the maximum extent of its Y axes, in case of
* Highstock panes and navigator.
*
* @private
* @function Highcharts.Chart#getBoostClipRect
*
* @param {Highcharts.Chart} target
*
* @return {Highcharts.BBoxObject}
*/
Chart.prototype.getBoostClipRect = function (target) {
var clipBox = {
x: this.plotLeft,
y: this.plotTop,
width: this.plotWidth,
height: this.plotHeight
};
if (target === this) {
this.yAxis.forEach(function (yAxis) {
clipBox.y = Math.min(yAxis.pos, clipBox.y);
clipBox.height = Math.max(yAxis.pos - this.plotTop + yAxis.len, clipBox.height);
}, this);
}
return clipBox;
};
/**
* Return a full Point object based on the index.
* The boost module uses stripped point objects for performance reasons.
*
* @function Highcharts.Series#getPoint
*
* @param {object|Highcharts.Point} boostPoint
* A stripped-down point object
*
* @return {Highcharts.Point}
* A Point object as per https://api.highcharts.com/highcharts#Point
*/
Series.prototype.getPoint = function (boostPoint) {
var point = boostPoint, xData = (this.xData || this.options.xData || this.processedXData ||
false);
if (boostPoint && !(boostPoint instanceof this.pointClass)) {
point = (new this.pointClass()).init(// eslint-disable-line new-cap
this, this.options.data[boostPoint.i], xData ? xData[boostPoint.i] : void 0);
point.category = pick(this.xAxis.categories ?
this.xAxis.categories[point.x] :
point.x, // @todo simplify
point.x);
point.dist = boostPoint.dist;
point.distX = boostPoint.distX;
point.plotX = boostPoint.plotX;
point.plotY = boostPoint.plotY;
point.index = boostPoint.i;
point.isInside = this.isPointInside(boostPoint);
}
return point;
};
/* eslint-disable no-invalid-this */
// Return a point instance from the k-d-tree
wrap(Series.prototype, 'searchPoint', function (proceed) {
return this.getPoint(proceed.apply(this, [].slice.call(arguments, 1)));
});
// For inverted series, we need to swap X-Y values before running base methods
wrap(Point.prototype, 'haloPath', function (proceed) {
var halo, point = this, series = point.series, chart = series.chart, plotX = point.plotX, plotY = point.plotY, inverted = chart.inverted;
if (series.isSeriesBoosting && inverted) {
point.plotX = series.yAxis.len - plotY;
point.plotY = series.xAxis.len - plotX;
}
halo = proceed.apply(this, Array.prototype.slice.call(arguments, 1));
if (series.isSeriesBoosting && inverted) {
point.plotX = plotX;
point.plotY = plotY;
}
return halo;
});
wrap(Series.prototype, 'markerAttribs', function (proceed, point) {
var attribs, series = this, chart = series.chart, plotX = point.plotX, plotY = point.plotY, inverted = chart.inverted;
if (series.isSeriesBoosting && inverted) {
point.plotX = series.yAxis.len - plotY;
point.plotY = series.xAxis.len - plotX;
}
attribs = proceed.apply(this, Array.prototype.slice.call(arguments, 1));
if (series.isSeriesBoosting && inverted) {
point.plotX = plotX;
point.plotY = plotY;
}
return attribs;
});
/*
* Extend series.destroy to also remove the fake k-d-tree points (#5137).
* Normally this is handled by Series.destroy that calls Point.destroy,
* but the fake search points are not registered like that.
*/
addEvent(Series, 'destroy', function () {
var series = this, chart = series.chart;
if (chart.markerGroup === series.markerGroup) {
series.markerGroup = null;
}
if (chart.hoverPoints) {
chart.hoverPoints = chart.hoverPoints.filter(function (point) {
return point.series === series;
});
}
if (chart.hoverPoint && chart.hoverPoint.series === series) {
chart.hoverPoint = null;
}
});
/*
* Do not compute extremes when min and max are set.
* If we use this in the core, we can add the hook
* to hasExtremes to the methods directly.
*/
wrap(Series.prototype, 'getExtremes', function (proceed) {
if (!this.isSeriesBoosting || (!this.hasExtremes || !this.hasExtremes())) {
return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
}
return {};
});
/*
* Override a bunch of methods the same way. If the number of points is
* below the threshold, run the original method. If not, check for a
* canvas version or do nothing.
*
* Note that we're not overriding any of these for heatmaps.
*/
[
'translate',
'generatePoints',
'drawTracker',
'drawPoints',
'render'
].forEach(function (method) {
/**
* @private
*/
function branch(proceed) {
var letItPass = this.options.stacking &&
(method === 'translate' || method === 'generatePoints');
if (!this.isSeriesBoosting ||
letItPass ||
!boostEnabled(this.chart) ||
this.type === 'heatmap' ||
this.type === 'treemap' ||
!boostableMap[this.type] ||
this.options.boostThreshold === 0) {
proceed.call(this);
// If a canvas version of the method exists, like renderCanvas(), run
}
else if (this[method + 'Canvas']) {
this[method + 'Canvas']();
}
}
wrap(Series.prototype, method, branch);
// A special case for some types - their translate method is already wrapped
if (method === 'translate') {
[
'column',
'bar',
'arearange',
'columnrange',
'heatmap',
'treemap'
].forEach(function (type) {
if (seriesTypes[type]) {
wrap(seriesTypes[type].prototype, method, branch);
}
});
}
});
// If the series is a heatmap or treemap, or if the series is not boosting
// do the default behaviour. Otherwise, process if the series has no extremes.
wrap(Series.prototype, 'processData', function (proceed) {
var series = this, dataToMeasure = this.options.data, firstPoint;
/**
* Used twice in this function, first on this.options.data, the second
* time it runs the check again after processedXData is built.
* @private
* @todo Check what happens with data grouping
*/
function getSeriesBoosting(data) {
return series.chart.isChartSeriesBoosting() || ((data ? data.length : 0) >=
(series.options.boostThreshold || Number.MAX_VALUE));
}
if (boostEnabled(this.chart) && boostableMap[this.type]) {
// If there are no extremes given in the options, we also need to
// process the data to read the data extremes. If this is a heatmap, do
// default behaviour.
if (!getSeriesBoosting(dataToMeasure) || // First pass with options.data
this.type === 'heatmap' ||
this.type === 'treemap' ||
this.options.stacking || // processedYData for the stack (#7481)
!this.hasExtremes ||
!this.hasExtremes(true)) {
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
dataToMeasure = this.processedXData;
}
// Set the isBoosting flag, second pass with processedXData to see if we
// have zoomed.
this.isSeriesBoosting = getSeriesBoosting(dataToMeasure);
// Enter or exit boost mode
if (this.isSeriesBoosting) {
// Force turbo-mode:
firstPoint = this.getFirstValidPoint(this.options.data);
if (!isNumber(firstPoint) && !isArray(firstPoint)) {
error(12, false, this.chart);
}
this.enterBoost();
}
else if (this.exitBoost) {
this.exitBoost();
}
// The series type is not boostable
}
else {
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
}
});
addEvent(Series, 'hide', function () {
if (this.canvas && this.renderTarget) {
if (this.ogl) {
this.ogl.clear();
}
this.boostClear();
}
});
/**
* Enter boost mode and apply boost-specific properties.
*
* @function Highcharts.Series#enterBoost
*/
Series.prototype.enterBoost = function () {
this.alteredByBoost = [];
// Save the original values, including whether it was an own property or
// inherited from the prototype.
['allowDG', 'directTouch', 'stickyTracking'].forEach(function (prop) {
this.alteredByBoost.push({
prop: prop,
val: this[prop],
own: Object.hasOwnProperty.call(this, prop)
});
}, this);
this.allowDG = false;
this.directTouch = false;
this.stickyTracking = true;
// Prevent animation when zooming in on boosted series(#13421).
this.finishedAnimating = true;
// Hide series label if any
if (this.labelBySeries) {
this.labelBySeries = this.labelBySeries.destroy();
}
};
/**
* Exit from boost mode and restore non-boost properties.
*
* @function Highcharts.Series#exitBoost
*/
Series.prototype.exitBoost = function () {
// Reset instance properties and/or delete instance properties and go back
// to prototype
(this.alteredByBoost || []).forEach(function (setting) {
if (setting.own) {
this[setting.prop] = setting.val;
}
else {
// Revert to prototype
delete this[setting.prop];
}
}, this);
// Clear previous run
if (this.boostClear) {
this.boostClear();
}
};
/**
* @private
* @function Highcharts.Series#hasExtremes
*
* @param {boolean} checkX
*
* @return {boolean}
*/
Series.prototype.hasExtremes = function (checkX) {
var options = this.options, data = options.data, xAxis = this.xAxis && this.xAxis.options, yAxis = this.yAxis && this.yAxis.options, colorAxis = this.colorAxis && this.colorAxis.options;
return data.length > (options.boostThreshold || Number.MAX_VALUE) &&
// Defined yAxis extremes
isNumber(yAxis.min) &&
isNumber(yAxis.max) &&
// Defined (and required) xAxis extremes
(!checkX ||
(isNumber(xAxis.min) && isNumber(xAxis.max))) &&
// Defined (e.g. heatmap) colorAxis extremes
(!colorAxis ||
(isNumber(colorAxis.min) && isNumber(colorAxis.max)));
};
/**
* If implemented in the core, parts of this can probably be
* shared with other similar methods in Highcharts.
*
* @function Highcharts.Series#destroyGraphics
*/
Series.prototype.destroyGraphics = function () {
var series = this, points = this.points, point, i;
if (points) {
for (i = 0; i < points.length; i = i + 1) {
point = points[i];
if (point && point.destroyElements) {
point.destroyElements(); // #7557
}
}
}
['graph', 'area', 'tracker'].forEach(function (prop) {
if (series[prop]) {
series[prop] = series[prop].destroy();
}
});
};
// Set default options
boostable.forEach(function (type) {
if (plotOptions[type]) {
plotOptions[type].boostThreshold = 5000;
plotOptions[type].boostData = [];
seriesTypes[type].prototype.fillOpacity = true;
}
});
@@ -0,0 +1,262 @@
/* *
*
* Copyright (c) 2019-2020 Highsoft AS
*
* Boost module: stripped-down renderer for higher performance
*
* License: highcharts.com/license
*
* This files contains generic utility functions used by the boost module.
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../../Core/Globals.js';
var win = H.win, doc = H.doc;
import '../../Core/Series/Series.js';
import boostableMap from './BoostableMap.js';
import createAndAttachRenderer from './BoostAttach.js';
import U from '../../Core/Utilities.js';
var pick = U.pick;
// This should be a const.
var CHUNK_SIZE = 3000;
/**
* Tolerant max() function.
*
* @private
* @function patientMax
*
* @param {...Array<Array<unknown>>} args
* Max arguments
*
* @return {number}
* Max value
*/
function patientMax() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var r = -Number.MAX_VALUE;
args.forEach(function (t) {
if (typeof t !== 'undefined' &&
t !== null &&
typeof t.length !== 'undefined') {
// r = r < t.length ? t.length : r;
if (t.length > 0) {
r = t.length;
return true;
}
}
});
return r;
}
/**
* Return true if ths boost.enabled option is true
*
* @private
* @function boostEnabled
*
* @param {Highcharts.Chart} chart
* The chart
*
* @return {boolean}
* True, if boost is enabled.
*/
function boostEnabled(chart) {
return pick((chart &&
chart.options &&
chart.options.boost &&
chart.options.boost.enabled), true);
}
/**
* Returns true if we should force boosting the chart
* @private
* @function shouldForceChartSeriesBoosting
*
* @param {Highcharts.Chart} chart
* The chart to check for forcing on
*
* @return {boolean}
* True, if boosting should be forced.
*/
function shouldForceChartSeriesBoosting(chart) {
// If there are more than five series currently boosting,
// we should boost the whole chart to avoid running out of webgl contexts.
var sboostCount = 0, canBoostCount = 0, allowBoostForce = pick(chart.options.boost && chart.options.boost.allowForce, true), series;
if (typeof chart.boostForceChartBoost !== 'undefined') {
return chart.boostForceChartBoost;
}
if (chart.series.length > 1) {
for (var i = 0; i < chart.series.length; i++) {
series = chart.series[i];
// Don't count series with boostThreshold set to 0
// See #8950
// Also don't count if the series is hidden.
// See #9046
if (series.options.boostThreshold === 0 ||
series.visible === false) {
continue;
}
// Don't count heatmap series as they are handled differently.
// In the future we should make the heatmap/treemap path compatible
// with forcing. See #9636.
if (series.type === 'heatmap') {
continue;
}
if (boostableMap[series.type]) {
++canBoostCount;
}
if (patientMax(series.processedXData, series.options.data,
// series.xData,
series.points) >= (series.options.boostThreshold || Number.MAX_VALUE)) {
++sboostCount;
}
}
}
chart.boostForceChartBoost = allowBoostForce && ((canBoostCount === chart.series.length &&
sboostCount > 0) ||
sboostCount > 5);
return chart.boostForceChartBoost;
}
/* eslint-disable valid-jsdoc */
/**
* Performs the actual render if the renderer is
* attached to the series.
* @private
* @param renderer {OGLRenderer} - the renderer
* @param series {Highcharts.Series} - the series
*/
function renderIfNotSeriesBoosting(renderer, series, chart) {
if (renderer &&
series.renderTarget &&
series.canvas &&
!(chart || series.chart).isChartSeriesBoosting()) {
renderer.render(chart || series.chart);
}
}
/**
* @private
*/
function allocateIfNotSeriesBoosting(renderer, series) {
if (renderer &&
series.renderTarget &&
series.canvas &&
!series.chart.isChartSeriesBoosting()) {
renderer.allocateBufferForSingleSeries(series);
}
}
/**
* An "async" foreach loop. Uses a setTimeout to keep the loop from blocking the
* UI thread.
*
* @private
*
* @param arr {Array} - the array to loop through
* @param fn {Function} - the callback to call for each item
* @param finalFunc {Function} - the callback to call when done
* @param chunkSize {Number} - the number of iterations per timeout
* @param i {Number} - the current index
* @param noTimeout {Boolean} - set to true to skip timeouts
*/
function eachAsync(arr, fn, finalFunc, chunkSize, i, noTimeout) {
i = i || 0;
chunkSize = chunkSize || CHUNK_SIZE;
var threshold = i + chunkSize, proceed = true;
while (proceed && i < threshold && i < arr.length) {
proceed = fn(arr[i], i);
++i;
}
if (proceed) {
if (i < arr.length) {
if (noTimeout) {
eachAsync(arr, fn, finalFunc, chunkSize, i, noTimeout);
}
else if (win.requestAnimationFrame) {
// If available, do requestAnimationFrame - shaves off a few ms
win.requestAnimationFrame(function () {
eachAsync(arr, fn, finalFunc, chunkSize, i);
});
}
else {
setTimeout(function () {
eachAsync(arr, fn, finalFunc, chunkSize, i);
});
}
}
else if (finalFunc) {
finalFunc();
}
}
}
/**
* Returns true if the current browser supports webgl
*
* @private
* @function hasWebGLSupport
*
* @return {boolean}
*/
function hasWebGLSupport() {
var i = 0, canvas, contexts = ['webgl', 'experimental-webgl', 'moz-webgl', 'webkit-3d'], context = false;
if (typeof win.WebGLRenderingContext !== 'undefined') {
canvas = doc.createElement('canvas');
for (; i < contexts.length; i++) {
try {
context = canvas.getContext(contexts[i]);
if (typeof context !== 'undefined' && context !== null) {
return true;
}
}
catch (e) {
// silent error
}
}
}
return false;
}
/* eslint-disable no-invalid-this */
/**
* Used for treemap|heatmap.drawPoints
*
* @private
* @function pointDrawHandler
*
* @param {Function} proceed
*
* @return {*}
*/
function pointDrawHandler(proceed) {
var enabled = true, renderer;
if (this.chart.options && this.chart.options.boost) {
enabled = typeof this.chart.options.boost.enabled === 'undefined' ?
true :
this.chart.options.boost.enabled;
}
if (!enabled || !this.isSeriesBoosting) {
return proceed.call(this);
}
this.chart.isBoosting = true;
// Make sure we have a valid OGL context
renderer = createAndAttachRenderer(this.chart, this);
if (renderer) {
allocateIfNotSeriesBoosting(renderer, this);
renderer.pushSeries(this);
}
renderIfNotSeriesBoosting(renderer, this);
}
/* eslint-enable no-invalid-this, valid-jsdoc */
var funs = {
patientMax: patientMax,
boostEnabled: boostEnabled,
shouldForceChartSeriesBoosting: shouldForceChartSeriesBoosting,
renderIfNotSeriesBoosting: renderIfNotSeriesBoosting,
allocateIfNotSeriesBoosting: allocateIfNotSeriesBoosting,
eachAsync: eachAsync,
hasWebGLSupport: hasWebGLSupport,
pointDrawHandler: pointDrawHandler
};
// This needs to be fixed.
H.hasWebGLSupport = hasWebGLSupport;
export default funs;
@@ -0,0 +1,18 @@
/* *
*
* Copyright (c) 2019-2020 Highsoft AS
*
* Boost module: stripped-down renderer for higher performance
*
* License: highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
import boostables from './Boostables.js';
// These are the series we allow boosting for.
var boostableMap = {};
boostables.forEach(function (item) {
boostableMap[item] = 1;
});
export default boostableMap;
@@ -0,0 +1,25 @@
/* *
*
* Copyright (c) 2019-2020 Highsoft AS
*
* Boost module: stripped-down renderer for higher performance
*
* License: highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
// These are the series we allow boosting for.
var boostables = [
'area',
'arearange',
'column',
'columnrange',
'bar',
'line',
'scatter',
'heatmap',
'bubble',
'treemap'
];
export default boostables;
@@ -0,0 +1,162 @@
/* *
*
* Copyright (c) 2019-2020 Highsoft AS
*
* Boost module: stripped-down renderer for higher performance
*
* License: highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Color from '../../Core/Color.js';
// Register color names since GL can't render those directly.
// TODO: When supporting modern syntax, make this a const and a named export
var defaultHTMLColorMap = {
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#00ffff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000000',
blanchedalmond: '#ffebcd',
blue: '#0000ff',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#00ffff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgreen: '#006400',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dodgerblue: '#1e90ff',
feldspar: '#d19275',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#ff00ff',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
green: '#008000',
greenyellow: '#adff2f',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
indianred: '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgrey: '#d3d3d3',
lightgreen: '#90ee90',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslateblue: '#8470ff',
lightslategray: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#00ff00',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370d8',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#d87093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
red: '#ff0000',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
violetred: '#d02090',
wheat: '#f5deb3',
white: '#ffffff',
whitesmoke: '#f5f5f5',
yellow: '#ffff00',
yellowgreen: '#9acd32'
};
Color.names = defaultHTMLColorMap;
export default defaultHTMLColorMap;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,469 @@
/* *
*
* Copyright (c) 2019-2020 Highsoft AS
*
* Boost module: stripped-down renderer for higher performance
*
* License: highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import U from '../../Core/Utilities.js';
var clamp = U.clamp, error = U.error, pick = U.pick;
/* eslint-disable valid-jsdoc */
/**
* A static shader mimicing axis translation functions found in Core/Axis
*
* @private
* @function GLShader
*
* @param {WebGLContext} gl
* the context in which the shader is active
*
* @return {*}
*/
function GLShader(gl) {
var vertShade = [
/* eslint-disable max-len, @typescript-eslint/indent */
'#version 100',
'#define LN10 2.302585092994046',
'precision highp float;',
'attribute vec4 aVertexPosition;',
'attribute vec4 aColor;',
'varying highp vec2 position;',
'varying highp vec4 vColor;',
'uniform mat4 uPMatrix;',
'uniform float pSize;',
'uniform float translatedThreshold;',
'uniform bool hasThreshold;',
'uniform bool skipTranslation;',
'uniform float xAxisTrans;',
'uniform float xAxisMin;',
'uniform float xAxisMinPad;',
'uniform float xAxisPointRange;',
'uniform float xAxisLen;',
'uniform bool xAxisPostTranslate;',
'uniform float xAxisOrdinalSlope;',
'uniform float xAxisOrdinalOffset;',
'uniform float xAxisPos;',
'uniform bool xAxisCVSCoord;',
'uniform bool xAxisIsLog;',
'uniform bool xAxisReversed;',
'uniform float yAxisTrans;',
'uniform float yAxisMin;',
'uniform float yAxisMinPad;',
'uniform float yAxisPointRange;',
'uniform float yAxisLen;',
'uniform bool yAxisPostTranslate;',
'uniform float yAxisOrdinalSlope;',
'uniform float yAxisOrdinalOffset;',
'uniform float yAxisPos;',
'uniform bool yAxisCVSCoord;',
'uniform bool yAxisIsLog;',
'uniform bool yAxisReversed;',
'uniform bool isBubble;',
'uniform bool bubbleSizeByArea;',
'uniform float bubbleZMin;',
'uniform float bubbleZMax;',
'uniform float bubbleZThreshold;',
'uniform float bubbleMinSize;',
'uniform float bubbleMaxSize;',
'uniform bool bubbleSizeAbs;',
'uniform bool isInverted;',
'float bubbleRadius(){',
'float value = aVertexPosition.w;',
'float zMax = bubbleZMax;',
'float zMin = bubbleZMin;',
'float radius = 0.0;',
'float pos = 0.0;',
'float zRange = zMax - zMin;',
'if (bubbleSizeAbs){',
'value = value - bubbleZThreshold;',
'zMax = max(zMax - bubbleZThreshold, zMin - bubbleZThreshold);',
'zMin = 0.0;',
'}',
'if (value < zMin){',
'radius = bubbleZMin / 2.0 - 1.0;',
'} else {',
'pos = zRange > 0.0 ? (value - zMin) / zRange : 0.5;',
'if (bubbleSizeByArea && pos > 0.0){',
'pos = sqrt(pos);',
'}',
'radius = ceil(bubbleMinSize + pos * (bubbleMaxSize - bubbleMinSize)) / 2.0;',
'}',
'return radius * 2.0;',
'}',
'float translate(float val,',
'float pointPlacement,',
'float localA,',
'float localMin,',
'float minPixelPadding,',
'float pointRange,',
'float len,',
'bool cvsCoord,',
'bool isLog,',
'bool reversed',
'){',
'float sign = 1.0;',
'float cvsOffset = 0.0;',
'if (cvsCoord) {',
'sign *= -1.0;',
'cvsOffset = len;',
'}',
'if (isLog) {',
'val = log(val) / LN10;',
'}',
'if (reversed) {',
'sign *= -1.0;',
'cvsOffset -= sign * len;',
'}',
'return sign * (val - localMin) * localA + cvsOffset + ',
'(sign * minPixelPadding);',
'}',
'float xToPixels(float value) {',
'if (skipTranslation){',
'return value;// + xAxisPos;',
'}',
'return translate(value, 0.0, xAxisTrans, xAxisMin, xAxisMinPad, xAxisPointRange, xAxisLen, xAxisCVSCoord, xAxisIsLog, xAxisReversed);// + xAxisPos;',
'}',
'float yToPixels(float value, float checkTreshold) {',
'float v;',
'if (skipTranslation){',
'v = value;// + yAxisPos;',
'} else {',
'v = translate(value, 0.0, yAxisTrans, yAxisMin, yAxisMinPad, yAxisPointRange, yAxisLen, yAxisCVSCoord, yAxisIsLog, yAxisReversed);// + yAxisPos;',
'if (v > yAxisLen) {',
'v = yAxisLen;',
'}',
'}',
'if (checkTreshold > 0.0 && hasThreshold) {',
'v = min(v, translatedThreshold);',
'}',
'return v;',
'}',
'void main(void) {',
'if (isBubble){',
'gl_PointSize = bubbleRadius();',
'} else {',
'gl_PointSize = pSize;',
'}',
// 'gl_PointSize = 10.0;',
'vColor = aColor;',
'if (skipTranslation && isInverted) {',
// If we get translated values from JS, just swap them (x, y)
'gl_Position = uPMatrix * vec4(aVertexPosition.y + yAxisPos, aVertexPosition.x + xAxisPos, 0.0, 1.0);',
'} else if (isInverted) {',
// But when calculating pixel positions directly,
// swap axes and values (x, y)
'gl_Position = uPMatrix * vec4(yToPixels(aVertexPosition.y, aVertexPosition.z) + yAxisPos, xToPixels(aVertexPosition.x) + xAxisPos, 0.0, 1.0);',
'} else {',
'gl_Position = uPMatrix * vec4(xToPixels(aVertexPosition.x) + xAxisPos, yToPixels(aVertexPosition.y, aVertexPosition.z) + yAxisPos, 0.0, 1.0);',
'}',
// 'gl_Position = uPMatrix * vec4(aVertexPosition.x, aVertexPosition.y, 0.0, 1.0);',
'}'
/* eslint-enable max-len, @typescript-eslint/indent */
].join('\n'),
// Fragment shader source
fragShade = [
/* eslint-disable max-len, @typescript-eslint/indent */
'precision highp float;',
'uniform vec4 fillColor;',
'varying highp vec2 position;',
'varying highp vec4 vColor;',
'uniform sampler2D uSampler;',
'uniform bool isCircle;',
'uniform bool hasColor;',
// 'vec4 toColor(float value, vec2 point) {',
// 'return vec4(0.0, 0.0, 0.0, 0.0);',
// '}',
'void main(void) {',
'vec4 col = fillColor;',
'vec4 tcol;',
'if (hasColor) {',
'col = vColor;',
'}',
'if (isCircle) {',
'tcol = texture2D(uSampler, gl_PointCoord.st);',
'col *= tcol;',
'if (tcol.r < 0.0) {',
'discard;',
'} else {',
'gl_FragColor = col;',
'}',
'} else {',
'gl_FragColor = col;',
'}',
'}'
/* eslint-enable max-len, @typescript-eslint/indent */
].join('\n'), uLocations = {},
// The shader program
shaderProgram,
// Uniform handle to the perspective matrix
pUniform,
// Uniform for point size
psUniform,
// Uniform for fill color
fillColorUniform,
// Uniform for isBubble
isBubbleUniform,
// Uniform for bubble abs sizing
bubbleSizeAbsUniform, bubbleSizeAreaUniform,
// Skip translation uniform
skipTranslationUniform,
// Set to 1 if circle
isCircleUniform,
// Uniform for invertion
isInverted,
// Error stack
errors = [],
// Texture uniform
uSamplerUniform;
/**
* Handle errors accumulated in errors stack
* @private
*/
function handleErrors() {
if (errors.length) {
error('[highcharts boost] shader error - ' + errors.join('\n'));
}
}
/**
* String to shader program
* @private
* @param {string} str - the program source
* @param {string} type - the program type: either `vertex` or `fragment`
* @returns {bool|shader}
*/
function stringToProgram(str, type) {
var t = type === 'vertex' ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER, shader = gl.createShader(t);
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
errors.push('when compiling ' +
type +
' shader:\n' +
gl.getShaderInfoLog(shader));
return false;
}
return shader;
}
/**
* Create the shader.
* Loads the shader program statically defined above
* @private
*/
function createShader() {
var v = stringToProgram(vertShade, 'vertex'), f = stringToProgram(fragShade, 'fragment');
if (!v || !f) {
shaderProgram = false;
handleErrors();
return false;
}
/**
* @private
*/
function uloc(n) {
return gl.getUniformLocation(shaderProgram, n);
}
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, v);
gl.attachShader(shaderProgram, f);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
errors.push(gl.getProgramInfoLog(shaderProgram));
handleErrors();
shaderProgram = false;
return false;
}
gl.useProgram(shaderProgram);
gl.bindAttribLocation(shaderProgram, 0, 'aVertexPosition');
pUniform = uloc('uPMatrix');
psUniform = uloc('pSize');
fillColorUniform = uloc('fillColor');
isBubbleUniform = uloc('isBubble');
bubbleSizeAbsUniform = uloc('bubbleSizeAbs');
bubbleSizeAreaUniform = uloc('bubbleSizeByArea');
uSamplerUniform = uloc('uSampler');
skipTranslationUniform = uloc('skipTranslation');
isCircleUniform = uloc('isCircle');
isInverted = uloc('isInverted');
return true;
}
/**
* Destroy the shader
* @private
*/
function destroy() {
if (gl && shaderProgram) {
gl.deleteProgram(shaderProgram);
shaderProgram = false;
}
}
/**
* Bind the shader.
* This makes the shader the active one until another one is bound,
* or until 0 is bound.
* @private
*/
function bind() {
if (gl && shaderProgram) {
gl.useProgram(shaderProgram);
}
}
/**
* Set a uniform value.
* This uses a hash map to cache uniform locations.
* @private
* @param name {string} - the name of the uniform to set
* @param val {float} - the value to set
*/
function setUniform(name, val) {
if (gl && shaderProgram) {
var u = uLocations[name] = (uLocations[name] ||
gl.getUniformLocation(shaderProgram, name));
gl.uniform1f(u, val);
}
}
/**
* Set the active texture
* @private
* @param texture - the texture
*/
function setTexture(texture) {
if (gl && shaderProgram) {
gl.uniform1i(uSamplerUniform, texture);
}
}
/**
* Set if inversion state
* @private
* @flag is the state
*/
function setInverted(flag) {
if (gl && shaderProgram) {
gl.uniform1i(isInverted, flag);
}
}
/**
* Enable/disable circle drawing
* @private
*/
function setDrawAsCircle(flag) {
if (gl && shaderProgram) {
gl.uniform1i(isCircleUniform, flag ? 1 : 0);
}
}
/**
* Flush
* @private
*/
function reset() {
if (gl && shaderProgram) {
gl.uniform1i(isBubbleUniform, 0);
gl.uniform1i(isCircleUniform, 0);
}
}
/**
* Set bubble uniforms
* @private
* @param series {Highcharts.Series} - the series to use
*/
function setBubbleUniforms(series, zCalcMin, zCalcMax) {
var seriesOptions = series.options, zMin = Number.MAX_VALUE, zMax = -Number.MAX_VALUE;
if (gl && shaderProgram && series.type === 'bubble') {
zMin = pick(seriesOptions.zMin, clamp(zCalcMin, seriesOptions.displayNegative === false ?
seriesOptions.zThreshold : -Number.MAX_VALUE, zMin));
zMax = pick(seriesOptions.zMax, Math.max(zMax, zCalcMax));
gl.uniform1i(isBubbleUniform, 1);
gl.uniform1i(isCircleUniform, 1);
gl.uniform1i(bubbleSizeAreaUniform, (series.options.sizeBy !== 'width'));
gl.uniform1i(bubbleSizeAbsUniform, series.options
.sizeByAbsoluteValue);
setUniform('bubbleZMin', zMin);
setUniform('bubbleZMax', zMax);
setUniform('bubbleZThreshold', series.options.zThreshold);
setUniform('bubbleMinSize', series.minPxSize);
setUniform('bubbleMaxSize', series.maxPxSize);
}
}
/**
* Set the Color uniform.
* @private
* @param color {Array<float>} - an array with RGBA values
*/
function setColor(color) {
if (gl && shaderProgram) {
gl.uniform4f(fillColorUniform, color[0] / 255.0, color[1] / 255.0, color[2] / 255.0, color[3]);
}
}
/**
* Set skip translation
* @private
*/
function setSkipTranslation(flag) {
if (gl && shaderProgram) {
gl.uniform1i(skipTranslationUniform, flag === true ? 1 : 0);
}
}
/**
* Set the perspective matrix
* @private
* @param m {Matrix4x4} - the matrix
*/
function setPMatrix(m) {
if (gl && shaderProgram) {
gl.uniformMatrix4fv(pUniform, false, m);
}
}
/**
* Set the point size.
* @private
* @param p {float} - point size
*/
function setPointSize(p) {
if (gl && shaderProgram) {
gl.uniform1f(psUniform, p);
}
}
/**
* Get the shader program handle
* @private
* @return {GLInt} - the handle for the program
*/
function getProgram() {
return shaderProgram;
}
if (gl) {
if (!createShader()) {
return false;
}
}
return {
psUniform: function () {
return psUniform;
},
pUniform: function () {
return pUniform;
},
fillColorUniform: function () {
return fillColorUniform;
},
setBubbleUniforms: setBubbleUniforms,
bind: bind,
program: getProgram,
create: createShader,
setUniform: setUniform,
setPMatrix: setPMatrix,
setColor: setColor,
setPointSize: setPointSize,
setSkipTranslation: setSkipTranslation,
setTexture: setTexture,
setDrawAsCircle: setDrawAsCircle,
reset: reset,
setInverted: setInverted,
destroy: destroy
};
}
export default GLShader;
@@ -0,0 +1,153 @@
/* *
*
* Copyright (c) 2019-2020 Highsoft AS
*
* Boost module: stripped-down renderer for higher performance
*
* License: highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
/* eslint-disable valid-jsdoc */
/**
* Vertex Buffer abstraction.
* A vertex buffer is a set of vertices which are passed to the GPU
* in a single call.
*
* @private
* @function GLVertexBuffer
*
* @param {WebGLContext} gl
* the context in which to create the buffer
*
* @param {GLShader} shader
* the shader to use
*
* @return {*}
*/
function GLVertexBuffer(gl, shader, dataComponents
/* , type */
) {
var buffer = false, vertAttribute = false, components = dataComponents || 2, preAllocated = false, iterator = 0,
// farray = false,
data;
// type = type || 'float';
/**
* @private
*/
function destroy() {
if (buffer) {
gl.deleteBuffer(buffer);
buffer = false;
vertAttribute = false;
}
iterator = 0;
components = dataComponents || 2;
data = [];
}
/**
* Build the buffer
* @private
* @param dataIn {Array<float>} - a 0 padded array of indices
* @param attrib {String} - the name of the Attribute to bind the buffer to
* @param dataComponents {Integer} - the number of components per. indice
*/
function build(dataIn, attrib, dataComponents) {
var farray;
data = dataIn || [];
if ((!data || data.length === 0) && !preAllocated) {
// console.error('trying to render empty vbuffer');
destroy();
return false;
}
components = dataComponents || components;
if (buffer) {
gl.deleteBuffer(buffer);
}
if (!preAllocated) {
farray = new Float32Array(data);
}
buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, preAllocated || farray, gl.STATIC_DRAW);
// gl.bindAttribLocation(shader.program(), 0, 'aVertexPosition');
vertAttribute = gl.getAttribLocation(shader.program(), attrib);
gl.enableVertexAttribArray(vertAttribute);
// Trigger cleanup
farray = false;
return true;
}
/**
* Bind the buffer
* @private
*/
function bind() {
if (!buffer) {
return false;
}
// gl.bindAttribLocation(shader.program(), 0, 'aVertexPosition');
// gl.enableVertexAttribArray(vertAttribute);
// gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(vertAttribute, components, gl.FLOAT, false, 0, 0);
// gl.enableVertexAttribArray(vertAttribute);
}
/**
* Render the buffer
* @private
* @param from {Integer} - the start indice
* @param to {Integer} - the end indice
* @param drawMode {String} - the draw mode
*/
function render(from, to, drawMode) {
var length = preAllocated ? preAllocated.length : data.length;
if (!buffer) {
return false;
}
if (!length) {
return false;
}
if (!from || from > length || from < 0) {
from = 0;
}
if (!to || to > length) {
to = length;
}
drawMode = drawMode || 'points';
gl.drawArrays(gl[drawMode.toUpperCase()], from / components, (to - from) / components);
return true;
}
/**
* @private
*/
function push(x, y, a, b) {
if (preAllocated) { // && iterator <= preAllocated.length - 4) {
preAllocated[++iterator] = x;
preAllocated[++iterator] = y;
preAllocated[++iterator] = a;
preAllocated[++iterator] = b;
}
}
/**
* Note about pre-allocated buffers:
* - This is slower for charts with many series
* @private
*/
function allocate(size) {
size *= 4;
iterator = -1;
preAllocated = new Float32Array(size);
}
// /////////////////////////////////////////////////////////////////////////
return {
destroy: destroy,
bind: bind,
data: data,
build: build,
render: render,
allocate: allocate,
push: push
};
}
export default GLVertexBuffer;
@@ -0,0 +1,505 @@
/* *
*
* License: www.highcharts.com/license
* Author: Torstein Honsi, Christer Vasseng
*
* This module serves as a fallback for the Boost module in IE9 and IE10. Newer
* browsers support WebGL which is faster.
*
* It is recommended to include this module in conditional comments targeting
* IE9 and IE10.
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Chart from '../Core/Chart/Chart.js';
import H from '../Core/Globals.js';
import Color from '../Core/Color.js';
var color = Color.parse;
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, extend = U.extend, fireEvent = U.fireEvent, isNumber = U.isNumber, merge = U.merge, pick = U.pick, wrap = U.wrap;
import '../Core/Series/Series.js';
import '../Core/Options.js';
var win = H.win, doc = win.document, noop = function () { }, Series = H.Series, seriesTypes = H.seriesTypes, CHUNK_SIZE = 50000, destroyLoadingDiv;
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* Initialize the canvas boost.
*
* @function Highcharts.initCanvasBoost
*/
H.initCanvasBoost = function () {
if (H.seriesTypes.heatmap) {
wrap(H.seriesTypes.heatmap.prototype, 'drawPoints', function () {
var chart = this.chart, ctx = this.getContext(), inverted = this.chart.inverted, xAxis = this.xAxis, yAxis = this.yAxis;
if (ctx) {
// draw the columns
this.points.forEach(function (point) {
var plotY = point.plotY, shapeArgs, pointAttr;
if (typeof plotY !== 'undefined' &&
!isNaN(plotY) &&
point.y !== null) {
shapeArgs = point.shapeArgs;
if (!chart.styledMode) {
pointAttr = point.series.pointAttribs(point);
}
else {
pointAttr = point.series.colorAttribs(point);
}
ctx.fillStyle = pointAttr.fill;
if (inverted) {
ctx.fillRect(yAxis.len - shapeArgs.y + xAxis.left, xAxis.len - shapeArgs.x + yAxis.top, -shapeArgs.height, -shapeArgs.width);
}
else {
ctx.fillRect(shapeArgs.x + xAxis.left, shapeArgs.y + yAxis.top, shapeArgs.width, shapeArgs.height);
}
}
});
this.canvasToSVG();
}
else {
this.chart.showLoading('Your browser doesn\'t support HTML5 canvas, <br>' +
'please use a modern browser');
// Uncomment this to provide low-level (slow) support in oldIE.
// It will cause script errors on charts with more than a few
// thousand points.
// arguments[0].call(this);
}
});
}
extend(Series.prototype, {
/**
* Create a hidden canvas to draw the graph on. The contents is later
* copied over to an SVG image element.
*
* @private
* @function Highcharts.Series#getContext
*/
getContext: function () {
var chart = this.chart, width = chart.chartWidth, height = chart.chartHeight, targetGroup = chart.seriesGroup || this.group, target = this, ctx, swapXY = function (proceed, x, y, a, b, c, d) {
proceed.call(this, y, x, a, b, c, d);
};
if (chart.isChartSeriesBoosting()) {
target = chart;
targetGroup = chart.seriesGroup;
}
ctx = target.ctx;
if (!target.canvas) {
target.canvas = doc.createElement('canvas');
target.renderTarget = chart.renderer
.image('', 0, 0, width, height)
.addClass('highcharts-boost-canvas')
.add(targetGroup);
target.ctx = ctx = target.canvas.getContext('2d');
if (chart.inverted) {
['moveTo', 'lineTo', 'rect', 'arc'].forEach(function (fn) {
wrap(ctx, fn, swapXY);
});
}
target.boostCopy = function () {
target.renderTarget.attr({
href: target.canvas.toDataURL('image/png')
});
};
target.boostClear = function () {
ctx.clearRect(0, 0, target.canvas.width, target.canvas.height);
if (target === this) {
target.renderTarget.attr({ href: '' });
}
};
target.boostClipRect = chart.renderer.clipRect();
target.renderTarget.clip(target.boostClipRect);
}
else if (!(target instanceof H.Chart)) {
// ctx.clearRect(0, 0, width, height);
}
if (target.canvas.width !== width) {
target.canvas.width = width;
}
if (target.canvas.height !== height) {
target.canvas.height = height;
}
target.renderTarget.attr({
x: 0,
y: 0,
width: width,
height: height,
style: 'pointer-events: none',
href: ''
});
target.boostClipRect.attr(chart.getBoostClipRect(target));
return ctx;
},
/**
* Draw the canvas image inside an SVG image
*
* @private
* @function Highcharts.Series#canvasToSVG
*/
canvasToSVG: function () {
if (!this.chart.isChartSeriesBoosting()) {
if (this.boostCopy || this.chart.boostCopy) {
(this.boostCopy || this.chart.boostCopy)();
}
}
else {
if (this.boostClear) {
this.boostClear();
}
}
},
cvsLineTo: function (ctx, clientX, plotY) {
ctx.lineTo(clientX, plotY);
},
renderCanvas: function () {
var series = this, options = series.options, chart = series.chart, xAxis = this.xAxis, yAxis = this.yAxis, activeBoostSettings = chart.options.boost || {}, boostSettings = {
timeRendering: activeBoostSettings.timeRendering || false,
timeSeriesProcessing: activeBoostSettings.timeSeriesProcessing || false,
timeSetup: activeBoostSettings.timeSetup || false
}, ctx, c = 0, xData = series.processedXData, yData = series.processedYData, rawData = options.data, xExtremes = xAxis.getExtremes(), xMin = xExtremes.min, xMax = xExtremes.max, yExtremes = yAxis.getExtremes(), yMin = yExtremes.min, yMax = yExtremes.max, pointTaken = {}, lastClientX, sampling = !!series.sampling, points, r = options.marker && options.marker.radius, cvsDrawPoint = this.cvsDrawPoint, cvsLineTo = options.lineWidth ? this.cvsLineTo : void 0, cvsMarker = (r && r <= 1 ?
this.cvsMarkerSquare :
this.cvsMarkerCircle), strokeBatch = this.cvsStrokeBatch || 1000, enableMouseTracking = options.enableMouseTracking !== false, lastPoint, threshold = options.threshold, yBottom = yAxis.getThreshold(threshold), hasThreshold = isNumber(threshold), translatedThreshold = yBottom, doFill = this.fill, isRange = (series.pointArrayMap &&
series.pointArrayMap.join(',') === 'low,high'), isStacked = !!options.stacking, cropStart = series.cropStart || 0, loadingOptions = chart.options.loading, requireSorting = series.requireSorting, wasNull, connectNulls = options.connectNulls, useRaw = !xData, minVal, maxVal, minI, maxI, index, sdata = (isStacked ?
series.data :
(xData || rawData)), fillColor = (series.fillOpacity ?
new Color(series.color).setOpacity(pick(options.fillOpacity, 0.75)).get() :
series.color),
//
stroke = function () {
if (doFill) {
ctx.fillStyle = fillColor;
ctx.fill();
}
else {
ctx.strokeStyle = series.color;
ctx.lineWidth = options.lineWidth;
ctx.stroke();
}
},
//
drawPoint = function (clientX, plotY, yBottom, i) {
if (c === 0) {
ctx.beginPath();
if (cvsLineTo) {
ctx.lineJoin = 'round';
}
}
if (chart.scroller &&
series.options.className ===
'highcharts-navigator-series') {
plotY += chart.scroller.top;
if (yBottom) {
yBottom += chart.scroller.top;
}
}
else {
plotY += chart.plotTop;
}
clientX += chart.plotLeft;
if (wasNull) {
ctx.moveTo(clientX, plotY);
}
else {
if (cvsDrawPoint) {
cvsDrawPoint(ctx, clientX, plotY, yBottom, lastPoint);
}
else if (cvsLineTo) {
cvsLineTo(ctx, clientX, plotY);
}
else if (cvsMarker) {
cvsMarker.call(series, ctx, clientX, plotY, r, i);
}
}
// We need to stroke the line for every 1000 pixels. It will
// crash the browser memory use if we stroke too
// infrequently.
c = c + 1;
if (c === strokeBatch) {
stroke();
c = 0;
}
// Area charts need to keep track of the last point
lastPoint = {
clientX: clientX,
plotY: plotY,
yBottom: yBottom
};
},
//
compareX = options.findNearestPointBy === 'x',
//
xDataFull = (this.xData ||
this.options.xData ||
this.processedXData ||
false),
//
addKDPoint = function (clientX, plotY, i) {
// Shaves off about 60ms compared to repeated concatenation
index = compareX ? clientX : clientX + ',' + plotY;
// The k-d tree requires series points.
// Reduce the amount of points, since the time to build the
// tree increases exponentially.
if (enableMouseTracking && !pointTaken[index]) {
pointTaken[index] = true;
if (chart.inverted) {
clientX = xAxis.len - clientX;
plotY = yAxis.len - plotY;
}
points.push({
x: xDataFull ?
xDataFull[cropStart + i] :
false,
clientX: clientX,
plotX: clientX,
plotY: plotY,
i: cropStart + i
});
}
};
if (this.renderTarget) {
this.renderTarget.attr({ 'href': '' });
}
// If we are zooming out from SVG mode, destroy the graphics
if (this.points || this.graph) {
this.destroyGraphics();
}
// The group
series.plotGroup('group', 'series', series.visible ? 'visible' : 'hidden', options.zIndex, chart.seriesGroup);
series.markerGroup = series.group;
addEvent(series, 'destroy', function () {
// Prevent destroy twice
series.markerGroup = null;
});
points = this.points = [];
ctx = this.getContext();
series.buildKDTree = noop; // Do not start building while drawing
if (this.boostClear) {
this.boostClear();
}
// if (this.canvas) {
// ctx.clearRect(
// 0,
// 0,
// this.canvas.width,
// this.canvas.height
// );
// }
if (!this.visible) {
return;
}
// Display a loading indicator
if (rawData.length > 99999) {
chart.options.loading = merge(loadingOptions, {
labelStyle: {
backgroundColor: color('#ffffff').setOpacity(0.75).get(),
padding: '1em',
borderRadius: '0.5em'
},
style: {
backgroundColor: 'none',
opacity: 1
}
});
U.clearTimeout(destroyLoadingDiv);
chart.showLoading('Drawing...');
chart.options.loading = loadingOptions; // reset
}
if (boostSettings.timeRendering) {
console.time('canvas rendering'); // eslint-disable-line no-console
}
// Loop over the points
H.eachAsync(sdata, function (d, i) {
var x, y, clientX, plotY, isNull, low, isNextInside = false, isPrevInside = false, nx = false, px = false, chartDestroyed = typeof chart.index === 'undefined', isYInside = true;
if (!chartDestroyed) {
if (useRaw) {
x = d[0];
y = d[1];
if (sdata[i + 1]) {
nx = sdata[i + 1][0];
}
if (sdata[i - 1]) {
px = sdata[i - 1][0];
}
}
else {
x = d;
y = yData[i];
if (sdata[i + 1]) {
nx = sdata[i + 1];
}
if (sdata[i - 1]) {
px = sdata[i - 1];
}
}
if (nx && nx >= xMin && nx <= xMax) {
isNextInside = true;
}
if (px && px >= xMin && px <= xMax) {
isPrevInside = true;
}
// Resolve low and high for range series
if (isRange) {
if (useRaw) {
y = d.slice(1, 3);
}
low = y[0];
y = y[1];
}
else if (isStacked) {
x = d.x;
y = d.stackY;
low = y - d.y;
}
isNull = y === null;
// Optimize for scatter zooming
if (!requireSorting) {
isYInside = y >= yMin && y <= yMax;
}
if (!isNull &&
((x >= xMin && x <= xMax && isYInside) ||
(isNextInside || isPrevInside))) {
clientX = Math.round(xAxis.toPixels(x, true));
if (sampling) {
if (typeof minI === 'undefined' ||
clientX === lastClientX) {
if (!isRange) {
low = y;
}
if (typeof maxI === 'undefined' || y > maxVal) {
maxVal = y;
maxI = i;
}
if (typeof minI === 'undefined' ||
low < minVal) {
minVal = low;
minI = i;
}
}
// Add points and reset
if (clientX !== lastClientX) {
// maxI also a number:
if (typeof minI !== 'undefined') {
plotY = yAxis.toPixels(maxVal, true);
yBottom = yAxis.toPixels(minVal, true);
drawPoint(clientX, hasThreshold ?
Math.min(plotY, translatedThreshold) : plotY, hasThreshold ?
Math.max(yBottom, translatedThreshold) : yBottom, i);
addKDPoint(clientX, plotY, maxI);
if (yBottom !== plotY) {
addKDPoint(clientX, yBottom, minI);
}
}
minI = maxI = void 0;
lastClientX = clientX;
}
}
else {
plotY = Math.round(yAxis.toPixels(y, true));
drawPoint(clientX, plotY, yBottom, i);
addKDPoint(clientX, plotY, i);
}
}
wasNull = isNull && !connectNulls;
if (i % CHUNK_SIZE === 0) {
if (series.boostCopy || series.chart.boostCopy) {
(series.boostCopy || series.chart.boostCopy)();
}
}
}
return !chartDestroyed;
}, function () {
var loadingDiv = chart.loadingDiv, loadingShown = chart.loadingShown;
stroke();
// if (series.boostCopy || series.chart.boostCopy) {
// (series.boostCopy || series.chart.boostCopy)();
// }
series.canvasToSVG();
if (boostSettings.timeRendering) {
console.timeEnd('canvas rendering'); // eslint-disable-line no-console
}
fireEvent(series, 'renderedCanvas');
// Do not use chart.hideLoading, as it runs JS animation and
// will be blocked by buildKDTree. CSS animation looks good, but
// then it must be deleted in timeout. If we add the module to
// core, change hideLoading so we can skip this block.
if (loadingShown) {
extend(loadingDiv.style, {
transition: 'opacity 250ms',
opacity: 0
});
chart.loadingShown = false;
destroyLoadingDiv = setTimeout(function () {
if (loadingDiv.parentNode) { // In exporting it is falsy
loadingDiv.parentNode.removeChild(loadingDiv);
}
chart.loadingDiv = chart.loadingSpan = null;
}, 250);
}
// Go back to prototype, ready to build
delete series.buildKDTree;
series.buildKDTree();
// Don't do async on export, the exportChart, getSVGForExport and
// getSVG methods are not chained for it.
}, chart.renderer.forExport ? Number.MAX_VALUE : void 0);
}
});
seriesTypes.scatter.prototype.cvsMarkerCircle = function (ctx, clientX, plotY, r) {
ctx.moveTo(clientX, plotY);
ctx.arc(clientX, plotY, r, 0, 2 * Math.PI, false);
};
// Rect is twice as fast as arc, should be used for small markers
seriesTypes.scatter.prototype.cvsMarkerSquare = function (ctx, clientX, plotY, r) {
ctx.rect(clientX - r, plotY - r, r * 2, r * 2);
};
seriesTypes.scatter.prototype.fill = true;
if (seriesTypes.bubble) {
seriesTypes.bubble.prototype.cvsMarkerCircle = function (ctx, clientX, plotY, r, i) {
ctx.moveTo(clientX, plotY);
ctx.arc(clientX, plotY, this.radii && this.radii[i], 0, 2 * Math.PI, false);
};
seriesTypes.bubble.prototype.cvsStrokeBatch = 1;
}
extend(seriesTypes.area.prototype, {
cvsDrawPoint: function (ctx, clientX, plotY, yBottom, lastPoint) {
if (lastPoint && clientX !== lastPoint.clientX) {
ctx.moveTo(lastPoint.clientX, lastPoint.yBottom);
ctx.lineTo(lastPoint.clientX, lastPoint.plotY);
ctx.lineTo(clientX, plotY);
ctx.lineTo(clientX, yBottom);
}
},
fill: true,
fillOpacity: true,
sampling: true
});
extend(seriesTypes.column.prototype, {
cvsDrawPoint: function (ctx, clientX, plotY, yBottom) {
ctx.rect(clientX - 1, plotY, 1, yBottom - plotY);
},
fill: true,
sampling: true
});
Chart.prototype.callbacks.push(function (chart) {
/**
* @private
*/
function canvasToSVG() {
if (chart.boostCopy) {
chart.boostCopy();
}
}
/**
* @private
*/
function clear() {
if (chart.renderTarget) {
chart.renderTarget.attr({ href: '' });
}
if (chart.canvas) {
chart.canvas.getContext('2d').clearRect(0, 0, chart.canvas.width, chart.canvas.height);
}
}
addEvent(chart, 'predraw', clear);
addEvent(chart, 'render', canvasToSVG);
});
};
@@ -0,0 +1,99 @@
/* *
*
* (c) 2016-2020 Highsoft AS
*
* Author: Lars A. V. Cabrera
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../Core/Globals.js';
import O from '../Core/Options.js';
var dateFormat = O.dateFormat;
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, merge = U.merge, wrap = U.wrap;
import PlotLineOrBand from '../Core/Axis/PlotLineOrBand.js';
var Axis = H.Axis;
var defaultConfig = {
/**
* Show an indicator on the axis for the current date and time. Can be a
* boolean or a configuration object similar to
* [xAxis.plotLines](#xAxis.plotLines).
*
* @sample gantt/current-date-indicator/demo
* Current date indicator enabled
* @sample gantt/current-date-indicator/object-config
* Current date indicator with custom options
*
* @declare Highcharts.AxisCurrentDateIndicatorOptions
* @type {boolean|*}
* @default true
* @extends xAxis.plotLines
* @excluding value
* @product gantt
* @apioption xAxis.currentDateIndicator
*/
currentDateIndicator: true,
color: '#ccd6eb',
width: 2,
/**
* @declare Highcharts.AxisCurrentDateIndicatorLabelOptions
*/
label: {
/**
* Format of the label. This options is passed as the fist argument to
* [dateFormat](/class-reference/Highcharts#dateFormat) function.
*
* @type {string}
* @default '%a, %b %d %Y, %H:%M'
* @product gantt
* @apioption xAxis.currentDateIndicator.label.format
*/
format: '%a, %b %d %Y, %H:%M',
formatter: function (value, format) {
return dateFormat(format, value);
},
rotation: 0,
/**
* @type {Highcharts.CSSObject}
*/
style: {
/** @internal */
fontSize: '10px'
}
}
};
/* eslint-disable no-invalid-this */
addEvent(Axis, 'afterSetOptions', function () {
var options = this.options, cdiOptions = options.currentDateIndicator;
if (cdiOptions) {
cdiOptions = typeof cdiOptions === 'object' ?
merge(defaultConfig, cdiOptions) : merge(defaultConfig);
cdiOptions.value = new Date();
if (!options.plotLines) {
options.plotLines = [];
}
options.plotLines.push(cdiOptions);
}
});
addEvent(PlotLineOrBand, 'render', function () {
// If the label already exists, update its text
if (this.label) {
this.label.attr({
text: this.getLabelText(this.options.label)
});
}
});
wrap(PlotLineOrBand.prototype, 'getLabelText', function (defaultMethod, defaultLabelOptions) {
var options = this.options;
if (options.currentDateIndicator && options.label &&
typeof options.label.formatter === 'function') {
options.value = new Date();
return options.label.formatter
.call(this, options.value, options.label.format);
}
return defaultMethod.call(this, defaultLabelOptions);
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,896 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import DateTimeAxis from '../Core/Axis/DateTimeAxis.js';
import H from '../Core/Globals.js';
/**
* @typedef {"average"|"averages"|"open"|"high"|"low"|"close"|"sum"} Highcharts.DataGroupingApproximationValue
*/
/**
* @interface Highcharts.DataGroupingInfoObject
*/ /**
* @name Highcharts.DataGroupingInfoObject#length
* @type {number}
*/ /**
* @name Highcharts.DataGroupingInfoObject#options
* @type {Highcharts.SeriesOptionsType|undefined}
*/ /**
* @name Highcharts.DataGroupingInfoObject#start
* @type {number}
*/
''; // detach doclets above
import O from '../Core/Options.js';
var defaultOptions = O.defaultOptions;
import Point from '../Core/Series/Point.js';
import Tooltip from '../Core/Tooltip.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, arrayMax = U.arrayMax, arrayMin = U.arrayMin, correctFloat = U.correctFloat, defined = U.defined, error = U.error, extend = U.extend, format = U.format, isNumber = U.isNumber, merge = U.merge, pick = U.pick;
import '../Core/Axis/Axis.js';
import '../Core/Series/Series.js';
var Axis = H.Axis, Series = H.Series;
/* ************************************************************************** *
* Start data grouping module *
* ************************************************************************** */
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* Define the available approximation types. The data grouping
* approximations takes an array or numbers as the first parameter. In case
* of ohlc, four arrays are sent in as four parameters. Each array consists
* only of numbers. In case null values belong to the group, the property
* .hasNulls will be set to true on the array.
*
* @product highstock
*
* @private
* @name Highcharts.approximations
* @type {Highcharts.Dictionary<Function>}
*/
var approximations = H.approximations = {
sum: function (arr) {
var len = arr.length, ret;
// 1. it consists of nulls exclusive
if (!len && arr.hasNulls) {
ret = null;
// 2. it has a length and real values
}
else if (len) {
ret = 0;
while (len--) {
ret += arr[len];
}
}
// 3. it has zero length, so just return undefined
// => doNothing()
return ret;
},
average: function (arr) {
var len = arr.length, ret = approximations.sum(arr);
// If we have a number, return it divided by the length. If not,
// return null or undefined based on what the sum method finds.
if (isNumber(ret) && len) {
ret = correctFloat(ret / len);
}
return ret;
},
// The same as average, but for series with multiple values, like area
// ranges.
averages: function () {
var ret = [];
[].forEach.call(arguments, function (arr) {
ret.push(approximations.average(arr));
});
// Return undefined when first elem. is undefined and let
// sum method handle null (#7377)
return typeof ret[0] === 'undefined' ? void 0 : ret;
},
open: function (arr) {
return arr.length ? arr[0] : (arr.hasNulls ? null : void 0);
},
high: function (arr) {
return arr.length ?
arrayMax(arr) :
(arr.hasNulls ? null : void 0);
},
low: function (arr) {
return arr.length ?
arrayMin(arr) :
(arr.hasNulls ? null : void 0);
},
close: function (arr) {
return arr.length ?
arr[arr.length - 1] :
(arr.hasNulls ? null : void 0);
},
// ohlc and range are special cases where a multidimensional array is
// input and an array is output
ohlc: function (open, high, low, close) {
open = approximations.open(open);
high = approximations.high(high);
low = approximations.low(low);
close = approximations.close(close);
if (isNumber(open) ||
isNumber(high) ||
isNumber(low) ||
isNumber(close)) {
return [open, high, low, close];
}
// else, return is undefined
},
range: function (low, high) {
low = approximations.low(low);
high = approximations.high(high);
if (isNumber(low) || isNumber(high)) {
return [low, high];
}
if (low === null && high === null) {
return null;
}
// else, return is undefined
}
};
var groupData = function (xData, yData, groupPositions, approximation) {
var series = this, data = series.data, dataOptions = series.options && series.options.data, groupedXData = [], groupedYData = [], groupMap = [], dataLength = xData.length, pointX, pointY, groupedY,
// when grouping the fake extended axis for panning,
// we don't need to consider y
handleYData = !!yData, values = [], approximationFn, pointArrayMap = series.pointArrayMap, pointArrayMapLength = pointArrayMap && pointArrayMap.length, extendedPointArrayMap = ['x'].concat(pointArrayMap || ['y']), pos = 0, start = 0, valuesLen, i, j;
/**
* @private
*/
function getApproximation(approx) {
if (typeof approx === 'function') {
return approx;
}
if (approximations[approx]) {
return approximations[approx];
}
return approximations[(series.getDGApproximation && series.getDGApproximation()) ||
'average'];
}
approximationFn = getApproximation(approximation);
// Calculate values array size from pointArrayMap length
if (pointArrayMapLength) {
pointArrayMap.forEach(function () {
values.push([]);
});
}
else {
values.push([]);
}
valuesLen = pointArrayMapLength || 1;
// Start with the first point within the X axis range (#2696)
for (i = 0; i <= dataLength; i++) {
if (xData[i] >= groupPositions[0]) {
break;
}
}
for (i; i <= dataLength; i++) {
// when a new group is entered, summarize and initialize
// the previous group
while ((typeof groupPositions[pos + 1] !== 'undefined' &&
xData[i] >= groupPositions[pos + 1]) ||
i === dataLength) { // get the last group
// get group x and y
pointX = groupPositions[pos];
series.dataGroupInfo = {
start: series.cropStart + start,
length: values[0].length
};
groupedY = approximationFn.apply(series, values);
// By default, let options of the first grouped point be passed over
// to the grouped point. This allows preserving properties like
// `name` and `color` or custom properties. Implementers can
// override this from the approximation function, where they can
// write custom options to `this.dataGroupInfo.options`.
if (series.pointClass && !defined(series.dataGroupInfo.options)) {
// Convert numbers and arrays into objects
series.dataGroupInfo.options = merge(series.pointClass.prototype
.optionsToObject.call({ series: series }, series.options.data[series.cropStart + start]));
// Make sure the raw data (x, y, open, high etc) is not copied
// over and overwriting approximated data.
extendedPointArrayMap.forEach(function (key) {
delete series.dataGroupInfo.options[key];
});
}
// push the grouped data
if (typeof groupedY !== 'undefined') {
groupedXData.push(pointX);
groupedYData.push(groupedY);
groupMap.push(series.dataGroupInfo);
}
// reset the aggregate arrays
start = i;
for (j = 0; j < valuesLen; j++) {
values[j].length = 0; // faster than values[j] = []
values[j].hasNulls = false;
}
// Advance on the group positions
pos += 1;
// don't loop beyond the last group
if (i === dataLength) {
break;
}
}
// break out
if (i === dataLength) {
break;
}
// for each raw data point, push it to an array that contains all values
// for this specific group
if (pointArrayMap) {
var index = series.cropStart + i, point = (data && data[index]) ||
series.pointClass.prototype.applyOptions.apply({
series: series
}, [dataOptions[index]]), val;
for (j = 0; j < pointArrayMapLength; j++) {
val = point[pointArrayMap[j]];
if (isNumber(val)) {
values[j].push(val);
}
else if (val === null) {
values[j].hasNulls = true;
}
}
}
else {
pointY = handleYData ? yData[i] : null;
if (isNumber(pointY)) {
values[0].push(pointY);
}
else if (pointY === null) {
values[0].hasNulls = true;
}
}
}
return {
groupedXData: groupedXData,
groupedYData: groupedYData,
groupMap: groupMap
};
};
var dataGrouping = {
approximations: approximations,
groupData: groupData
};
// -----------------------------------------------------------------------------
// The following code applies to implementation of data grouping on a Series
var seriesProto = Series.prototype, baseProcessData = seriesProto.processData, baseGeneratePoints = seriesProto.generatePoints,
/** @ignore */
commonOptions = {
// enabled: null, // (true for stock charts, false for basic),
// forced: undefined,
groupPixelWidth: 2,
// the first one is the point or start value, the second is the start
// value if we're dealing with range, the third one is the end value if
// dealing with a range
dateTimeLabelFormats: {
millisecond: [
'%A, %b %e, %H:%M:%S.%L',
'%A, %b %e, %H:%M:%S.%L',
'-%H:%M:%S.%L'
],
second: [
'%A, %b %e, %H:%M:%S',
'%A, %b %e, %H:%M:%S',
'-%H:%M:%S'
],
minute: [
'%A, %b %e, %H:%M',
'%A, %b %e, %H:%M',
'-%H:%M'
],
hour: [
'%A, %b %e, %H:%M',
'%A, %b %e, %H:%M',
'-%H:%M'
],
day: [
'%A, %b %e, %Y',
'%A, %b %e',
'-%A, %b %e, %Y'
],
week: [
'Week from %A, %b %e, %Y',
'%A, %b %e',
'-%A, %b %e, %Y'
],
month: [
'%B %Y',
'%B',
'-%B %Y'
],
year: [
'%Y',
'%Y',
'-%Y'
]
}
// smoothed = false, // enable this for navigator series only
}, specificOptions = {
line: {},
spline: {},
area: {},
areaspline: {},
arearange: {},
column: {
groupPixelWidth: 10
},
columnrange: {
groupPixelWidth: 10
},
candlestick: {
groupPixelWidth: 10
},
ohlc: {
groupPixelWidth: 5
}
},
// units are defined in a separate array to allow complete overriding in
// case of a user option
defaultDataGroupingUnits = H.defaultDataGroupingUnits = [
[
'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]
], [
'week',
[1]
], [
'month',
[1, 3, 6]
], [
'year',
null
]
];
// Set default approximations to the prototypes if present. Properties are
// inherited down. Can be overridden for individual series types.
seriesProto.getDGApproximation = function () {
if (this.is('arearange')) {
return 'range';
}
if (this.is('ohlc')) {
return 'ohlc';
}
if (this.is('column')) {
return 'sum';
}
return 'average';
};
/**
* Takes parallel arrays of x and y data and groups the data into intervals
* defined by groupPositions, a collection of starting x values for each group.
*
* @private
* @function Highcharts.Series#groupData
*
* @param {Array<number>} xData
*
* @param {Array<number>|Array<Array<number>>} yData
*
* @param {boolean} groupPositions
*
* @param {string|Function} approximation
*
* @return {void}
*/
seriesProto.groupData = groupData;
// Extend the basic processData method, that crops the data to the current zoom
// range, with data grouping logic.
seriesProto.processData = function () {
var series = this, chart = series.chart, options = series.options, dataGroupingOptions = options.dataGrouping, groupingEnabled = series.allowDG !== false && dataGroupingOptions &&
pick(dataGroupingOptions.enabled, chart.options.isStock), visible = (series.visible || !chart.options.chart.ignoreHiddenSeries), hasGroupedData, skip, lastDataGrouping = this.currentDataGrouping, currentDataGrouping, croppedData, revertRequireSorting = false;
// Run base method
series.forceCrop = groupingEnabled; // #334
series.groupPixelWidth = null; // #2110
series.hasProcessed = true; // #2692
// Data needs to be sorted for dataGrouping
if (groupingEnabled && !series.requireSorting) {
series.requireSorting = revertRequireSorting = true;
}
// Skip if processData returns false or if grouping is disabled (in that
// order)
skip = (baseProcessData.apply(series, arguments) === false ||
!groupingEnabled);
// Revert original requireSorting value if changed
if (revertRequireSorting) {
series.requireSorting = false;
}
if (!skip) {
series.destroyGroupedData();
var i, processedXData = dataGroupingOptions.groupAll ?
series.xData :
series.processedXData, processedYData = dataGroupingOptions.groupAll ?
series.yData :
series.processedYData, plotSizeX = chart.plotSizeX, xAxis = series.xAxis, ordinal = xAxis.options.ordinal, groupPixelWidth = series.groupPixelWidth =
xAxis.getGroupPixelWidth && xAxis.getGroupPixelWidth();
// Execute grouping if the amount of points is greater than the limit
// defined in groupPixelWidth
if (groupPixelWidth) {
hasGroupedData = true;
// Force recreation of point instances in series.translate, #5699
series.isDirty = true;
series.points = null; // #6709
var extremes = xAxis.getExtremes(), xMin = extremes.min, xMax = extremes.max, groupIntervalFactor = (ordinal &&
xAxis.ordinal &&
xAxis.ordinal.getGroupIntervalFactor(xMin, xMax, series)) || 1, interval = (groupPixelWidth * (xMax - xMin) / plotSizeX) *
groupIntervalFactor, groupPositions = xAxis.getTimeTicks(DateTimeAxis.AdditionsClass.prototype.normalizeTimeTickInterval(interval, dataGroupingOptions.units ||
defaultDataGroupingUnits),
// Processed data may extend beyond axis (#4907)
Math.min(xMin, processedXData[0]), Math.max(xMax, processedXData[processedXData.length - 1]), xAxis.options.startOfWeek, processedXData, series.closestPointRange), groupedData = seriesProto.groupData.apply(series, [
processedXData,
processedYData,
groupPositions,
dataGroupingOptions.approximation
]), groupedXData = groupedData.groupedXData, groupedYData = groupedData.groupedYData, gapSize = 0;
// Prevent the smoothed data to spill out left and right, and make
// sure data is not shifted to the left
if (dataGroupingOptions.smoothed && groupedXData.length) {
i = groupedXData.length - 1;
groupedXData[i] = Math.min(groupedXData[i], xMax);
while (i-- && i > 0) {
groupedXData[i] += interval / 2;
}
groupedXData[0] = Math.max(groupedXData[0], xMin);
}
// Record what data grouping values were used
for (i = 1; i < groupPositions.length; i++) {
// The grouped gapSize needs to be the largest distance between
// the group to capture varying group sizes like months or DST
// crossing (#10000). Also check that the gap is not at the
// start of a segment.
if (!groupPositions.info.segmentStarts ||
groupPositions.info.segmentStarts.indexOf(i) === -1) {
gapSize = Math.max(groupPositions[i] - groupPositions[i - 1], gapSize);
}
}
currentDataGrouping = groupPositions.info;
currentDataGrouping.gapSize = gapSize;
series.closestPointRange = groupPositions.info.totalRange;
series.groupMap = groupedData.groupMap;
// Make sure the X axis extends to show the first group (#2533)
// But only for visible series (#5493, #6393)
if (defined(groupedXData[0]) &&
groupedXData[0] < xAxis.min &&
visible) {
if ((!defined(xAxis.options.min) &&
xAxis.min <= xAxis.dataMin) ||
xAxis.min === xAxis.dataMin) {
xAxis.min = Math.min(groupedXData[0], xAxis.min);
}
xAxis.dataMin = Math.min(groupedXData[0], xAxis.dataMin);
}
// We calculated all group positions but we should render
// only the ones within the visible range
if (dataGroupingOptions.groupAll) {
croppedData = series.cropData(groupedXData, groupedYData, xAxis.min, xAxis.max, 1 // Ordinal xAxis will remove left-most points otherwise
);
groupedXData = croppedData.xData;
groupedYData = croppedData.yData;
}
// Set series props
series.processedXData = groupedXData;
series.processedYData = groupedYData;
}
else {
series.groupMap = null;
}
series.hasGroupedData = hasGroupedData;
series.currentDataGrouping = currentDataGrouping;
series.preventGraphAnimation =
(lastDataGrouping && lastDataGrouping.totalRange) !==
(currentDataGrouping && currentDataGrouping.totalRange);
}
};
// Destroy the grouped data points. #622, #740
seriesProto.destroyGroupedData = function () {
// Clear previous groups
if (this.groupedData) {
this.groupedData.forEach(function (point, i) {
if (point) {
this.groupedData[i] = point.destroy ?
point.destroy() : null;
}
}, this);
// Clears all:
// - `this.groupedData`
// - `this.points`
// - `preserve` object in series.update()
this.groupedData.length = 0;
}
};
// Override the generatePoints method by adding a reference to grouped data
seriesProto.generatePoints = function () {
baseGeneratePoints.apply(this);
// Record grouped data in order to let it be destroyed the next time
// processData runs
this.destroyGroupedData(); // #622
this.groupedData = this.hasGroupedData ? this.points : null;
};
// Override point prototype to throw a warning when trying to update grouped
// points.
addEvent(Point, 'update', function () {
if (this.dataGroup) {
error(24, false, this.series.chart);
return false;
}
});
// Extend the original method, make the tooltip's header reflect the grouped
// range.
addEvent(Tooltip, 'headerFormatter', function (e) {
var tooltip = this, chart = this.chart, time = chart.time, labelConfig = e.labelConfig, series = labelConfig.series, options = series.options, tooltipOptions = series.tooltipOptions, dataGroupingOptions = options.dataGrouping, xDateFormat = tooltipOptions.xDateFormat, xDateFormatEnd, xAxis = series.xAxis, currentDataGrouping, dateTimeLabelFormats, labelFormats, formattedKey, formatString = tooltipOptions[(e.isFooter ? 'footer' : 'header') + 'Format'];
// apply only to grouped series
if (xAxis &&
xAxis.options.type === 'datetime' &&
dataGroupingOptions &&
isNumber(labelConfig.key)) {
// set variables
currentDataGrouping = series.currentDataGrouping;
dateTimeLabelFormats = dataGroupingOptions.dateTimeLabelFormats ||
// Fallback to commonOptions (#9693)
commonOptions.dateTimeLabelFormats;
// if we have grouped data, use the grouping information to get the
// right format
if (currentDataGrouping) {
labelFormats =
dateTimeLabelFormats[currentDataGrouping.unitName];
if (currentDataGrouping.count === 1) {
xDateFormat = labelFormats[0];
}
else {
xDateFormat = labelFormats[1];
xDateFormatEnd = labelFormats[2];
}
// if not grouped, and we don't have set the xDateFormat option, get the
// best fit, so if the least distance between points is one minute, show
// it, but if the least distance is one day, skip hours and minutes etc.
}
else if (!xDateFormat && dateTimeLabelFormats) {
xDateFormat = tooltip.getXDateFormat(labelConfig, tooltipOptions, xAxis);
}
// now format the key
formattedKey = time.dateFormat(xDateFormat, labelConfig.key);
if (xDateFormatEnd) {
formattedKey += time.dateFormat(xDateFormatEnd, labelConfig.key + currentDataGrouping.totalRange - 1);
}
// Replace default header style with class name
if (series.chart.styledMode) {
formatString = this.styledModeFormat(formatString);
}
// return the replaced format
e.text = format(formatString, {
point: extend(labelConfig.point, { key: formattedKey }),
series: series
}, chart);
e.preventDefault();
}
});
// Destroy grouped data on series destroy
addEvent(Series, 'destroy', seriesProto.destroyGroupedData);
// Handle default options for data grouping. This must be set at runtime because
// some series types are defined after this.
addEvent(Series, 'afterSetOptions', function (e) {
var options = e.options, type = this.type, plotOptions = this.chart.options.plotOptions, defaultOptions = O.defaultOptions.plotOptions[type].dataGrouping,
// External series, for example technical indicators should also
// inherit commonOptions which are not available outside this module
baseOptions = this.useCommonDataGrouping && commonOptions;
if (specificOptions[type] || baseOptions) { // #1284
if (!defaultOptions) {
defaultOptions = merge(commonOptions, specificOptions[type]);
}
options.dataGrouping = merge(baseOptions, defaultOptions, plotOptions.series && plotOptions.series.dataGrouping, // #1228
// Set by the StockChart constructor:
plotOptions[type].dataGrouping, this.userOptions.dataGrouping);
}
});
// When resetting the scale reset the hasProccessed flag to avoid taking
// previous data grouping of neighbour series into accound when determining
// group pixel width (#2692).
addEvent(Axis, 'afterSetScale', function () {
this.series.forEach(function (series) {
series.hasProcessed = false;
});
});
// Get the data grouping pixel width based on the greatest defined individual
// width of the axis' series, and if whether one of the axes need grouping.
Axis.prototype.getGroupPixelWidth = function () {
var series = this.series, len = series.length, i, groupPixelWidth = 0, doGrouping = false, dataLength, dgOptions;
// If multiple series are compared on the same x axis, give them the same
// group pixel width (#334)
i = len;
while (i--) {
dgOptions = series[i].options.dataGrouping;
if (dgOptions) {
groupPixelWidth = Math.max(groupPixelWidth,
// Fallback to commonOptions (#9693)
pick(dgOptions.groupPixelWidth, commonOptions.groupPixelWidth));
}
}
// If one of the series needs grouping, apply it to all (#1634)
i = len;
while (i--) {
dgOptions = series[i].options.dataGrouping;
if (dgOptions && series[i].hasProcessed) { // #2692
dataLength = (series[i].processedXData || series[i].data).length;
// Execute grouping if the amount of points is greater than the
// limit defined in groupPixelWidth
if (series[i].groupPixelWidth ||
dataLength >
(this.chart.plotSizeX / groupPixelWidth) ||
(dataLength && dgOptions.forced)) {
doGrouping = true;
}
}
}
return doGrouping ? groupPixelWidth : 0;
};
/**
* Highstock only. Force data grouping on all the axis' series.
*
* @product highstock
*
* @function Highcharts.Axis#setDataGrouping
*
* @param {boolean|Highcharts.DataGroupingOptionsObject} [dataGrouping]
* A `dataGrouping` configuration. Use `false` to disable data grouping
* dynamically.
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart or wait for a later call to
* {@link Chart#redraw}.
*
* @return {void}
*/
Axis.prototype.setDataGrouping = function (dataGrouping, redraw) {
var axis = this;
var i;
redraw = pick(redraw, true);
if (!dataGrouping) {
dataGrouping = {
forced: false,
units: null
};
}
// Axis is instantiated, update all series
if (this instanceof Axis) {
i = this.series.length;
while (i--) {
this.series[i].update({
dataGrouping: dataGrouping
}, false);
}
// Axis not yet instanciated, alter series options
}
else {
this.chart.options.series.forEach(function (seriesOptions) {
seriesOptions.dataGrouping = dataGrouping;
}, false);
}
// Clear ordinal slope, so we won't accidentaly use the old one (#7827)
if (axis.ordinal) {
axis.ordinal.slope = void 0;
}
if (redraw) {
this.chart.redraw();
}
};
H.dataGrouping = dataGrouping;
export default dataGrouping;
/* eslint-enable no-invalid-this, valid-jsdoc */
/**
* Data grouping is the concept of sampling the data values into larger
* blocks in order to ease readability and increase performance of the
* JavaScript charts. Highstock by default applies data grouping when
* the points become closer than a certain pixel value, determined by
* the `groupPixelWidth` option.
*
* If data grouping is applied, the grouping information of grouped
* points can be read from the [Point.dataGroup](
* /class-reference/Highcharts.Point#dataGroup). If point options other than
* the data itself are set, for example `name` or `color` or custom properties,
* the grouping logic doesn't know how to group it. In this case the options of
* the first point instance are copied over to the group point. This can be
* altered through a custom `approximation` callback function.
*
* @declare Highcharts.DataGroupingOptionsObject
* @product highstock
* @requires product:highstock
* @requires module:modules/datagrouping
* @apioption plotOptions.series.dataGrouping
*/
/**
* The method of approximation inside a group. When for example 30 days
* are grouped into one month, this determines what value should represent
* the group. Possible values are "average", "averages", "open", "high",
* "low", "close" and "sum". For OHLC and candlestick series the approximation
* is "ohlc" by default, which finds the open, high, low and close values
* within all the grouped data. For ranges, the approximation is "range",
* which finds the low and high values. For multi-dimensional data,
* like ranges and OHLC, "averages" will compute the average for each
* dimension.
*
* Custom aggregate methods can be added by assigning a callback function
* as the approximation. This function takes a numeric array as the
* argument and should return a single numeric value or `null`. Note
* that the numeric array will never contain null values, only true
* numbers. Instead, if null values are present in the raw data, the
* numeric array will have an `.hasNulls` property set to `true`. For
* single-value data sets the data is available in the first argument
* of the callback function. For OHLC data sets, all the open values
* are in the first argument, all high values in the second etc.
*
* Since v4.2.7, grouping meta data is available in the approximation
* callback from `this.dataGroupInfo`. It can be used to extract information
* from the raw data.
*
* Defaults to `average` for line-type series, `sum` for columns, `range`
* for range series and `ohlc` for OHLC and candlestick.
*
* @sample {highstock} stock/plotoptions/series-datagrouping-approximation
* Approximation callback with custom data
*
* @type {Highcharts.DataGroupingApproximationValue|Function}
* @apioption plotOptions.series.dataGrouping.approximation
*/
/**
* Datetime formats for the header of the tooltip in a stock chart.
* The format can vary within a chart depending on the currently selected
* time range and the current data grouping.
*
* The default formats are:
* ```js
* {
* millisecond: [
* '%A, %b %e, %H:%M:%S.%L', '%A, %b %e, %H:%M:%S.%L', '-%H:%M:%S.%L'
* ],
* second: ['%A, %b %e, %H:%M:%S', '%A, %b %e, %H:%M:%S', '-%H:%M:%S'],
* minute: ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'],
* hour: ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'],
* day: ['%A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'],
* week: ['Week from %A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'],
* month: ['%B %Y', '%B', '-%B %Y'],
* year: ['%Y', '%Y', '-%Y']
* }
* ```
*
* For each of these array definitions, the first item is the format
* used when the active time span is one unit. For instance, if the
* current data applies to one week, the first item of the week array
* is used. The second and third items are used when the active time
* span is more than two units. For instance, if the current data applies
* to two weeks, the second and third item of the week array are used,
* and applied to the start and end date of the time span.
*
* @type {object}
* @apioption plotOptions.series.dataGrouping.dateTimeLabelFormats
*/
/**
* Enable or disable data grouping.
*
* @type {boolean}
* @default true
* @apioption plotOptions.series.dataGrouping.enabled
*/
/**
* When data grouping is forced, it runs no matter how small the intervals
* are. This can be handy for example when the sum should be calculated
* for values appearing at random times within each hour.
*
* @type {boolean}
* @default false
* @apioption plotOptions.series.dataGrouping.forced
*/
/**
* The approximate pixel width of each group. If for example a series
* with 30 points is displayed over a 600 pixel wide plot area, no grouping
* is performed. If however the series contains so many points that
* the spacing is less than the groupPixelWidth, Highcharts will try
* to group it into appropriate groups so that each is more or less
* two pixels wide. If multiple series with different group pixel widths
* are drawn on the same x axis, all series will take the greatest width.
* For example, line series have 2px default group width, while column
* series have 10px. If combined, both the line and the column will
* have 10px by default.
*
* @type {number}
* @default 2
* @apioption plotOptions.series.dataGrouping.groupPixelWidth
*/
/**
* By default only points within the visible range are grouped. Enabling this
* option will force data grouping to calculate all grouped points for a given
* dataset. That option prevents for example a column series from calculating
* a grouped point partially. The effect is similar to
* [Series.getExtremesFromAll](#plotOptions.series.getExtremesFromAll) but does
* not affect yAxis extremes.
*
* @sample {highstock} stock/plotoptions/series-datagrouping-groupall/
* Two series with the same data but different groupAll setting
*
* @type {boolean}
* @default false
* @since 6.1.0
* @apioption plotOptions.series.dataGrouping.groupAll
*/
/**
* Normally, a group is indexed by the start of that group, so for example
* when 30 daily values are grouped into one month, that month's x value
* will be the 1st of the month. This apparently shifts the data to
* the left. When the smoothed option is true, this is compensated for.
* The data is shifted to the middle of the group, and min and max
* values are preserved. Internally, this is used in the Navigator series.
*
* @type {boolean}
* @default false
* @apioption plotOptions.series.dataGrouping.smoothed
*/
/**
* An array determining what time intervals the data is allowed to be
* grouped to. Each array item is an array where the first value is
* the time unit and the second value another array of allowed multiples.
*
* Defaults to:
* ```js
* units: [[
* 'millisecond', // unit name
* [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]
* ], [
* 'week',
* [1]
* ], [
* 'month',
* [1, 3, 6]
* ], [
* 'year',
* null
* ]]
* ```
*
* @type {Array<Array<string,(Array<number>|null)>>}
* @apioption plotOptions.series.dataGrouping.units
*/
/**
* The approximate pixel width of each group. If for example a series
* with 30 points is displayed over a 600 pixel wide plot area, no grouping
* is performed. If however the series contains so many points that
* the spacing is less than the groupPixelWidth, Highcharts will try
* to group it into appropriate groups so that each is more or less
* two pixels wide. Defaults to `10`.
*
* @sample {highstock} stock/plotoptions/series-datagrouping-grouppixelwidth/
* Two series with the same data density but different groupPixelWidth
*
* @type {number}
* @default 10
* @apioption plotOptions.column.dataGrouping.groupPixelWidth
*/
''; // required by JSDoc parsing
@@ -0,0 +1,84 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../Core/Globals.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, isNumber = U.isNumber, setOptions = U.setOptions;
setOptions({
/**
* @optionparent chart
*/
chart: {
/**
* Whether to display errors on the chart. When `false`, the errors will
* be shown only in the console.
*
* @sample highcharts/chart/display-errors/
* Show errors on chart
*
* @since 7.0.0
* @requires modules/debugger
*/
displayErrors: true
}
});
/* eslint-disable no-invalid-this */
addEvent(H.Chart, 'displayError', function (e) {
var chart = this, code = e.code, msg, options = chart.options.chart, renderer = chart.renderer, chartWidth, chartHeight;
if (chart.errorElements) {
(chart.errorElements).forEach(function (el) {
if (el) {
el.destroy();
}
});
}
if (options && options.displayErrors && renderer) {
chart.errorElements = [];
msg = isNumber(code) ?
('Highcharts error #' + code + ': ' +
H.errorMessages[code].text) :
code;
chartWidth = chart.chartWidth;
chartHeight = chart.chartHeight;
// Format msg so SVGRenderer can handle it
msg = msg
.replace(/<h1>(.*)<\/h1>/g, '<br><span style="font-size: 24px">$1</span><br>')
.replace(/<\/p>/g, '</p><br>');
// Render red chart frame.
chart.errorElements[0] = renderer.rect(2, 2, chartWidth - 4, chartHeight - 4).attr({
'stroke-width': 4,
stroke: '#ff0000',
zIndex: 3
}).add();
// Render error message
chart.errorElements[1] = renderer.label(msg, 0, 0, 'rect', void 0, void 0, void 0, void 0, 'debugger').css({
color: '#ffffff',
width: (chartWidth - 16) + 'px',
padding: 0
}).attr({
fill: 'rgba(255, 0, 0, 0.9)',
width: chartWidth,
padding: 8,
zIndex: 10
}).add();
chart.errorElements[1].attr({
y: chartHeight - this.errorElements[1].getBBox().height
});
}
});
addEvent(H.Chart, 'beforeRedraw', function () {
var errorElements = this.errorElements;
if (errorElements && errorElements.length) {
errorElements.forEach(function (el) {
el.destroy();
});
}
this.errorElements = null;
});
@@ -0,0 +1,98 @@
/* *
*
* (c) 2015-2020 Oystein Moseng
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Mixin for downloading content in the browser
*
* */
'use strict';
import Highcharts from '../Core/Globals.js';
var win = Highcharts.win, nav = win.navigator, doc = win.document, domurl = win.URL || win.webkitURL || win, isEdgeBrowser = /Edge\/\d+/.test(nav.userAgent);
/**
* Convert base64 dataURL to Blob if supported, otherwise returns undefined.
* @private
* @function Highcharts.dataURLtoBlob
* @param {string} dataURL
* URL to convert
* @return {string|undefined}
* Blob
*/
var dataURLtoBlob = Highcharts.dataURLtoBlob = function (dataURL) {
var parts = dataURL.match(/data:([^;]*)(;base64)?,([0-9A-Za-z+/]+)/);
if (parts &&
parts.length > 3 &&
win.atob &&
win.ArrayBuffer &&
win.Uint8Array &&
win.Blob &&
domurl.createObjectURL) {
// Try to convert data URL to Blob
var binStr = win.atob(parts[3]), buf = new win.ArrayBuffer(binStr.length), binary = new win.Uint8Array(buf), blob;
for (var i = 0; i < binary.length; ++i) {
binary[i] = binStr.charCodeAt(i);
}
blob = new win.Blob([binary], { 'type': parts[1] });
return domurl.createObjectURL(blob);
}
};
/**
* Download a data URL in the browser. Can also take a blob as first param.
*
* @private
* @function Highcharts.downloadURL
* @param {string|global.URL} dataURL
* The dataURL/Blob to download
* @param {string} filename
* The name of the resulting file (w/extension)
* @return {void}
*/
var downloadURL = Highcharts.downloadURL = function (dataURL, filename) {
var a = doc.createElement('a'), windowRef;
// IE specific blob implementation
// Don't use for normal dataURLs
if (typeof dataURL !== 'string' &&
!(dataURL instanceof String) &&
nav.msSaveOrOpenBlob) {
nav.msSaveOrOpenBlob(dataURL, filename);
return;
}
dataURL = "" + dataURL;
// Some browsers have limitations for data URL lengths. Try to convert to
// Blob or fall back. Edge always needs that blob.
if (isEdgeBrowser || dataURL.length > 2000000) {
dataURL = dataURLtoBlob(dataURL) || '';
if (!dataURL) {
throw new Error('Failed to convert to blob');
}
}
// Try HTML5 download attr if supported
if (typeof a.download !== 'undefined') {
a.href = dataURL;
a.download = filename; // HTML5 download attribute
doc.body.appendChild(a);
a.click();
doc.body.removeChild(a);
}
else {
// No download attr, just opening data URI
try {
windowRef = win.open(dataURL, 'chart');
if (typeof windowRef === 'undefined' || windowRef === null) {
throw new Error('Failed to open window');
}
}
catch (e) {
// window.open failed, trying location.href
win.location.href = dataURL;
}
}
};
var exports = {
dataURLtoBlob: dataURLtoBlob,
downloadURL: downloadURL
};
export default exports;
@@ -0,0 +1,506 @@
/* *
*
* Plugin for resizing axes / panes in a chart.
*
* (c) 2010-2017 Highsoft AS
*
* Author: Kacper Madej
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../Core/Globals.js';
var hasTouch = H.hasTouch;
import Axis from '../Core/Axis/Axis.js';
import Pointer from '../Core/Pointer.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, clamp = U.clamp, isNumber = U.isNumber, merge = U.merge, objectEach = U.objectEach, relativeLength = U.relativeLength, wrap = U.wrap;
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* The AxisResizer class.
*
* @private
* @class
* @name Highcharts.AxisResizer
*
* @param {Highcharts.Axis} axis
* Main axis for the AxisResizer.
*/
var AxisResizer = /** @class */ (function () {
function AxisResizer(axis) {
/* eslint-enable no-invalid-this */
this.axis = void 0;
this.controlLine = void 0;
this.lastPos = void 0;
this.options = void 0;
this.init(axis);
}
/**
* Initialize the AxisResizer object.
*
* @function Highcharts.AxisResizer#init
*
* @param {Highcharts.Axis} axis
* Main axis for the AxisResizer.
*/
AxisResizer.prototype.init = function (axis, update) {
this.axis = axis;
this.options = axis.options.resize;
this.render();
if (!update) {
// Add mouse events.
this.addMouseEvents();
}
};
/**
* Render the AxisResizer
*
* @function Highcharts.AxisResizer#render
*/
AxisResizer.prototype.render = function () {
var resizer = this, axis = resizer.axis, chart = axis.chart, options = resizer.options, x = options.x || 0, y = options.y,
// Normalize control line position according to the plot area
pos = clamp(axis.top + axis.height + y, chart.plotTop, chart.plotTop + chart.plotHeight), attr = {}, lineWidth;
if (!chart.styledMode) {
attr = {
cursor: options.cursor,
stroke: options.lineColor,
'stroke-width': options.lineWidth,
dashstyle: options.lineDashStyle
};
}
// Register current position for future reference.
resizer.lastPos = pos - y;
if (!resizer.controlLine) {
resizer.controlLine = chart.renderer.path()
.addClass('highcharts-axis-resizer');
}
// Add to axisGroup after axis update, because the group is recreated
// Do .add() before path is calculated because strokeWidth() needs it.
resizer.controlLine.add(axis.axisGroup);
lineWidth = chart.styledMode ?
resizer.controlLine.strokeWidth() :
options.lineWidth;
attr.d = chart.renderer.crispLine([
['M', axis.left + x, pos],
['L', axis.left + axis.width + x, pos]
], lineWidth);
resizer.controlLine.attr(attr);
};
/**
* Set up the mouse and touch events for the control line.
*
* @function Highcharts.AxisResizer#addMouseEvents
*/
AxisResizer.prototype.addMouseEvents = function () {
var resizer = this, ctrlLineElem = resizer.controlLine.element, container = resizer.axis.chart.container, eventsToUnbind = [], mouseMoveHandler, mouseUpHandler, mouseDownHandler;
// Create mouse events' handlers.
// Make them as separate functions to enable wrapping them:
resizer.mouseMoveHandler = mouseMoveHandler = function (e) {
resizer.onMouseMove(e);
};
resizer.mouseUpHandler = mouseUpHandler = function (e) {
resizer.onMouseUp(e);
};
resizer.mouseDownHandler = mouseDownHandler = function (e) {
resizer.onMouseDown(e);
};
// Add mouse move and mouseup events. These are bind to doc/container,
// because resizer.grabbed flag is stored in mousedown events.
eventsToUnbind.push(addEvent(container, 'mousemove', mouseMoveHandler), addEvent(container.ownerDocument, 'mouseup', mouseUpHandler), addEvent(ctrlLineElem, 'mousedown', mouseDownHandler));
// Touch events.
if (hasTouch) {
eventsToUnbind.push(addEvent(container, 'touchmove', mouseMoveHandler), addEvent(container.ownerDocument, 'touchend', mouseUpHandler), addEvent(ctrlLineElem, 'touchstart', mouseDownHandler));
}
resizer.eventsToUnbind = eventsToUnbind;
};
/**
* Mouse move event based on x/y mouse position.
*
* @function Highcharts.AxisResizer#onMouseMove
*
* @param {Highcharts.PointerEventObject} e
* Mouse event.
*/
AxisResizer.prototype.onMouseMove = function (e) {
/*
* In iOS, a mousemove event with e.pageX === 0 is fired when holding
* the finger down in the center of the scrollbar. This should
* be ignored. Borrowed from Navigator.
*/
if (!e.touches || e.touches[0].pageX !== 0) {
// Drag the control line
if (this.grabbed) {
this.hasDragged = true;
this.updateAxes(this.axis.chart.pointer.normalize(e).chartY -
this.options.y);
}
}
};
/**
* Mouse up event based on x/y mouse position.
*
* @function Highcharts.AxisResizer#onMouseUp
*
* @param {Highcharts.PointerEventObject} e
* Mouse event.
*/
AxisResizer.prototype.onMouseUp = function (e) {
if (this.hasDragged) {
this.updateAxes(this.axis.chart.pointer.normalize(e).chartY -
this.options.y);
}
// Restore runPointActions.
this.grabbed = this.hasDragged = this.axis.chart.activeResizer =
null;
};
/**
* Mousedown on a control line.
* Will store necessary information for drag&drop.
*
* @function Highcharts.AxisResizer#onMouseDown
*/
AxisResizer.prototype.onMouseDown = function (e) {
// Clear all hover effects.
this.axis.chart.pointer.reset(false, 0);
// Disable runPointActions.
this.grabbed = this.axis.chart.activeResizer = true;
};
/**
* Update all connected axes after a change of control line position
*
* @function Highcharts.AxisResizer#updateAxes
*
* @param {number} chartY
*/
AxisResizer.prototype.updateAxes = function (chartY) {
var resizer = this, chart = resizer.axis.chart, axes = resizer.options.controlledAxis, nextAxes = axes.next.length === 0 ?
[chart.yAxis.indexOf(resizer.axis) + 1] : axes.next,
// Main axis is included in the prev array by default
prevAxes = [resizer.axis].concat(axes.prev),
// prev and next configs
axesConfigs = [], stopDrag = false, plotTop = chart.plotTop, plotHeight = chart.plotHeight, plotBottom = plotTop + plotHeight, yDelta, calculatePercent = function (value) {
return value * 100 / plotHeight + '%';
}, normalize = function (val, min, max) {
return Math.round(clamp(val, min, max));
};
// Normalize chartY to plot area limits
chartY = clamp(chartY, plotTop, plotBottom);
yDelta = chartY - resizer.lastPos;
// Update on changes of at least 1 pixel in the desired direction
if (yDelta * yDelta < 1) {
return;
}
// First gather info how axes should behave
[prevAxes, nextAxes].forEach(function (axesGroup, isNext) {
axesGroup.forEach(function (axisInfo, i) {
// Axes given as array index, axis object or axis id
var axis = isNumber(axisInfo) ?
// If it's a number - it's an index
chart.yAxis[axisInfo] :
(
// If it's first elem. in first group
(!isNext && !i) ?
// then it's an Axis object
axisInfo :
// else it should be an id
chart.get(axisInfo)), axisOptions = axis && axis.options, optionsToUpdate = {}, hDelta = 0, height, top, minLength, maxLength;
// Skip if axis is not found
// or it is navigator's yAxis (#7732)
if (!axisOptions ||
axisOptions.id === 'navigator-y-axis') {
return;
}
top = axis.top;
minLength = Math.round(relativeLength(axisOptions.minLength, plotHeight));
maxLength = Math.round(relativeLength(axisOptions.maxLength, plotHeight));
if (isNext) {
// Try to change height first. yDelta could had changed
yDelta = chartY - resizer.lastPos;
// Normalize height to option limits
height = normalize(axis.len - yDelta, minLength, maxLength);
// Adjust top, so the axis looks like shrinked from top
top = axis.top + yDelta;
// Check for plot area limits
if (top + height > plotBottom) {
hDelta = plotBottom - height - top;
chartY += hDelta;
top += hDelta;
}
// Fit to plot - when overflowing on top
if (top < plotTop) {
top = plotTop;
if (top + height > plotBottom) {
height = plotHeight;
}
}
// If next axis meets min length, stop dragging:
if (height === minLength) {
stopDrag = true;
}
axesConfigs.push({
axis: axis,
options: {
top: calculatePercent(top - plotTop),
height: calculatePercent(height)
}
});
}
else {
// Normalize height to option limits
height = normalize(chartY - top, minLength, maxLength);
// If prev axis meets max length, stop dragging:
if (height === maxLength) {
stopDrag = true;
}
// Check axis size limits
chartY = top + height;
axesConfigs.push({
axis: axis,
options: {
height: calculatePercent(height)
}
});
}
optionsToUpdate.height = height;
});
});
// If we hit the min/maxLength with dragging, don't do anything:
if (!stopDrag) {
// Now update axes:
axesConfigs.forEach(function (config) {
config.axis.update(config.options, false);
});
chart.redraw(false);
}
};
/**
* Destroy AxisResizer. Clear outside references, clear events,
* destroy elements, nullify properties.
*
* @function Highcharts.AxisResizer#destroy
*/
AxisResizer.prototype.destroy = function () {
var resizer = this, axis = resizer.axis;
// Clear resizer in axis
delete axis.resizer;
// Clear control line events
if (this.eventsToUnbind) {
this.eventsToUnbind.forEach(function (unbind) {
unbind();
});
}
// Destroy AxisResizer elements
resizer.controlLine.destroy();
// Nullify properties
objectEach(resizer, function (val, key) {
resizer[key] = null;
});
};
// Default options for AxisResizer.
AxisResizer.resizerOptions = {
/**
* Minimal size of a resizable axis. Could be set as a percent
* of plot area or pixel size.
*
* @sample {highstock} stock/yaxis/resize-min-max-length
* minLength and maxLength
*
* @type {number|string}
* @product highstock
* @requires modules/drag-panes
* @apioption yAxis.minLength
*/
minLength: '10%',
/**
* Maximal size of a resizable axis. Could be set as a percent
* of plot area or pixel size.
*
* @sample {highstock} stock/yaxis/resize-min-max-length
* minLength and maxLength
*
* @type {number|string}
* @product highstock
* @requires modules/drag-panes
* @apioption yAxis.maxLength
*/
maxLength: '100%',
/**
* Options for axis resizing. It adds a thick line between panes which
* the user can drag in order to resize the panes.
*
* @sample {highstock} stock/demo/candlestick-and-volume
* Axis resizing enabled
*
* @product highstock
* @requires modules/drag-panes
* @optionparent yAxis.resize
*/
resize: {
/**
* Contains two arrays of axes that are controlled by control line
* of the axis.
*
* @requires modules/drag-panes
*/
controlledAxis: {
/**
* Array of axes that should move out of the way of resizing
* being done for the current axis. If not set, the next axis
* will be used.
*
* @sample {highstock} stock/yaxis/multiple-resizers
* Three panes with resizers
* @sample {highstock} stock/yaxis/resize-multiple-axes
* One resizer controlling multiple axes
*
* @type {Array<number|string>}
* @default []
* @requires modules/drag-panes
*/
next: [],
/**
* Array of axes that should move with the current axis
* while resizing.
*
* @sample {highstock} stock/yaxis/multiple-resizers
* Three panes with resizers
* @sample {highstock} stock/yaxis/resize-multiple-axes
* One resizer controlling multiple axes
*
* @type {Array<number|string>}
* @default []
* @requires modules/drag-panes
*/
prev: []
},
/**
* Enable or disable resize by drag for the axis.
*
* @sample {highstock} stock/demo/candlestick-and-volume
* Enabled resizer
*
* @requires modules/drag-panes
*/
enabled: false,
/**
* Cursor style for the control line.
*
* In styled mode use class `highcharts-axis-resizer` instead.
*
* @requires modules/drag-panes
*/
cursor: 'ns-resize',
/**
* Color of the control line.
*
* In styled mode use class `highcharts-axis-resizer` instead.
*
* @sample {highstock} stock/yaxis/styled-resizer
* Styled resizer
*
* @type {Highcharts.ColorString}
* @requires modules/drag-panes
*/
lineColor: '#cccccc',
/**
* Dash style of the control line.
*
* In styled mode use class `highcharts-axis-resizer` instead.
*
* @see For supported options check [dashStyle](#plotOptions.series.dashStyle)
*
* @sample {highstock} stock/yaxis/styled-resizer
* Styled resizer
*
* @requires modules/drag-panes
*/
lineDashStyle: 'Solid',
/**
* Width of the control line.
*
* In styled mode use class `highcharts-axis-resizer` instead.
*
* @sample {highstock} stock/yaxis/styled-resizer
* Styled resizer
*
* @requires modules/drag-panes
*/
lineWidth: 4,
/**
* Horizontal offset of the control line.
*
* @sample {highstock} stock/yaxis/styled-resizer
* Styled resizer
*
* @requires modules/drag-panes
*/
x: 0,
/**
* Vertical offset of the control line.
*
* @sample {highstock} stock/yaxis/styled-resizer
* Styled resizer
*
* @requires modules/drag-panes
*/
y: 0
}
};
return AxisResizer;
}());
// Keep resizer reference on axis update
Axis.keepProps.push('resizer');
/* eslint-disable no-invalid-this */
// Add new AxisResizer, update or remove it
addEvent(Axis, 'afterRender', function () {
var axis = this, resizer = axis.resizer, resizerOptions = axis.options.resize, enabled;
if (resizerOptions) {
enabled = resizerOptions.enabled !== false;
if (resizer) {
// Resizer present and enabled
if (enabled) {
// Update options
resizer.init(axis, true);
// Resizer present, but disabled
}
else {
// Destroy the resizer
resizer.destroy();
}
}
else {
// Resizer not present and enabled
if (enabled) {
// Add new resizer
axis.resizer = new H.AxisResizer(axis);
}
// Resizer not present and disabled, so do nothing
}
}
});
// Clear resizer on axis remove.
addEvent(Axis, 'destroy', function (e) {
if (!e.keepEvents && this.resizer) {
this.resizer.destroy();
}
});
// Prevent any hover effects while dragging a control line of AxisResizer.
wrap(Pointer.prototype, 'runPointActions', function (proceed) {
if (!this.chart.activeResizer) {
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
}
});
// Prevent default drag action detection while dragging a control line of
// AxisResizer. (#7563)
wrap(Pointer.prototype, 'drag', function (proceed) {
if (!this.chart.activeResizer) {
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
}
});
merge(true, Axis.defaultYAxisOptions, AxisResizer.resizerOptions);
H.AxisResizer = AxisResizer;
export default H.AxisResizer;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,926 @@
/* *
*
* Experimental data export module for Highcharts
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
// @todo
// - Set up systematic tests for all series types, paired with tests of the data
// module importing the same data.
'use strict';
import Axis from '../Core/Axis/Axis.js';
import Chart from '../Core/Chart/Chart.js';
import H from '../Core/Globals.js';
var doc = H.doc, seriesTypes = H.seriesTypes, win = H.win;
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, defined = U.defined, extend = U.extend, find = U.find, fireEvent = U.fireEvent, getOptions = U.getOptions, isNumber = U.isNumber, pick = U.pick, setOptions = U.setOptions;
/**
* Function callback to execute while data rows are processed for exporting.
* This allows the modification of data rows before processed into the final
* format.
*
* @callback Highcharts.ExportDataCallbackFunction
* @extends Highcharts.EventCallbackFunction<Highcharts.Chart>
*
* @param {Highcharts.Chart} this
* Chart context where the event occured.
*
* @param {Highcharts.ExportDataEventObject} event
* Event object with data rows that can be modified.
*/
/**
* Contains information about the export data event.
*
* @interface Highcharts.ExportDataEventObject
*/ /**
* Contains the data rows for the current export task and can be modified.
* @name Highcharts.ExportDataEventObject#dataRows
* @type {Array<Array<string>>}
*/
import DownloadURL from '../Extensions/DownloadURL.js';
var downloadURL = DownloadURL.downloadURL;
// Can we add this to utils? Also used in screen-reader.js
/**
* HTML encode some characters vulnerable for XSS.
* @private
* @param {string} html The input string
* @return {string} The excaped string
*/
function htmlencode(html) {
return html
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;')
.replace(/\//g, '&#x2F;');
}
setOptions({
/**
* Callback that fires while exporting data. This allows the modification of
* data rows before processed into the final format.
*
* @type {Highcharts.ExportDataCallbackFunction}
* @context Highcharts.Chart
* @requires modules/export-data
* @apioption chart.events.exportData
*/
/**
* When set to `false` will prevent the series data from being included in
* any form of data export.
*
* Since version 6.0.0 until 7.1.0 the option was existing undocumented
* as `includeInCSVExport`.
*
* @type {boolean}
* @since 7.1.0
* @requires modules/export-data
* @apioption plotOptions.series.includeInDataExport
*/
/**
* @optionparent exporting
* @private
*/
exporting: {
/**
* Caption for the data table. Same as chart title by default. Set to
* `false` to disable.
*
* @sample highcharts/export-data/multilevel-table
* Multiple table headers
*
* @type {boolean|string}
* @since 6.0.4
* @requires modules/export-data
* @apioption exporting.tableCaption
*/
/**
* Options for exporting data to CSV or ExCel, or displaying the data
* in a HTML table or a JavaScript structure.
*
* This module adds data export options to the export menu and provides
* functions like `Chart.getCSV`, `Chart.getTable`, `Chart.getDataRows`
* and `Chart.viewData`.
*
* The XLS converter is limited and only creates a HTML string that is
* passed for download, which works but creates a warning before
* opening. The workaround for this is to use a third party XLSX
* converter, as demonstrated in the sample below.
*
* @sample highcharts/export-data/categorized/ Categorized data
* @sample highcharts/export-data/stock-timeaxis/ Highstock time axis
* @sample highcharts/export-data/xlsx/
* Using a third party XLSX converter
*
* @since 6.0.0
* @requires modules/export-data
*/
csv: {
/**
*
* Options for annotations in the export-data table.
*
* @since 8.2.0
* @requires modules/export-data
* @requires modules/annotations
*
*
*/
annotations: {
/**
* The way to mark the separator for annotations
* combined in one export-data table cell.
*
* @since 8.2.0
* @requires modules/annotations
*/
itemDelimiter: '; ',
/**
* When several labels are assigned to a specific point,
* they will be displayed in one field in the table.
*
* @sample highcharts/export-data/join-annotations/
* Concatenate point annotations with itemDelimiter set.
*
* @since 8.2.0
* @requires modules/annotations
*/
join: false
},
/**
* Formatter callback for the column headers. Parameters are:
* - `item` - The series or axis object)
* - `key` - The point key, for example y or z
* - `keyLength` - The amount of value keys for this item, for
* example a range series has the keys `low` and `high` so the
* key length is 2.
*
* If [useMultiLevelHeaders](#exporting.useMultiLevelHeaders) is
* true, columnHeaderFormatter by default returns an object with
* columnTitle and topLevelColumnTitle for each key. Columns with
* the same topLevelColumnTitle have their titles merged into a
* single cell with colspan for table/Excel export.
*
* If `useMultiLevelHeaders` is false, or for CSV export, it returns
* the series name, followed by the key if there is more than one
* key.
*
* For the axis it returns the axis title or "Category" or
* "DateTime" by default.
*
* Return `false` to use Highcharts' proposed header.
*
* @sample highcharts/export-data/multilevel-table
* Multiple table headers
*
* @type {Function|null}
*/
columnHeaderFormatter: null,
/**
* Which date format to use for exported dates on a datetime X axis.
* See `Highcharts.dateFormat`.
*/
dateFormat: '%Y-%m-%d %H:%M:%S',
/**
* Which decimal point to use for exported CSV. Defaults to the same
* as the browser locale, typically `.` (English) or `,` (German,
* French etc).
*
* @type {string|null}
* @since 6.0.4
*/
decimalPoint: null,
/**
* The item delimiter in the exported data. Use `;` for direct
* exporting to Excel. Defaults to a best guess based on the browser
* locale. If the locale _decimal point_ is `,`, the `itemDelimiter`
* defaults to `;`, otherwise the `itemDelimiter` defaults to `,`.
*
* @type {string|null}
*/
itemDelimiter: null,
/**
* The line delimiter in the exported data, defaults to a newline.
*/
lineDelimiter: '\n'
},
/**
* Show a HTML table below the chart with the chart's current data.
*
* @sample highcharts/export-data/showtable/
* Show the table
* @sample highcharts/studies/exporting-table-html
* Experiment with putting the table inside the subtitle to
* allow exporting it.
*
* @since 6.0.0
* @requires modules/export-data
*/
showTable: false,
/**
* Use multi level headers in data table. If [csv.columnHeaderFormatter
* ](#exporting.csv.columnHeaderFormatter) is defined, it has to return
* objects in order for multi level headers to work.
*
* @sample highcharts/export-data/multilevel-table
* Multiple table headers
*
* @since 6.0.4
* @requires modules/export-data
*/
useMultiLevelHeaders: true,
/**
* If using multi level table headers, use rowspans for headers that
* have only one level.
*
* @sample highcharts/export-data/multilevel-table
* Multiple table headers
*
* @since 6.0.4
* @requires modules/export-data
*/
useRowspanHeaders: true
},
/**
* @optionparent lang
*
* @private
*/
lang: {
/**
* The text for the menu item.
*
* @since 6.0.0
* @requires modules/export-data
*/
downloadCSV: 'Download CSV',
/**
* The text for the menu item.
*
* @since 6.0.0
* @requires modules/export-data
*/
downloadXLS: 'Download XLS',
/**
* The text for exported table.
*
* @since 8.1.0
* @requires modules/export-data
*/
exportData: {
/**
* The annotation column title.
*/
annotationHeader: 'Annotations',
/**
* The category column title.
*/
categoryHeader: 'Category',
/**
* The category column title when axis type set to "datetime".
*/
categoryDatetimeHeader: 'DateTime'
},
/**
* The text for the menu item.
*
* @since 6.0.0
* @requires modules/export-data
*/
viewData: 'View data table',
/**
* The text for the menu item.
*
* @since 8.2.0
* @requires modules/export-data
*/
hideData: 'Hide data table'
}
});
/* eslint-disable no-invalid-this */
// Add an event listener to handle the showTable option
addEvent(Chart, 'render', function () {
if (this.options &&
this.options.exporting &&
this.options.exporting.showTable &&
!this.options.chart.forExport &&
!this.dataTableDiv) {
this.viewData();
}
});
/* eslint-enable no-invalid-this */
/**
* Set up key-to-axis bindings. This is used when the Y axis is datetime or
* categorized. For example in an arearange series, the low and high values
* should be formatted according to the Y axis type, and in order to link them
* we need this map.
*
* @private
* @function Highcharts.Chart#setUpKeyToAxis
*/
Chart.prototype.setUpKeyToAxis = function () {
if (seriesTypes.arearange) {
seriesTypes.arearange.prototype.keyToAxis = {
low: 'y',
high: 'y'
};
}
if (seriesTypes.gantt) {
seriesTypes.gantt.prototype.keyToAxis = {
start: 'x',
end: 'x'
};
}
};
/**
* Export-data module required. Returns a two-dimensional array containing the
* current chart data.
*
* @function Highcharts.Chart#getDataRows
*
* @param {boolean} [multiLevelHeaders]
* Use multilevel headers for the rows by default. Adds an extra row with
* top level headers. If a custom columnHeaderFormatter is defined, this
* can override the behavior.
*
* @return {Array<Array<(number|string)>>}
* The current chart data
*
* @fires Highcharts.Chart#event:exportData
*/
Chart.prototype.getDataRows = function (multiLevelHeaders) {
var hasParallelCoords = this.hasParallelCoordinates, time = this.time, csvOptions = ((this.options.exporting && this.options.exporting.csv) || {}), xAxis, xAxes = this.xAxis, rows = {}, rowArr = [], dataRows, topLevelColumnTitles = [], columnTitles = [], columnTitleObj, i, x, xTitle, langOptions = this.options.lang, exportDataOptions = langOptions.exportData, categoryHeader = exportDataOptions.categoryHeader, categoryDatetimeHeader = exportDataOptions.categoryDatetimeHeader,
// Options
columnHeaderFormatter = function (item, key, keyLength) {
if (csvOptions.columnHeaderFormatter) {
var s = csvOptions.columnHeaderFormatter(item, key, keyLength);
if (s !== false) {
return s;
}
}
if (!item) {
return categoryHeader;
}
if (item instanceof Axis) {
return (item.options.title && item.options.title.text) ||
(item.dateTime ? categoryDatetimeHeader : categoryHeader);
}
if (multiLevelHeaders) {
return {
columnTitle: keyLength > 1 ?
key :
item.name,
topLevelColumnTitle: item.name
};
}
return item.name + (keyLength > 1 ? ' (' + key + ')' : '');
},
// Map the categories for value axes
getCategoryAndDateTimeMap = function (series, pointArrayMap, pIdx) {
var categoryMap = {}, dateTimeValueAxisMap = {};
pointArrayMap.forEach(function (prop) {
var axisName = ((series.keyToAxis && series.keyToAxis[prop]) ||
prop) + 'Axis',
// Points in parallel coordinates refers to all yAxis
// not only `series.yAxis`
axis = isNumber(pIdx) ?
series.chart[axisName][pIdx] :
series[axisName];
categoryMap[prop] = (axis && axis.categories) || [];
dateTimeValueAxisMap[prop] = (axis && axis.dateTime);
});
return {
categoryMap: categoryMap,
dateTimeValueAxisMap: dateTimeValueAxisMap
};
},
// Create point array depends if xAxis is category
// or point.name is defined #13293
getPointArray = function (series, xAxis) {
var namedPoints = series.data.filter(function (d) {
return (typeof d.y !== 'undefined') && d.name;
});
if (namedPoints.length &&
xAxis &&
!xAxis.categories &&
!series.keyToAxis) {
if (series.pointArrayMap) {
var pointArrayMapCheck = series.pointArrayMap.filter(function (p) { return p === 'x'; });
if (pointArrayMapCheck.length) {
series.pointArrayMap.unshift('x');
return series.pointArrayMap;
}
}
return ['x', 'y'];
}
return series.pointArrayMap || ['y'];
}, xAxisIndices = [];
// Loop the series and index values
i = 0;
this.setUpKeyToAxis();
this.series.forEach(function (series) {
var keys = series.options.keys, xAxis = series.xAxis, pointArrayMap = keys || getPointArray(series, xAxis), valueCount = pointArrayMap.length, xTaken = !series.requireSorting && {}, xAxisIndex = xAxes.indexOf(xAxis), categoryAndDatetimeMap = getCategoryAndDateTimeMap(series, pointArrayMap), mockSeries, j;
if (series.options.includeInDataExport !== false &&
!series.options.isInternal &&
series.visible !== false // #55
) {
// Build a lookup for X axis index and the position of the first
// series that belongs to that X axis. Includes -1 for non-axis
// series types like pies.
if (!find(xAxisIndices, function (index) {
return index[0] === xAxisIndex;
})) {
xAxisIndices.push([xAxisIndex, i]);
}
// Compute the column headers and top level headers, usually the
// same as series names
j = 0;
while (j < valueCount) {
columnTitleObj = columnHeaderFormatter(series, pointArrayMap[j], pointArrayMap.length);
columnTitles.push(columnTitleObj.columnTitle || columnTitleObj);
if (multiLevelHeaders) {
topLevelColumnTitles.push(columnTitleObj.topLevelColumnTitle ||
columnTitleObj);
}
j++;
}
mockSeries = {
chart: series.chart,
autoIncrement: series.autoIncrement,
options: series.options,
pointArrayMap: series.pointArrayMap
};
// Export directly from options.data because we need the uncropped
// data (#7913), and we need to support Boost (#7026).
series.options.data.forEach(function eachData(options, pIdx) {
var key, prop, val, name, point;
// In parallel coordinates chart, each data point is connected
// to a separate yAxis, conform this
if (hasParallelCoords) {
categoryAndDatetimeMap = getCategoryAndDateTimeMap(series, pointArrayMap, pIdx);
}
point = { series: mockSeries };
series.pointClass.prototype.applyOptions.apply(point, [options]);
key = point.x;
name = series.data[pIdx] && series.data[pIdx].name;
j = 0;
// Pies, funnels, geo maps etc. use point name in X row
if (!xAxis ||
series.exportKey === 'name' ||
(!hasParallelCoords && xAxis && xAxis.hasNames) && name) {
key = name;
}
if (xTaken) {
if (xTaken[key]) {
key += '|' + pIdx;
}
xTaken[key] = true;
}
if (!rows[key]) {
// Generate the row
rows[key] = [];
// Contain the X values from one or more X axes
rows[key].xValues = [];
}
rows[key].x = point.x;
rows[key].name = name;
rows[key].xValues[xAxisIndex] = point.x;
while (j < valueCount) {
prop = pointArrayMap[j]; // y, z etc
val = point[prop];
rows[key][i + j] = pick(
// Y axis category if present
categoryAndDatetimeMap.categoryMap[prop][val],
// datetime yAxis
categoryAndDatetimeMap.dateTimeValueAxisMap[prop] ?
time.dateFormat(csvOptions.dateFormat, val) :
null,
// linear/log yAxis
val);
j++;
}
});
i = i + j;
}
});
// Make a sortable array
for (x in rows) {
if (Object.hasOwnProperty.call(rows, x)) {
rowArr.push(rows[x]);
}
}
var xAxisIndex, column;
// Add computed column headers and top level headers to final row set
dataRows = multiLevelHeaders ? [topLevelColumnTitles, columnTitles] :
[columnTitles];
i = xAxisIndices.length;
while (i--) { // Start from end to splice in
xAxisIndex = xAxisIndices[i][0];
column = xAxisIndices[i][1];
xAxis = xAxes[xAxisIndex];
// Sort it by X values
rowArr.sort(function (// eslint-disable-line no-loop-func
a, b) {
return a.xValues[xAxisIndex] - b.xValues[xAxisIndex];
});
// Add header row
xTitle = columnHeaderFormatter(xAxis);
dataRows[0].splice(column, 0, xTitle);
if (multiLevelHeaders && dataRows[1]) {
// If using multi level headers, we just added top level header.
// Also add for sub level
dataRows[1].splice(column, 0, xTitle);
}
// Add the category column
rowArr.forEach(function (// eslint-disable-line no-loop-func
row) {
var category = row.name;
if (xAxis && !defined(category)) {
if (xAxis.dateTime) {
if (row.x instanceof Date) {
row.x = row.x.getTime();
}
category = time.dateFormat(csvOptions.dateFormat, row.x);
}
else if (xAxis.categories) {
category = pick(xAxis.names[row.x], xAxis.categories[row.x], row.x);
}
else {
category = row.x;
}
}
// Add the X/date/category
row.splice(column, 0, category);
});
}
dataRows = dataRows.concat(rowArr);
fireEvent(this, 'exportData', { dataRows: dataRows });
return dataRows;
};
/**
* Export-data module required. Returns the current chart data as a CSV string.
*
* @function Highcharts.Chart#getCSV
*
* @param {boolean} [useLocalDecimalPoint]
* Whether to use the local decimal point as detected from the browser.
* This makes it easier to export data to Excel in the same locale as the
* user is.
*
* @return {string}
* CSV representation of the data
*/
Chart.prototype.getCSV = function (useLocalDecimalPoint) {
var csv = '', rows = this.getDataRows(), csvOptions = this.options.exporting.csv, decimalPoint = pick(csvOptions.decimalPoint, csvOptions.itemDelimiter !== ',' && useLocalDecimalPoint ?
(1.1).toLocaleString()[1] :
'.'),
// use ';' for direct to Excel
itemDelimiter = pick(csvOptions.itemDelimiter, decimalPoint === ',' ? ';' : ','),
// '\n' isn't working with the js csv data extraction
lineDelimiter = csvOptions.lineDelimiter;
// Transform the rows to CSV
rows.forEach(function (row, i) {
var val = '', j = row.length;
while (j--) {
val = row[j];
if (typeof val === 'string') {
val = '"' + val + '"';
}
if (typeof val === 'number') {
if (decimalPoint !== '.') {
val = val.toString().replace('.', decimalPoint);
}
}
row[j] = val;
}
// Add the values
csv += row.join(itemDelimiter);
// Add the line delimiter
if (i < rows.length - 1) {
csv += lineDelimiter;
}
});
return csv;
};
/**
* Export-data module required. Build a HTML table with the chart's current
* data.
*
* @sample highcharts/export-data/viewdata/
* View the data from the export menu
*
* @function Highcharts.Chart#getTable
*
* @param {boolean} [useLocalDecimalPoint]
* Whether to use the local decimal point as detected from the browser.
* This makes it easier to export data to Excel in the same locale as the
* user is.
*
* @return {string}
* HTML representation of the data.
*
* @fires Highcharts.Chart#event:afterGetTable
*/
Chart.prototype.getTable = function (useLocalDecimalPoint) {
var html = '<table id="highcharts-data-table-' + this.index + '">', options = this.options, decimalPoint = useLocalDecimalPoint ? (1.1).toLocaleString()[1] : '.', useMultiLevelHeaders = pick(options.exporting.useMultiLevelHeaders, true), rows = this.getDataRows(useMultiLevelHeaders), rowLength = 0, topHeaders = useMultiLevelHeaders ? rows.shift() : null, subHeaders = rows.shift(),
// Compare two rows for equality
isRowEqual = function (row1, row2) {
var i = row1.length;
if (row2.length === i) {
while (i--) {
if (row1[i] !== row2[i]) {
return false;
}
}
}
else {
return false;
}
return true;
},
// Get table cell HTML from value
getCellHTMLFromValue = function (tag, classes, attrs, value) {
var val = pick(value, ''), className = 'text' + (classes ? ' ' + classes : '');
// Convert to string if number
if (typeof val === 'number') {
val = val.toString();
if (decimalPoint === ',') {
val = val.replace('.', decimalPoint);
}
className = 'number';
}
else if (!value) {
className = 'empty';
}
return '<' + tag + (attrs ? ' ' + attrs : '') +
' class="' + className + '">' +
val + '</' + tag + '>';
},
// Get table header markup from row data
getTableHeaderHTML = function (topheaders, subheaders, rowLength) {
var html = '<thead>', i = 0, len = rowLength || subheaders && subheaders.length, next, cur, curColspan = 0, rowspan;
// Clean up multiple table headers. Chart.getDataRows() returns two
// levels of headers when using multilevel, not merged. We need to
// merge identical headers, remove redundant headers, and keep it
// all marked up nicely.
if (useMultiLevelHeaders &&
topheaders &&
subheaders &&
!isRowEqual(topheaders, subheaders)) {
html += '<tr>';
for (; i < len; ++i) {
cur = topheaders[i];
next = topheaders[i + 1];
if (cur === next) {
++curColspan;
}
else if (curColspan) {
// Ended colspan
// Add cur to HTML with colspan.
html += getCellHTMLFromValue('th', 'highcharts-table-topheading', 'scope="col" ' +
'colspan="' + (curColspan + 1) + '"', cur);
curColspan = 0;
}
else {
// Cur is standalone. If it is same as sublevel,
// remove sublevel and add just toplevel.
if (cur === subheaders[i]) {
if (options.exporting.useRowspanHeaders) {
rowspan = 2;
delete subheaders[i];
}
else {
rowspan = 1;
subheaders[i] = '';
}
}
else {
rowspan = 1;
}
html += getCellHTMLFromValue('th', 'highcharts-table-topheading', 'scope="col"' +
(rowspan > 1 ?
' valign="top" rowspan="' + rowspan + '"' :
''), cur);
}
}
html += '</tr>';
}
// Add the subheaders (the only headers if not using multilevels)
if (subheaders) {
html += '<tr>';
for (i = 0, len = subheaders.length; i < len; ++i) {
if (typeof subheaders[i] !== 'undefined') {
html += getCellHTMLFromValue('th', null, 'scope="col"', subheaders[i]);
}
}
html += '</tr>';
}
html += '</thead>';
return html;
};
// Add table caption
if (options.exporting.tableCaption !== false) {
html += '<caption class="highcharts-table-caption">' + pick(options.exporting.tableCaption, (options.title.text ?
htmlencode(options.title.text) :
'Chart')) + '</caption>';
}
// Find longest row
for (var i = 0, len = rows.length; i < len; ++i) {
if (rows[i].length > rowLength) {
rowLength = rows[i].length;
}
}
// Add header
html += getTableHeaderHTML(topHeaders, subHeaders, Math.max(rowLength, subHeaders.length));
// Transform the rows to HTML
html += '<tbody>';
rows.forEach(function (row) {
html += '<tr>';
for (var j = 0; j < rowLength; j++) {
// Make first column a header too. Especially important for
// category axes, but also might make sense for datetime? Should
// await user feedback on this.
html += getCellHTMLFromValue(j ? 'td' : 'th', null, j ? '' : 'scope="row"', row[j]);
}
html += '</tr>';
});
html += '</tbody></table>';
var e = { html: html };
fireEvent(this, 'afterGetTable', e);
return e.html;
};
/**
* Get a blob object from content, if blob is supported
*
* @private
* @param {string} content
* The content to create the blob from.
* @param {string} type
* The type of the content.
* @return {string|undefined}
* The blob object, or undefined if not supported.
*/
function getBlobFromContent(content, type) {
var nav = win.navigator, webKit = (nav.userAgent.indexOf('WebKit') > -1 &&
nav.userAgent.indexOf('Chrome') < 0), domurl = win.URL || win.webkitURL || win;
try {
// MS specific
if (nav.msSaveOrOpenBlob && win.MSBlobBuilder) {
var blob = new win.MSBlobBuilder();
blob.append(content);
return blob.getBlob('image/svg+xml');
}
// Safari requires data URI since it doesn't allow navigation to blob
// URLs.
if (!webKit) {
return domurl.createObjectURL(new win.Blob(['\uFEFF' + content], // #7084
{ type: type }));
}
}
catch (e) {
// Ignore
}
}
/**
* Generates a data URL of CSV for local download in the browser. This is the
* default action for a click on the 'Download CSV' button.
*
* See {@link Highcharts.Chart#getCSV} to get the CSV data itself.
*
* @function Highcharts.Chart#downloadCSV
*
* @requires modules/exporting
*/
Chart.prototype.downloadCSV = function () {
var csv = this.getCSV(true);
downloadURL(getBlobFromContent(csv, 'text/csv') ||
'data:text/csv,\uFEFF' + encodeURIComponent(csv), this.getFilename() + '.csv');
};
/**
* Generates a data URL of an XLS document for local download in the browser.
* This is the default action for a click on the 'Download XLS' button.
*
* See {@link Highcharts.Chart#getTable} to get the table data itself.
*
* @function Highcharts.Chart#downloadXLS
*
* @requires modules/exporting
*/
Chart.prototype.downloadXLS = function () {
var uri = 'data:application/vnd.ms-excel;base64,', template = '<html xmlns:o="urn:schemas-microsoft-com:office:office" ' +
'xmlns:x="urn:schemas-microsoft-com:office:excel" ' +
'xmlns="http://www.w3.org/TR/REC-html40">' +
'<head><!--[if gte mso 9]><xml><x:ExcelWorkbook>' +
'<x:ExcelWorksheets><x:ExcelWorksheet>' +
'<x:Name>Ark1</x:Name>' +
'<x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions>' +
'</x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook>' +
'</xml><![endif]-->' +
'<style>td{border:none;font-family: Calibri, sans-serif;} ' +
'.number{mso-number-format:"0.00";} ' +
'.text{ mso-number-format:"\@";}</style>' +
'<meta name=ProgId content=Excel.Sheet>' +
'<meta charset=UTF-8>' +
'</head><body>' +
this.getTable(true) +
'</body></html>', base64 = function (s) {
return win.btoa(unescape(encodeURIComponent(s))); // #50
};
downloadURL(getBlobFromContent(template, 'application/vnd.ms-excel') ||
uri + base64(template), this.getFilename() + '.xls');
};
/**
* Export-data module required. View the data in a table below the chart.
*
* @function Highcharts.Chart#viewData
*
* @fires Highcharts.Chart#event:afterViewData
*/
Chart.prototype.viewData = function () {
// Create div and generate the data table.
if (!this.dataTableDiv) {
this.dataTableDiv = doc.createElement('div');
this.dataTableDiv.className = 'highcharts-data-table';
// Insert after the chart container
this.renderTo.parentNode.insertBefore(this.dataTableDiv, this.renderTo.nextSibling);
this.dataTableDiv.innerHTML = this.getTable();
}
// Show the data table again.
if (this.dataTableDiv.style.display === '' || this.dataTableDiv.style.display === 'none') {
this.dataTableDiv.style.display = 'block';
}
this.isDataTableVisible = true;
fireEvent(this, 'afterViewData', this.dataTableDiv);
};
/**
* Export-data module required. Hide the data table when visible.
*
* @function Highcharts.Chart#hideData
*/
Chart.prototype.hideData = function () {
if (this.dataTableDiv && this.dataTableDiv.style.display === 'block') {
this.dataTableDiv.style.display = 'none';
}
this.isDataTableVisible = false;
};
Chart.prototype.toggleDataTable = function () {
var _a;
var exportDivElements = this.exportDivElements, menuItems = (_a = exportingOptions === null || exportingOptions === void 0 ? void 0 : exportingOptions.buttons) === null || _a === void 0 ? void 0 : _a.contextButton.menuItems, lang = this.options.lang;
if (this.isDataTableVisible) {
this.hideData();
}
else {
this.viewData();
}
// Change the button text based on table visibility.
if ((exportingOptions === null || exportingOptions === void 0 ? void 0 : exportingOptions.menuItemDefinitions) && (lang === null || lang === void 0 ? void 0 : lang.viewData) &&
lang.hideData &&
menuItems &&
exportDivElements &&
exportDivElements.length) {
exportDivElements[menuItems.indexOf('viewData')]
.innerHTML = this.isDataTableVisible ? lang.hideData : lang.viewData;
}
};
// Add "Download CSV" to the exporting menu.
var exportingOptions = getOptions().exporting;
if (exportingOptions) {
extend(exportingOptions.menuItemDefinitions, {
downloadCSV: {
textKey: 'downloadCSV',
onclick: function () {
this.downloadCSV();
}
},
downloadXLS: {
textKey: 'downloadXLS',
onclick: function () {
this.downloadXLS();
}
},
viewData: {
textKey: 'viewData',
onclick: function () {
this.toggleDataTable();
}
}
});
if (exportingOptions.buttons) {
exportingOptions.buttons.contextButton.menuItems.push('separator', 'downloadCSV', 'downloadXLS', 'viewData');
}
}
// Series specific
if (seriesTypes.map) {
seriesTypes.map.prototype.exportKey = 'name';
}
if (seriesTypes.mapbubble) {
seriesTypes.mapbubble.prototype.exportKey = 'name';
}
if (seriesTypes.treemap) {
seriesTypes.treemap.prototype.exportKey = 'name';
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,214 @@
/* *
* (c) 2009-2020 Rafal Sebestjanski
*
* Full screen for Highcharts
*
* License: www.highcharts.com/license
*/
'use strict';
import Chart from '../Core/Chart/Chart.js';
import H from '../Core/Globals.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent;
/**
* The module allows user to enable display chart in full screen mode.
* Used in StockTools too.
* Based on default solutions in browsers.
*
*/
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* Handles displaying chart's container in the fullscreen mode.
*
* **Note**: Fullscreen is not supported on iPhone due to iOS limitations.
*
* @class
* @name Highcharts.Fullscreen
* @hideconstructor
* @requires modules/full-screen
*/
var Fullscreen = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function Fullscreen(chart) {
/**
* Chart managed by the fullscreen controller.
* @name Highcharts.Fullscreen#chart
* @type {Highcharts.Chart}
*/
this.chart = chart;
/**
* The flag is set to `true` when the chart is displayed in
* the fullscreen mode.
*
* @name Highcharts.Fullscreen#isOpen
* @type {boolean|undefined}
* @since 8.0.1
*/
this.isOpen = false;
var container = chart.renderTo;
// Hold event and methods available only for a current browser.
if (!this.browserProps) {
if (typeof container.requestFullscreen === 'function') {
this.browserProps = {
fullscreenChange: 'fullscreenchange',
requestFullscreen: 'requestFullscreen',
exitFullscreen: 'exitFullscreen'
};
}
else if (container.mozRequestFullScreen) {
this.browserProps = {
fullscreenChange: 'mozfullscreenchange',
requestFullscreen: 'mozRequestFullScreen',
exitFullscreen: 'mozCancelFullScreen'
};
}
else if (container.webkitRequestFullScreen) {
this.browserProps = {
fullscreenChange: 'webkitfullscreenchange',
requestFullscreen: 'webkitRequestFullScreen',
exitFullscreen: 'webkitExitFullscreen'
};
}
else if (container.msRequestFullscreen) {
this.browserProps = {
fullscreenChange: 'MSFullscreenChange',
requestFullscreen: 'msRequestFullscreen',
exitFullscreen: 'msExitFullscreen'
};
}
}
}
/* *
*
* Functions
*
* */
/**
* Stops displaying the chart in fullscreen mode.
* Exporting module required.
*
* @since 8.0.1
*
* @function Highcharts.Fullscreen#close
* @return {void}
* @requires modules/full-screen
*/
Fullscreen.prototype.close = function () {
var fullscreen = this, chart = fullscreen.chart;
// Don't fire exitFullscreen() when user exited using 'Escape' button.
if (fullscreen.isOpen &&
fullscreen.browserProps &&
chart.container.ownerDocument instanceof Document) {
chart.container.ownerDocument[fullscreen.browserProps.exitFullscreen]();
}
// Unbind event as it's necessary only before exiting from fullscreen.
if (fullscreen.unbindFullscreenEvent) {
fullscreen.unbindFullscreenEvent();
}
fullscreen.isOpen = false;
fullscreen.setButtonText();
};
/**
* Displays the chart in fullscreen mode.
* When fired customly by user before exporting context button is created,
* button's text will not be replaced - it's on the user side.
* Exporting module required.
*
* @since 8.0.1
*
* @function Highcharts.Fullscreen#open
* @return {void}
* @requires modules/full-screen
*/
Fullscreen.prototype.open = function () {
var fullscreen = this, chart = fullscreen.chart;
// Handle exitFullscreen() method when user clicks 'Escape' button.
if (fullscreen.browserProps) {
fullscreen.unbindFullscreenEvent = addEvent(chart.container.ownerDocument, // chart's document
fullscreen.browserProps.fullscreenChange, function () {
// Handle lack of async of browser's fullScreenChange event.
if (fullscreen.isOpen) {
fullscreen.isOpen = false;
fullscreen.close();
}
else {
fullscreen.isOpen = true;
fullscreen.setButtonText();
}
});
var promise = chart.renderTo[fullscreen.browserProps.requestFullscreen]();
if (promise) {
// No dot notation because of IE8 compatibility
promise['catch'](function () {
alert(// eslint-disable-line no-alert
'Full screen is not supported inside a frame.');
});
}
addEvent(chart, 'destroy', fullscreen.unbindFullscreenEvent);
}
};
/**
* Replaces the exporting context button's text when toogling the
* fullscreen mode.
*
* @private
*
* @since 8.0.1
*
* @requires modules/full-screen
* @return {void}
*/
Fullscreen.prototype.setButtonText = function () {
var _a;
var chart = this.chart, exportDivElements = chart.exportDivElements, exportingOptions = chart.options.exporting, menuItems = (_a = exportingOptions === null || exportingOptions === void 0 ? void 0 : exportingOptions.buttons) === null || _a === void 0 ? void 0 : _a.contextButton.menuItems, lang = chart.options.lang;
if ((exportingOptions === null || exportingOptions === void 0 ? void 0 : exportingOptions.menuItemDefinitions) && (lang === null || lang === void 0 ? void 0 : lang.exitFullscreen) &&
lang.viewFullscreen &&
menuItems &&
exportDivElements &&
exportDivElements.length) {
exportDivElements[menuItems.indexOf('viewFullscreen')]
.innerHTML = !this.isOpen ?
(exportingOptions.menuItemDefinitions.viewFullscreen.text ||
lang.viewFullscreen) : lang.exitFullscreen;
}
};
/**
* Toggles displaying the chart in fullscreen mode.
* By default, when the exporting module is enabled, a context button with
* a drop down menu in the upper right corner accesses this function.
* Exporting module required.
*
* @since 8.0.1
*
* @sample highcharts/members/chart-togglefullscreen/
* Toggle fullscreen mode from a HTML button
*
* @function Highcharts.Fullscreen#toggle
* @requires modules/full-screen
*/
Fullscreen.prototype.toggle = function () {
var fullscreen = this;
if (!fullscreen.isOpen) {
fullscreen.open();
}
else {
fullscreen.close();
}
};
return Fullscreen;
}());
H.Fullscreen = Fullscreen;
export default H.Fullscreen;
// Initialize fullscreen
addEvent(Chart, 'beforeRender', function () {
/**
* @name Highcharts.Chart#fullscreen
* @type {Highcharts.Fullscreen}
* @requires modules/full-screen
*/
this.fullscreen = new H.Fullscreen(this);
});
+458
View File
@@ -0,0 +1,458 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Chart from '../Core/Chart/Chart.js';
import H from '../Core/Globals.js';
var win = H.win;
import '../Core/Options.js';
import U from '../Core/Utilities.js';
var error = U.error, extend = U.extend, format = U.format, merge = U.merge, wrap = U.wrap;
/**
* Represents the loose structure of a geographic JSON file.
*
* @interface Highcharts.GeoJSON
*/ /**
* Full copyright note of the geographic data.
* @name Highcharts.GeoJSON#copyright
* @type {string|undefined}
*/ /**
* Short copyright note of the geographic data suitable for watermarks.
* @name Highcharts.GeoJSON#copyrightShort
* @type {string|undefined}
*/ /**
* Additional meta information based on the coordinate reference system.
* @name Highcharts.GeoJSON#crs
* @type {Highcharts.Dictionary<any>|undefined}
*/ /**
* Data sets of geographic features.
* @name Highcharts.GeoJSON#features
* @type {Array<Highcharts.GeoJSONFeature>}
*/ /**
* Map projections and transformations to be used when calculating between
* lat/lon and chart values. Required for lat/lon support on maps. Allows
* resizing, rotating, and moving portions of a map within its projected
* coordinate system while still retaining lat/lon support. If using lat/lon
* on a portion of the map that does not match a `hitZone`, the definition with
* the key `default` is used.
* @name Highcharts.GeoJSON#hc-transform
* @type {Highcharts.Dictionary<Highcharts.GeoJSONTranslation>|undefined}
*/ /**
* Title of the geographic data.
* @name Highcharts.GeoJSON#title
* @type {string|undefined}
*/ /**
* Type of the geographic data. Type of an optimized map collection is
* `FeatureCollection`.
* @name Highcharts.GeoJSON#type
* @type {string|undefined}
*/ /**
* Version of the geographic data.
* @name Highcharts.GeoJSON#version
* @type {string|undefined}
*/
/**
* Data set of a geographic feature.
* @interface Highcharts.GeoJSONFeature
* @extends Highcharts.Dictionary<*>
*/ /**
* Data type of the geographic feature.
* @name Highcharts.GeoJSONFeature#type
* @type {string}
*/
/**
* Describes the map projection and transformations applied to a portion of
* a map.
* @interface Highcharts.GeoJSONTranslation
*/ /**
* The coordinate reference system used to generate this portion of the map.
* @name Highcharts.GeoJSONTranslation#crs
* @type {string}
*/ /**
* Define the portion of the map that this defintion applies to. Defined as a
* GeoJSON polygon feature object, with `type` and `coordinates` properties.
* @name Highcharts.GeoJSONTranslation#hitZone
* @type {Highcharts.Dictionary<*>|undefined}
*/ /**
* Property for internal use for maps generated by Highsoft.
* @name Highcharts.GeoJSONTranslation#jsonmarginX
* @type {number|undefined}
*/ /**
* Property for internal use for maps generated by Highsoft.
* @name Highcharts.GeoJSONTranslation#jsonmarginY
* @type {number|undefined}
*/ /**
* Property for internal use for maps generated by Highsoft.
* @name Highcharts.GeoJSONTranslation#jsonres
* @type {number|undefined}
*/ /**
* Specifies clockwise rotation of the coordinates after the projection, but
* before scaling and panning. Defined in radians, relative to the coordinate
* system origin.
* @name Highcharts.GeoJSONTranslation#rotation
* @type {number|undefined}
*/ /**
* The scaling factor applied to the projected coordinates.
* @name Highcharts.GeoJSONTranslation#scale
* @type {number|undefined}
*/ /**
* Property for internal use for maps generated by Highsoft.
* @name Highcharts.GeoJSONTranslation#xoffset
* @type {number|undefined}
*/ /**
* X offset of projected coordinates after scaling.
* @name Highcharts.GeoJSONTranslation#xpan
* @type {number|undefined}
*/ /**
* Property for internal use for maps generated by Highsoft.
* @name Highcharts.GeoJSONTranslation#yoffset
* @type {number|undefined}
*/ /**
* Y offset of projected coordinates after scaling.
* @name Highcharts.GeoJSONTranslation#ypan
* @type {number|undefined}
*/
/**
* Result object of a map transformation.
*
* @interface Highcharts.MapCoordinateObject
*/ /**
* X coordinate on the map.
* @name Highcharts.MapCoordinateObject#x
* @type {number}
*/ /**
* Y coordinate on the map.
* @name Highcharts.MapCoordinateObject#y
* @type {number|null}
*/
/**
* A latitude/longitude object.
*
* @interface Highcharts.MapLatLonObject
*/ /**
* The latitude.
* @name Highcharts.MapLatLonObject#lat
* @type {number}
*/ /**
* The longitude.
* @name Highcharts.MapLatLonObject#lon
* @type {number}
*/
''; // detach doclets above
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* Test for point in polygon. Polygon defined as array of [x,y] points.
* @private
*/
function pointInPolygon(point, polygon) {
var i, j, rel1, rel2, c = false, x = point.x, y = point.y;
for (i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
rel1 = polygon[i][1] > y;
rel2 = polygon[j][1] > y;
if (rel1 !== rel2 &&
(x < (polygon[j][0] -
polygon[i][0]) * (y - polygon[i][1]) /
(polygon[j][1] - polygon[i][1]) +
polygon[i][0])) {
c = !c;
}
}
return c;
}
/**
* Highmaps only. Get point from latitude and longitude using specified
* transform definition.
*
* @requires modules/map
*
* @sample maps/series/latlon-transform/
* Use specific transformation for lat/lon
*
* @function Highcharts.Chart#transformFromLatLon
*
* @param {Highcharts.MapLatLonObject} latLon
* A latitude/longitude object.
*
* @param {*} transform
* The transform definition to use as explained in the
* {@link https://www.highcharts.com/docs/maps/latlon|documentation}.
*
* @return {Highcharts.MapCoordinateObject}
* An object with `x` and `y` properties.
*/
Chart.prototype.transformFromLatLon = function (latLon, transform) {
/**
* Allows to manually load the proj4 library from Highcharts options
* instead of the `window`.
* In case of loading the library from a `script` tag,
* this option is not needed, it will be loaded from there by default.
*
* @type {function}
* @product highmaps
* @apioption chart.proj4
*/
var _a;
var proj4 = (((_a = this.userOptions.chart) === null || _a === void 0 ? void 0 : _a.proj4) || win.proj4);
if (!proj4) {
error(21, false, this);
return {
x: 0,
y: null
};
}
var projected = proj4(transform.crs, [latLon.lon, latLon.lat]), cosAngle = transform.cosAngle ||
(transform.rotation && Math.cos(transform.rotation)), sinAngle = transform.sinAngle ||
(transform.rotation && Math.sin(transform.rotation)), rotated = transform.rotation ? [
projected[0] * cosAngle + projected[1] * sinAngle,
-projected[0] * sinAngle + projected[1] * cosAngle
] : projected;
return {
x: ((rotated[0] - (transform.xoffset || 0)) * (transform.scale || 1) +
(transform.xpan || 0)) * (transform.jsonres || 1) +
(transform.jsonmarginX || 0),
y: (((transform.yoffset || 0) - rotated[1]) * (transform.scale || 1) +
(transform.ypan || 0)) * (transform.jsonres || 1) -
(transform.jsonmarginY || 0)
};
};
/**
* Highmaps only. Get latLon from point using specified transform definition.
* The method returns an object with the numeric properties `lat` and `lon`.
*
* @requires modules/map
*
* @sample maps/series/latlon-transform/
* Use specific transformation for lat/lon
*
* @function Highcharts.Chart#transformToLatLon
*
* @param {Highcharts.Point|Highcharts.MapCoordinateObject} point
* A `Point` instance, or any object containing the properties `x` and
* `y` with numeric values.
*
* @param {*} transform
* The transform definition to use as explained in the
* {@link https://www.highcharts.com/docs/maps/latlon|documentation}.
*
* @return {Highcharts.MapLatLonObject|undefined}
* An object with `lat` and `lon` properties.
*/
Chart.prototype.transformToLatLon = function (point, transform) {
if (typeof win.proj4 === 'undefined') {
error(21, false, this);
return;
}
var normalized = {
x: ((point.x -
(transform.jsonmarginX || 0)) / (transform.jsonres || 1) -
(transform.xpan || 0)) / (transform.scale || 1) +
(transform.xoffset || 0),
y: ((-point.y - (transform.jsonmarginY || 0)) / (transform.jsonres || 1) +
(transform.ypan || 0)) / (transform.scale || 1) +
(transform.yoffset || 0)
}, cosAngle = transform.cosAngle ||
(transform.rotation && Math.cos(transform.rotation)), sinAngle = transform.sinAngle ||
(transform.rotation && Math.sin(transform.rotation)),
// Note: Inverted sinAngle to reverse rotation direction
projected = win.proj4(transform.crs, 'WGS84', transform.rotation ? {
x: normalized.x * cosAngle + normalized.y * -sinAngle,
y: normalized.x * sinAngle + normalized.y * cosAngle
} : normalized);
return { lat: projected.y, lon: projected.x };
};
/**
* Highmaps only. Calculate latitude/longitude values for a point. Returns an
* object with the numeric properties `lat` and `lon`.
*
* @requires modules/map
*
* @sample maps/demo/latlon-advanced/
* Advanced lat/lon demo
*
* @function Highcharts.Chart#fromPointToLatLon
*
* @param {Highcharts.Point|Highcharts.MapCoordinateObject} point
* A `Point` instance or anything containing `x` and `y` properties with
* numeric values.
*
* @return {Highcharts.MapLatLonObject|undefined}
* An object with `lat` and `lon` properties.
*/
Chart.prototype.fromPointToLatLon = function (point) {
var transforms = this.mapTransforms, transform;
if (!transforms) {
error(22, false, this);
return;
}
for (transform in transforms) {
if (Object.hasOwnProperty.call(transforms, transform) &&
transforms[transform].hitZone &&
pointInPolygon({ x: point.x, y: -point.y }, transforms[transform].hitZone.coordinates[0])) {
return this.transformToLatLon(point, transforms[transform]);
}
}
return this.transformToLatLon(point, transforms['default'] // eslint-disable-line dot-notation
);
};
/**
* Highmaps only. Get chart coordinates from latitude/longitude. Returns an
* object with x and y values corresponding to the `xAxis` and `yAxis`.
*
* @requires modules/map
*
* @sample maps/series/latlon-to-point/
* Find a point from lat/lon
*
* @function Highcharts.Chart#fromLatLonToPoint
*
* @param {Highcharts.MapLatLonObject} latLon
* Coordinates.
*
* @return {Highcharts.MapCoordinateObject}
* X and Y coordinates in terms of chart axis values.
*/
Chart.prototype.fromLatLonToPoint = function (latLon) {
var transforms = this.mapTransforms, transform, coords;
if (!transforms) {
error(22, false, this);
return {
x: 0,
y: null
};
}
for (transform in transforms) {
if (Object.hasOwnProperty.call(transforms, transform) &&
transforms[transform].hitZone) {
coords = this.transformFromLatLon(latLon, transforms[transform]);
if (pointInPolygon({ x: coords.x, y: -coords.y }, transforms[transform].hitZone.coordinates[0])) {
return coords;
}
}
}
return this.transformFromLatLon(latLon, transforms['default'] // eslint-disable-line dot-notation
);
};
/**
* Highmaps only. Restructure a GeoJSON object in preparation to be read
* directly by the
* {@link https://api.highcharts.com/highmaps/plotOptions.series.mapData|series.mapData}
* option. The GeoJSON will be broken down to fit a specific Highcharts type,
* either `map`, `mapline` or `mappoint`. Meta data in GeoJSON's properties
* object will be copied directly over to {@link Point.properties} in Highmaps.
*
* @requires modules/map
*
* @sample maps/demo/geojson/
* Simple areas
* @sample maps/demo/geojson-multiple-types/
* Multiple types
*
* @function Highcharts.geojson
*
* @param {Highcharts.GeoJSON} geojson
* The GeoJSON structure to parse, represented as a JavaScript object
* rather than a JSON string.
*
* @param {string} [hType=map]
* The Highmaps series type to prepare for. Setting "map" will return
* GeoJSON polygons and multipolygons. Setting "mapline" will return
* GeoJSON linestrings and multilinestrings. Setting "mappoint" will
* return GeoJSON points and multipoints.
*
* @return {Array<*>}
* An object ready for the `mapData` option.
*/
H.geojson = function (geojson, hType, series) {
var mapData = [], path = [], polygonToPath = function (polygon) {
polygon.forEach(function (point, i) {
if (i === 0) {
path.push(['M', point[0], -point[1]]);
}
else {
path.push(['L', point[0], -point[1]]);
}
});
};
hType = hType || 'map';
geojson.features.forEach(function (feature) {
var geometry = feature.geometry, type = geometry.type, coordinates = geometry.coordinates, properties = feature.properties, point;
path = [];
if (hType === 'map' || hType === 'mapbubble') {
if (type === 'Polygon') {
coordinates.forEach(polygonToPath);
path.push(['Z']);
}
else if (type === 'MultiPolygon') {
coordinates.forEach(function (items) {
items.forEach(polygonToPath);
});
path.push(['Z']);
}
if (path.length) {
point = { path: path };
}
}
else if (hType === 'mapline') {
if (type === 'LineString') {
polygonToPath(coordinates);
}
else if (type === 'MultiLineString') {
coordinates.forEach(polygonToPath);
}
if (path.length) {
point = { path: path };
}
}
else if (hType === 'mappoint') {
if (type === 'Point') {
point = {
x: coordinates[0],
y: -coordinates[1]
};
}
}
if (point) {
mapData.push(extend(point, {
name: properties.name || properties.NAME,
/**
* In Highmaps, when data is loaded from GeoJSON, the GeoJSON
* item's properies are copied over here.
*
* @requires modules/map
* @name Highcharts.Point#properties
* @type {*}
*/
properties: properties
}));
}
});
// Create a credits text that includes map source, to be picked up in
// Chart.addCredits
if (series && geojson.copyrightShort) {
series.chart.mapCredits = format(series.chart.options.credits.mapText, { geojson: geojson });
series.chart.mapCreditsFull = format(series.chart.options.credits.mapTextFull, { geojson: geojson });
}
return mapData;
};
// Override addCredits to include map source by default
wrap(Chart.prototype, 'addCredits', function (proceed, credits) {
credits = merge(true, this.options.credits, credits);
// Disable credits link if map credits enabled. This to allow for in-text
// anchors.
if (this.mapCredits) {
credits.href = null;
}
proceed.call(this, credits);
// Add full map credits to hover
if (this.credits && this.mapCreditsFull) {
this.credits.attr({
title: this.mapCreditsFull
});
}
});
File diff suppressed because it is too large Load Diff
+240
View File
@@ -0,0 +1,240 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../Core/Globals.js';
import U from '../Core/Utilities.js';
var pick = U.pick;
// Mathematical Functionility
var deg2rad = H.deg2rad;
/* eslint-disable max-len */
/**
* Apply 3-D rotation
* Euler Angles (XYZ):
* cosA = cos(Alfa|Roll)
* cosB = cos(Beta|Pitch)
* cosG = cos(Gamma|Yaw)
*
* Composite rotation:
* | cosB * cosG | cosB * sinG | -sinB |
* | sinA * sinB * cosG - cosA * sinG | sinA * sinB * sinG + cosA * cosG | sinA * cosB |
* | cosA * sinB * cosG + sinA * sinG | cosA * sinB * sinG - sinA * cosG | cosA * cosB |
*
* Now, Gamma/Yaw is not used (angle=0), so we assume cosG = 1 and sinG = 0, so
* we get:
* | cosB | 0 | - sinB |
* | sinA * sinB | cosA | sinA * cosB |
* | cosA * sinB | - sinA | cosA * cosB |
*
* But in browsers, y is reversed, so we get sinA => -sinA. The general result
* is:
* | cosB | 0 | - sinB | | x | | px |
* | - sinA * sinB | cosA | - sinA * cosB | x | y | = | py |
* | cosA * sinB | sinA | cosA * cosB | | z | | pz |
*
* @private
* @function rotate3D
*/
/* eslint-enable max-len */
/**
* @private
* @param {number} x
* X coordinate
* @param {number} y
* Y coordinate
* @param {number} z
* Z coordinate
* @param {Highcharts.Rotation3dObject} angles
* Rotation angles
* @return {Highcharts.Rotation3dObject}
* Rotated position
*/
function rotate3D(x, y, z, angles) {
return {
x: angles.cosB * x - angles.sinB * z,
y: -angles.sinA * angles.sinB * x + angles.cosA * y -
angles.cosB * angles.sinA * z,
z: angles.cosA * angles.sinB * x + angles.sinA * y +
angles.cosA * angles.cosB * z
};
}
/**
* Perspective3D function is available in global Highcharts scope because is
* needed also outside of perspective() function (#8042).
* @private
* @function Highcharts.perspective3D
*
* @param {Highcharts.Position3dObject} coordinate
* 3D position
*
* @param {Highcharts.Position3dObject} origin
* 3D root position
*
* @param {number} distance
* Perspective distance
*
* @return {Highcharts.PositionObject}
* Perspective 3D Position
*
* @requires highcharts-3d
*/
var perspective3D = H.perspective3D = function (coordinate, origin, distance) {
var projection = ((distance > 0) && (distance < Number.POSITIVE_INFINITY)) ?
distance / (coordinate.z + origin.z + distance) :
1;
return {
x: coordinate.x * projection,
y: coordinate.y * projection
};
};
/**
* Transforms a given array of points according to the angles in chart.options.
*
* @private
* @function Highcharts.perspective
*
* @param {Array<Highcharts.Position3dObject>} points
* The array of points
*
* @param {Highcharts.Chart} chart
* The chart
*
* @param {boolean} [insidePlotArea]
* Whether to verifiy that the points are inside the plotArea
*
* @param {boolean} [useInvertedPersp]
* Whether to use inverted perspective in calculations
*
* @return {Array<Highcharts.Position3dObject>}
* An array of transformed points
*
* @requires highcharts-3d
*/
var perspective = H.perspective = function (points, chart, insidePlotArea, useInvertedPersp) {
var options3d = chart.options.chart.options3d,
/* The useInvertedPersp argument is used for
* inverted charts with already inverted elements,
* such as dataLabels or tooltip positions.
*/
inverted = pick(useInvertedPersp, insidePlotArea ? chart.inverted : false), origin = {
x: chart.plotWidth / 2,
y: chart.plotHeight / 2,
z: options3d.depth / 2,
vd: pick(options3d.depth, 1) * pick(options3d.viewDistance, 0)
}, scale = chart.scale3d || 1, beta = deg2rad * options3d.beta * (inverted ? -1 : 1), alpha = deg2rad * options3d.alpha * (inverted ? -1 : 1), angles = {
cosA: Math.cos(alpha),
cosB: Math.cos(-beta),
sinA: Math.sin(alpha),
sinB: Math.sin(-beta)
};
if (!insidePlotArea) {
origin.x += chart.plotLeft;
origin.y += chart.plotTop;
}
// Transform each point
return points.map(function (point) {
var rotated = rotate3D((inverted ? point.y : point.x) - origin.x, (inverted ? point.x : point.y) - origin.y, (point.z || 0) - origin.z, angles),
// Apply perspective
coordinate = perspective3D(rotated, origin, origin.vd);
// Apply translation
coordinate.x = coordinate.x * scale + origin.x;
coordinate.y = coordinate.y * scale + origin.y;
coordinate.z = rotated.z * scale + origin.z;
return {
x: (inverted ? coordinate.y : coordinate.x),
y: (inverted ? coordinate.x : coordinate.y),
z: coordinate.z
};
});
};
/**
* Calculate a distance from camera to points - made for calculating zIndex of
* scatter points.
*
* @private
* @function Highcharts.pointCameraDistance
*
* @param {Highcharts.Dictionary<number>} coordinates
* Coordinates of the specific point
*
* @param {Highcharts.Chart} chart
* Related chart
*
* @return {number}
* Distance from camera to point
*
* @requires highcharts-3d
*/
var pointCameraDistance = H.pointCameraDistance = function (coordinates, chart) {
var options3d = chart.options.chart.options3d, cameraPosition = {
x: chart.plotWidth / 2,
y: chart.plotHeight / 2,
z: pick(options3d.depth, 1) * pick(options3d.viewDistance, 0) +
options3d.depth
},
// Added support for objects with plotX or x coordinates.
distance = Math.sqrt(Math.pow(cameraPosition.x - pick(coordinates.plotX, coordinates.x), 2) +
Math.pow(cameraPosition.y - pick(coordinates.plotY, coordinates.y), 2) +
Math.pow(cameraPosition.z - pick(coordinates.plotZ, coordinates.z), 2));
return distance;
};
/**
* Calculate area of a 2D polygon using Shoelace algorithm
* https://en.wikipedia.org/wiki/Shoelace_formula
*
* @private
* @function Highcharts.shapeArea
*
* @param {Array<Highcharts.PositionObject>} vertexes
* 2D Polygon
*
* @return {number}
* Calculated area
*
* @requires highcharts-3d
*/
var shapeArea = H.shapeArea = function (vertexes) {
var area = 0, i, j;
for (i = 0; i < vertexes.length; i++) {
j = (i + 1) % vertexes.length;
area += vertexes[i].x * vertexes[j].y - vertexes[j].x * vertexes[i].y;
}
return area / 2;
};
/**
* Calculate area of a 3D polygon after perspective projection
*
* @private
* @function Highcharts.shapeArea3d
*
* @param {Array<Highcharts.Position3dObject>} vertexes
* 3D Polygon
*
* @param {Highcharts.Chart} chart
* Related chart
*
* @param {boolean} [insidePlotArea]
* Whether to verifiy that the points are inside the plotArea
*
* @return {number}
* Calculated area
*
* @requires highcharts-3d
*/
var shapeArea3D = H.shapeArea3d = function (vertexes, chart, insidePlotArea) {
return shapeArea(perspective(vertexes, chart, insidePlotArea));
};
var mathModule = {
perspective: perspective,
perspective3D: perspective3D,
pointCameraDistance: pointCameraDistance,
shapeArea: shapeArea,
shapeArea3D: shapeArea3D
};
export default mathModule;
@@ -0,0 +1,186 @@
/* *
*
* Plugin for displaying a message when there is no data visible in chart.
*
* (c) 2010-2020 Highsoft AS
*
* Author: Oystein Moseng
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../Core/Globals.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, extend = U.extend, getOptions = U.getOptions;
import '../Core/Series/Series.js';
var chartPrototype = H.Chart.prototype, defaultOptions = getOptions();
// Add language option
extend(defaultOptions.lang,
/**
* @optionparent lang
*/
{
/**
* The text to display when the chart contains no data.
*
* @see [noData](#noData)
*
* @sample highcharts/no-data-to-display/no-data-line
* No-data text
*
* @since 3.0.8
* @product highcharts highstock
* @requires modules/no-data-to-display
*/
noData: 'No data to display'
});
// Add default display options for message
/**
* Options for displaying a message like "No data to display".
* This feature requires the file no-data-to-display.js to be loaded in the
* page. The actual text to display is set in the lang.noData option.
*
* @sample highcharts/no-data-to-display/no-data-line
* Line chart with no-data module
* @sample highcharts/no-data-to-display/no-data-pie
* Pie chart with no-data module
*
* @product highcharts highstock gantt
* @requires modules/no-data-to-display
* @optionparent noData
*/
defaultOptions.noData = {
/**
* An object of additional SVG attributes for the no-data label.
*
* @type {Highcharts.SVGAttributes}
* @since 3.0.8
* @product highcharts highstock gantt
* @apioption noData.attr
*/
attr: {
zIndex: 1
},
/**
* Whether to insert the label as HTML, or as pseudo-HTML rendered with
* SVG.
*
* @type {boolean}
* @default false
* @since 4.1.10
* @product highcharts highstock gantt
* @apioption noData.useHTML
*/
/**
* The position of the no-data label, relative to the plot area.
*
* @type {Highcharts.AlignObject}
* @since 3.0.8
*/
position: {
/**
* Horizontal offset of the label, in pixels.
*/
x: 0,
/**
* Vertical offset of the label, in pixels.
*/
y: 0,
/**
* Horizontal alignment of the label.
*
* @type {Highcharts.AlignValue}
*/
align: 'center',
/**
* Vertical alignment of the label.
*
* @type {Highcharts.VerticalAlignValue}
*/
verticalAlign: 'middle'
},
/**
* CSS styles for the no-data label.
*
* @sample highcharts/no-data-to-display/no-data-line
* Styled no-data text
*
* @type {Highcharts.CSSObject}
*/
style: {
/** @ignore */
fontWeight: 'bold',
/** @ignore */
fontSize: '12px',
/** @ignore */
color: '#666666'
}
};
/**
* Display a no-data message.
* @private
* @function Highcharts.Chart#showNoData
* @param {string} [str]
* An optional message to show in place of the default one
* @return {void}
* @requires modules/no-data-to-display
*/
chartPrototype.showNoData = function (str) {
var chart = this, options = chart.options, text = str || (options && options.lang.noData), noDataOptions = options && options.noData;
if (!chart.noDataLabel && chart.renderer) {
chart.noDataLabel = chart.renderer
.label(text, 0, 0, null, null, null, noDataOptions.useHTML, null, 'no-data');
if (!chart.styledMode) {
chart.noDataLabel
.attr(noDataOptions.attr)
.css(noDataOptions.style);
}
chart.noDataLabel.add();
chart.noDataLabel.align(extend(chart.noDataLabel.getBBox(), noDataOptions.position), false, 'plotBox');
}
};
/**
* Hide no-data message.
*
* @private
* @function Highcharts.Chart#hideNoData
* @return {void}
* @requires modules/no-data-to-display
*/
chartPrototype.hideNoData = function () {
var chart = this;
if (chart.noDataLabel) {
chart.noDataLabel = chart.noDataLabel.destroy();
}
};
/**
* Returns true if there are data points within the plot area now.
*
* @private
* @function Highcharts.Chart#hasData
* @return {boolean|undefined}
* True, if there are data points.
* @requires modules/no-data-to-display
*/
chartPrototype.hasData = function () {
var chart = this, series = chart.series || [], i = series.length;
while (i--) {
if (series[i].hasData() && !series[i].options.isInternal) {
return true;
}
}
return chart.loadingShown; // #4588
};
/* eslint-disable no-invalid-this */
// Add event listener to handle automatic show or hide no-data message.
addEvent(H.Chart, 'render', function handleNoData() {
if (this.hasData()) {
this.hideNoData();
}
else {
this.showNoData();
}
});
@@ -0,0 +1,595 @@
/* *
*
* Client side exporting module
*
* (c) 2015 Torstein Honsi / Oystein Moseng
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
/* global MSBlobBuilder */
import Chart from '../Core/Chart/Chart.js';
import H from '../Core/Globals.js';
var win = H.win, doc = H.doc;
import '../Core/Options.js';
import SVGRenderer from '../Core/Renderer/SVG/SVGRenderer.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, error = U.error, extend = U.extend, getOptions = U.getOptions, merge = U.merge;
import DownloadURL from '../Extensions/DownloadURL.js';
var downloadURL = DownloadURL.downloadURL;
var domurl = win.URL || win.webkitURL || win, nav = win.navigator, isMSBrowser = /Edge\/|Trident\/|MSIE /.test(nav.userAgent),
// Milliseconds to defer image load event handlers to offset IE bug
loadEventDeferDelay = isMSBrowser ? 150 : 0;
// Dummy object so we can reuse our canvas-tools.js without errors
H.CanVGRenderer = {};
/* eslint-disable valid-jsdoc */
/**
* Downloads a script and executes a callback when done.
*
* @private
* @function getScript
* @param {string} scriptLocation
* @param {Function} callback
* @return {void}
*/
function getScript(scriptLocation, callback) {
var head = doc.getElementsByTagName('head')[0], script = doc.createElement('script');
script.type = 'text/javascript';
script.src = scriptLocation;
script.onload = callback;
script.onerror = function () {
error('Error loading script ' + scriptLocation);
};
head.appendChild(script);
}
/**
* Get blob URL from SVG code. Falls back to normal data URI.
*
* @private
* @function Highcharts.svgToDataURL
* @param {string} svg
* @return {string}
*/
H.svgToDataUrl = function (svg) {
// Webkit and not chrome
var webKit = (nav.userAgent.indexOf('WebKit') > -1 &&
nav.userAgent.indexOf('Chrome') < 0);
try {
// Safari requires data URI since it doesn't allow navigation to blob
// URLs. Firefox has an issue with Blobs and internal references,
// leading to gradients not working using Blobs (#4550)
if (!webKit && nav.userAgent.toLowerCase().indexOf('firefox') < 0) {
return domurl.createObjectURL(new win.Blob([svg], {
type: 'image/svg+xml;charset-utf-16'
}));
}
}
catch (e) {
// Ignore
}
return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg);
};
/**
* Get data:URL from image URL. Pass in callbacks to handle results.
*
* @private
* @function Highcharts.imageToDataUrl
*
* @param {string} imageURL
*
* @param {string} imageType
*
* @param {*} callbackArgs
* callbackArgs is used only by callbacks.
*
* @param {number} scale
*
* @param {Function} successCallback
* Receives four arguments: imageURL, imageType, callbackArgs, and scale.
*
* @param {Function} taintedCallback
* Receives four arguments: imageURL, imageType, callbackArgs, and scale.
*
* @param {Function} noCanvasSupportCallback
* Receives four arguments: imageURL, imageType, callbackArgs, and scale.
*
* @param {Function} failedLoadCallback
* Receives four arguments: imageURL, imageType, callbackArgs, and scale.
*
* @param {Function} [finallyCallback]
* finallyCallback is always called at the end of the process. All
* callbacks receive four arguments: imageURL, imageType, callbackArgs,
* and scale.
*
* @return {void}
*/
H.imageToDataUrl = function (imageURL, imageType, callbackArgs, scale, successCallback, taintedCallback, noCanvasSupportCallback, failedLoadCallback, finallyCallback) {
var img = new win.Image(), taintedHandler, loadHandler = function () {
setTimeout(function () {
var canvas = doc.createElement('canvas'), ctx = canvas.getContext && canvas.getContext('2d'), dataURL;
try {
if (!ctx) {
noCanvasSupportCallback(imageURL, imageType, callbackArgs, scale);
}
else {
canvas.height = img.height * scale;
canvas.width = img.width * scale;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// Now we try to get the contents of the canvas.
try {
dataURL = canvas.toDataURL(imageType);
successCallback(dataURL, imageType, callbackArgs, scale);
}
catch (e) {
taintedHandler(imageURL, imageType, callbackArgs, scale);
}
}
}
finally {
if (finallyCallback) {
finallyCallback(imageURL, imageType, callbackArgs, scale);
}
}
// IE bug where image is not always ready despite calling load
// event.
}, loadEventDeferDelay);
},
// Image load failed (e.g. invalid URL)
errorHandler = function () {
failedLoadCallback(imageURL, imageType, callbackArgs, scale);
if (finallyCallback) {
finallyCallback(imageURL, imageType, callbackArgs, scale);
}
};
// This is called on load if the image drawing to canvas failed with a
// security error. We retry the drawing with crossOrigin set to Anonymous.
taintedHandler = function () {
img = new win.Image();
taintedHandler = taintedCallback;
// Must be set prior to loading image source
img.crossOrigin = 'Anonymous';
img.onload = loadHandler;
img.onerror = errorHandler;
img.src = imageURL;
};
img.onload = loadHandler;
img.onerror = errorHandler;
img.src = imageURL;
};
/* eslint-enable valid-jsdoc */
/**
* Get data URL to an image of an SVG and call download on it options object:
*
* - **filename:** Name of resulting downloaded file without extension. Default
* is `chart`.
*
* - **type:** File type of resulting download. Default is `image/png`.
*
* - **scale:** Scaling factor of downloaded image compared to source. Default
* is `1`.
*
* - **libURL:** URL pointing to location of dependency scripts to download on
* demand. Default is the exporting.libURL option of the global Highcharts
* options pointing to our server.
*
* @function Highcharts.downloadSVGLocal
*
* @param {string} svg
* The generated SVG
*
* @param {Highcharts.ExportingOptions} options
* The exporting options
*
* @param {Function} failCallback
* The callback function in case of errors
*
* @param {Function} [successCallback]
* The callback function in case of success
*
* @return {void}
*/
H.downloadSVGLocal = function (svg, options, failCallback, successCallback) {
var svgurl, blob, objectURLRevoke = true, finallyHandler, libURL = (options.libURL || getOptions().exporting.libURL), dummySVGContainer = doc.createElement('div'), imageType = options.type || 'image/png', filename = ((options.filename || 'chart') +
'.' +
(imageType === 'image/svg+xml' ? 'svg' : imageType.split('/')[1])), scale = options.scale || 1;
// Allow libURL to end with or without fordward slash
libURL = libURL.slice(-1) !== '/' ? libURL + '/' : libURL;
/* eslint-disable valid-jsdoc */
/**
* @private
*/
function svgToPdf(svgElement, margin) {
var width = svgElement.width.baseVal.value + 2 * margin, height = svgElement.height.baseVal.value + 2 * margin, pdf = new win.jsPDF(// eslint-disable-line new-cap
height > width ? 'p' : 'l', // setting orientation to portrait if height exceeds width
'pt', [width, height]);
// Workaround for #7090, hidden elements were drawn anyway. It comes
// down to https://github.com/yWorks/svg2pdf.js/issues/28. Check this
// later.
[].forEach.call(svgElement.querySelectorAll('*[visibility="hidden"]'), function (node) {
node.parentNode.removeChild(node);
});
win.svg2pdf(svgElement, pdf, { removeInvalid: true });
return pdf.output('datauristring');
}
/**
* @private
* @return {void}
*/
function downloadPDF() {
dummySVGContainer.innerHTML = svg;
var textElements = dummySVGContainer.getElementsByTagName('text'), titleElements, svgData,
// Copy style property to element from parents if it's not there.
// Searches up hierarchy until it finds prop, or hits the chart
// container.
setStylePropertyFromParents = function (el, propName) {
var curParent = el;
while (curParent && curParent !== dummySVGContainer) {
if (curParent.style[propName]) {
el.style[propName] =
curParent.style[propName];
break;
}
curParent = curParent.parentNode;
}
};
// Workaround for the text styling. Making sure it does pick up settings
// for parent elements.
[].forEach.call(textElements, function (el) {
// Workaround for the text styling. making sure it does pick up the
// root element
['font-family', 'font-size'].forEach(function (property) {
setStylePropertyFromParents(el, property);
});
el.style['font-family'] = (el.style['font-family'] &&
el.style['font-family'].split(' ').splice(-1));
// Workaround for plotband with width, removing title from text
// nodes
titleElements = el.getElementsByTagName('title');
[].forEach.call(titleElements, function (titleElement) {
el.removeChild(titleElement);
});
});
svgData = svgToPdf(dummySVGContainer.firstChild, 0);
try {
downloadURL(svgData, filename);
if (successCallback) {
successCallback();
}
}
catch (e) {
failCallback(e);
}
}
/* eslint-enable valid-jsdoc */
// Initiate download depending on file type
if (imageType === 'image/svg+xml') {
// SVG download. In this case, we want to use Microsoft specific Blob if
// available
try {
if (typeof nav.msSaveOrOpenBlob !== 'undefined') {
blob = new MSBlobBuilder();
blob.append(svg);
svgurl = blob.getBlob('image/svg+xml');
}
else {
svgurl = H.svgToDataUrl(svg);
}
downloadURL(svgurl, filename);
if (successCallback) {
successCallback();
}
}
catch (e) {
failCallback(e);
}
}
else if (imageType === 'application/pdf') {
if (win.jsPDF && win.svg2pdf) {
downloadPDF();
}
else {
// Must load pdf libraries first. // Don't destroy the object URL
// yet since we are doing things asynchronously. A cleaner solution
// would be nice, but this will do for now.
objectURLRevoke = true;
getScript(libURL + 'jspdf.js', function () {
getScript(libURL + 'svg2pdf.js', function () {
downloadPDF();
});
});
}
}
else {
// PNG/JPEG download - create bitmap from SVG
svgurl = H.svgToDataUrl(svg);
finallyHandler = function () {
try {
domurl.revokeObjectURL(svgurl);
}
catch (e) {
// Ignore
}
};
// First, try to get PNG by rendering on canvas
H.imageToDataUrl(svgurl, imageType, {}, scale, function (imageURL) {
// Success
try {
downloadURL(imageURL, filename);
if (successCallback) {
successCallback();
}
}
catch (e) {
failCallback(e);
}
}, function () {
// Failed due to tainted canvas
// Create new and untainted canvas
var canvas = doc.createElement('canvas'), ctx = canvas.getContext('2d'), imageWidth = svg.match(/^<svg[^>]*width\s*=\s*\"?(\d+)\"?[^>]*>/)[1] * scale, imageHeight = svg.match(/^<svg[^>]*height\s*=\s*\"?(\d+)\"?[^>]*>/)[1] * scale, downloadWithCanVG = function () {
ctx.drawSvg(svg, 0, 0, imageWidth, imageHeight);
try {
downloadURL(nav.msSaveOrOpenBlob ?
canvas.msToBlob() :
canvas.toDataURL(imageType), filename);
if (successCallback) {
successCallback();
}
}
catch (e) {
failCallback(e);
}
finally {
finallyHandler();
}
};
canvas.width = imageWidth;
canvas.height = imageHeight;
if (win.canvg) {
// Use preloaded canvg
downloadWithCanVG();
}
else {
// Must load canVG first. // Don't destroy the object URL
// yet since we are doing things asynchronously. A cleaner
// solution would be nice, but this will do for now.
objectURLRevoke = true;
// Get RGBColor.js first, then canvg
getScript(libURL + 'rgbcolor.js', function () {
getScript(libURL + 'canvg.js', function () {
downloadWithCanVG();
});
});
}
},
// No canvas support
failCallback,
// Failed to load image
failCallback,
// Finally
function () {
if (objectURLRevoke) {
finallyHandler();
}
});
}
};
/* eslint-disable valid-jsdoc */
/**
* Get SVG of chart prepared for client side export. This converts embedded
* images in the SVG to data URIs. It requires the regular exporting module. The
* options and chartOptions arguments are passed to the getSVGForExport
* function.
*
* @private
* @function Highcharts.Chart#getSVGForLocalExport
* @param {Highcharts.ExportingOptions} options
* @param {Highcharts.Options} chartOptions
* @param {Function} failCallback
* @param {Function} successCallback
* @return {void}
*/
Chart.prototype.getSVGForLocalExport = function (options, chartOptions, failCallback, successCallback) {
var chart = this, images, imagesEmbedded = 0, chartCopyContainer, chartCopyOptions, el, i, l, href,
// After grabbing the SVG of the chart's copy container we need to do
// sanitation on the SVG
sanitize = function (svg) {
return chart.sanitizeSVG(svg, chartCopyOptions);
},
// When done with last image we have our SVG
checkDone = function () {
if (imagesEmbedded === images.length) {
successCallback(sanitize(chartCopyContainer.innerHTML));
}
},
// Success handler, we converted image to base64!
embeddedSuccess = function (imageURL, imageType, callbackArgs) {
++imagesEmbedded;
// Change image href in chart copy
callbackArgs.imageElement.setAttributeNS('http://www.w3.org/1999/xlink', 'href', imageURL);
checkDone();
};
// Hook into getSVG to get a copy of the chart copy's container (#8273)
chart.unbindGetSVG = addEvent(chart, 'getSVG', function (e) {
chartCopyOptions = e.chartCopy.options;
chartCopyContainer = e.chartCopy.container.cloneNode(true);
});
// Trigger hook to get chart copy
chart.getSVGForExport(options, chartOptions);
images = chartCopyContainer.getElementsByTagName('image');
try {
// If there are no images to embed, the SVG is okay now.
if (!images.length) {
// Use SVG of chart copy
successCallback(sanitize(chartCopyContainer.innerHTML));
return;
}
// Go through the images we want to embed
for (i = 0, l = images.length; i < l; ++i) {
el = images[i];
href = el.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
if (href) {
H.imageToDataUrl(href, 'image/png', { imageElement: el }, options.scale, embeddedSuccess,
// Tainted canvas
failCallback,
// No canvas support
failCallback,
// Failed to load source
failCallback);
// Hidden, boosted series have blank href (#10243)
}
else {
++imagesEmbedded;
el.parentNode.removeChild(el);
checkDone();
}
}
}
catch (e) {
failCallback(e);
}
// Clean up
chart.unbindGetSVG();
};
/* eslint-enable valid-jsdoc */
/**
* Exporting and offline-exporting modules required. Export a chart to an image
* locally in the user's browser.
*
* @function Highcharts.Chart#exportChartLocal
*
* @param {Highcharts.ExportingOptions} [exportingOptions]
* Exporting options, the same as in
* {@link Highcharts.Chart#exportChart}.
*
* @param {Highcharts.Options} [chartOptions]
* Additional chart options for the exported chart. For example a
* different background color can be added here, or `dataLabels`
* for export only.
*
* @return {void}
*
* @requires modules/exporting
*/
Chart.prototype.exportChartLocal = function (exportingOptions, chartOptions) {
var chart = this, options = merge(chart.options.exporting, exportingOptions), fallbackToExportServer = function (err) {
if (options.fallbackToExportServer === false) {
if (options.error) {
options.error(options, err);
}
else {
error(28, true); // Fallback disabled
}
}
else {
chart.exportChart(options);
}
}, svgSuccess = function (svg) {
// If SVG contains foreignObjects all exports except SVG will fail,
// as both CanVG and svg2pdf choke on this. Gracefully fall back.
if (svg.indexOf('<foreignObject') > -1 &&
options.type !== 'image/svg+xml') {
fallbackToExportServer('Image type not supported' +
'for charts with embedded HTML');
}
else {
H.downloadSVGLocal(svg, extend({ filename: chart.getFilename() }, options), fallbackToExportServer);
}
},
// Return true if the SVG contains images with external data. With the
// boost module there are `image` elements with encoded PNGs, these are
// supported by svg2pdf and should pass (#10243).
hasExternalImages = function () {
return [].some.call(chart.container.getElementsByTagName('image'), function (image) {
var href = image.getAttribute('href');
return href !== '' && href.indexOf('data:') !== 0;
});
};
// If we are on IE and in styled mode, add a whitelist to the renderer for
// inline styles that we want to pass through. There are so many styles by
// default in IE that we don't want to blacklist them all.
if (isMSBrowser && chart.styledMode) {
SVGRenderer.prototype.inlineWhitelist = [
/^blockSize/,
/^border/,
/^caretColor/,
/^color/,
/^columnRule/,
/^columnRuleColor/,
/^cssFloat/,
/^cursor/,
/^fill$/,
/^fillOpacity/,
/^font/,
/^inlineSize/,
/^length/,
/^lineHeight/,
/^opacity/,
/^outline/,
/^parentRule/,
/^rx$/,
/^ry$/,
/^stroke/,
/^textAlign/,
/^textAnchor/,
/^textDecoration/,
/^transform/,
/^vectorEffect/,
/^visibility/,
/^x$/,
/^y$/
];
}
// Always fall back on:
// - MS browsers: Embedded images JPEG/PNG, or any PDF
// - Embedded images and PDF
if ((isMSBrowser &&
(options.type === 'application/pdf' ||
chart.container.getElementsByTagName('image').length &&
options.type !== 'image/svg+xml')) || (options.type === 'application/pdf' &&
hasExternalImages())) {
fallbackToExportServer('Image type not supported for this chart/browser.');
return;
}
chart.getSVGForLocalExport(options, chartOptions, fallbackToExportServer, svgSuccess);
};
// Extend the default options to use the local exporter logic
merge(true, getOptions().exporting, {
libURL: 'https://code.highcharts.com/8.2.0/lib/',
// When offline-exporting is loaded, redefine the menu item definitions
// related to download.
menuItemDefinitions: {
downloadPNG: {
textKey: 'downloadPNG',
onclick: function () {
this.exportChartLocal();
}
},
downloadJPEG: {
textKey: 'downloadJPEG',
onclick: function () {
this.exportChartLocal({
type: 'image/jpeg'
});
}
},
downloadSVG: {
textKey: 'downloadSVG',
onclick: function () {
this.exportChartLocal({
type: 'image/svg+xml'
});
}
},
downloadPDF: {
textKey: 'downloadPDF',
onclick: function () {
this.exportChartLocal({
type: 'application/pdf'
});
}
}
}
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,149 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* Simple polyfills for array functions in old IE browsers (6, 7 and 8) in
* Highcharts v7+. These polyfills are sufficient for Highcharts to work, but
* for fully compatible polyfills, see MDN.
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
/* global document */
'use strict';
/* eslint-disable no-extend-native */
if (!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
};
}
if (!Array.prototype.forEach) {
Array.prototype.forEach = function (fn, thisArg) {
var i = 0, len = this.length;
for (; i < len; i++) {
if (typeof this[i] !== 'undefined' && // added check
fn.call(thisArg, this[i], i, this) === false) {
return i;
}
}
};
}
if (!Array.prototype.map) {
Array.prototype.map = function (fn
// @todo support optional ctx
) {
var results = [], i = 0, len = this.length;
for (; i < len; i++) {
results[i] = fn.call(this[i], this[i], i, this);
}
return results;
};
}
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (member, fromIndex) {
var arr = this, // #8874
len, i = fromIndex || 0; // #8346
if (arr) {
len = arr.length;
for (; i < len; i++) {
if (arr[i] === member) {
return i;
}
}
}
return -1;
};
}
if (!Array.prototype.filter) {
Array.prototype.filter = function (fn
// @todo support optional ctx
) {
var ret = [], i = 0, length = this.length;
for (; i < length; i++) {
if (fn(this[i], i)) {
ret.push(this[i]);
}
}
return ret;
};
}
if (!Array.prototype.some) {
Array.prototype.some = function (fn, thisArg) {
var i = 0, len = this.length;
for (; i < len; i++) {
if (fn.call(thisArg, this[i], i, this) === true) {
return true;
}
}
return false;
};
}
if (!Array.prototype.reduce) {
Array.prototype.reduce = function (func, initialValue) {
var context = this, i = arguments.length > 1 ? 0 : 1, accumulator = arguments.length > 1 ? initialValue : this[0], len = this.length;
for (; i < len; ++i) {
accumulator = func.call(context, accumulator, this[i], i, this);
}
return accumulator;
};
}
if (!Function.prototype.bind) {
Function.prototype.bind = function () {
var thatFunc = this;
var thatArg = arguments[0];
var args = Array.prototype.slice.call(arguments, 1);
if (typeof thatFunc !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - ' +
'what is trying to be bound is not callable');
}
return function () {
var funcArgs = args.concat(Array.prototype.slice.call(arguments));
return thatFunc.apply(thatArg, funcArgs);
};
};
}
if (!Object.keys) {
Object.keys = function (obj) {
var result = [], prop;
for (prop in obj) {
if (Object.hasOwnProperty.call(obj, prop)) {
result.push(prop);
}
}
return result;
};
}
// Add a getElementsByClassName function if the browser doesn't have one
// Limitation: only works with one class name
// Copyright: Eike Send https://eike.se/nd
// License: MIT License
if (!document.getElementsByClassName) {
document.getElementsByClassName = function (search) {
var d = document, elements, pattern, i, results = [];
if (d.querySelectorAll) { // IE8
return d.querySelectorAll('.' + search);
}
if (d.evaluate) { // IE6, IE7
pattern = './/*[contains(concat(\' \', @class, \' \'), \' ' +
search + ' \')]';
elements = d.evaluate(pattern, d, null, 0, null);
while ((i = elements.iterateNext())) {
results.push(i);
}
}
else {
elements = d.getElementsByTagName('*');
pattern = new RegExp('(^|\\s)' + search + '(\\s|$)');
for (i = 0; i < elements.length; i++) {
if (pattern.test(elements[i].className)) {
results.push(elements[i]);
}
}
}
return results;
};
}
@@ -0,0 +1,189 @@
/* *
*
* Highcharts module to hide overlapping data labels.
* This module is included in Highcharts.
*
* (c) 2009-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Chart from '../Core/Chart/Chart.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, fireEvent = U.fireEvent, isArray = U.isArray, isNumber = U.isNumber, objectEach = U.objectEach, pick = U.pick;
/* eslint-disable no-invalid-this */
// Collect potensial overlapping data labels. Stack labels probably don't need
// to be considered because they are usually accompanied by data labels that lie
// inside the columns.
addEvent(Chart, 'render', function collectAndHide() {
var labels = [];
// Consider external label collectors
(this.labelCollectors || []).forEach(function (collector) {
labels = labels.concat(collector());
});
(this.yAxis || []).forEach(function (yAxis) {
if (yAxis.stacking &&
yAxis.options.stackLabels &&
!yAxis.options.stackLabels.allowOverlap) {
objectEach(yAxis.stacking.stacks, function (stack) {
objectEach(stack, function (stackItem) {
labels.push(stackItem.label);
});
});
}
});
(this.series || []).forEach(function (series) {
var dlOptions = series.options.dataLabels;
if (series.visible &&
!(dlOptions.enabled === false && !series._hasPointLabels)) { // #3866
(series.nodes || series.points).forEach(function (point) {
if (point.visible) {
var dataLabels = (isArray(point.dataLabels) ?
point.dataLabels :
(point.dataLabel ? [point.dataLabel] : []));
dataLabels.forEach(function (label) {
var options = label.options;
label.labelrank = pick(options.labelrank, point.labelrank, point.shapeArgs && point.shapeArgs.height); // #4118
if (!options.allowOverlap) {
labels.push(label);
}
});
}
});
}
});
this.hideOverlappingLabels(labels);
});
/**
* Hide overlapping labels. Labels are moved and faded in and out on zoom to
* provide a smooth visual imression.
*
* @private
* @function Highcharts.Chart#hideOverlappingLabels
* @param {Array<Highcharts.SVGElement>} labels
* Rendered data labels
* @requires modules/overlapping-datalabels
*/
Chart.prototype.hideOverlappingLabels = function (labels) {
var chart = this, len = labels.length, ren = chart.renderer, label, i, j, label1, label2, box1, box2, isLabelAffected = false, isIntersectRect = function (box1, box2) {
return !(box2.x >= box1.x + box1.width ||
box2.x + box2.width <= box1.x ||
box2.y >= box1.y + box1.height ||
box2.y + box2.height <= box1.y);
},
// Get the box with its position inside the chart, as opposed to getBBox
// that only reports the position relative to the parent.
getAbsoluteBox = function (label) {
var pos, parent, bBox,
// Substract the padding if no background or border (#4333)
padding = label.box ? 0 : (label.padding || 0), lineHeightCorrection = 0, xOffset = 0, boxWidth, alignValue;
if (label &&
(!label.alignAttr || label.placed)) {
pos = label.alignAttr || {
x: label.attr('x'),
y: label.attr('y')
};
parent = label.parentGroup;
// Get width and height if pure text nodes (stack labels)
if (!label.width) {
bBox = label.getBBox();
label.width = bBox.width;
label.height = bBox.height;
// Labels positions are computed from top left corner, so
// we need to substract the text height from text nodes too.
lineHeightCorrection = ren
.fontMetrics(null, label.element).h;
}
boxWidth = label.width - 2 * padding;
alignValue = {
left: '0',
center: '0.5',
right: '1'
}[label.alignValue];
if (alignValue) {
xOffset = +alignValue * boxWidth;
}
else if (isNumber(label.x) && Math.round(label.x) !== label.translateX) {
xOffset = label.x - label.translateX;
}
return {
x: pos.x + (parent.translateX || 0) + padding -
(xOffset || 0),
y: pos.y + (parent.translateY || 0) + padding -
lineHeightCorrection,
width: label.width - 2 * padding,
height: label.height - 2 * padding
};
}
};
for (i = 0; i < len; i++) {
label = labels[i];
if (label) {
// Mark with initial opacity
label.oldOpacity = label.opacity;
label.newOpacity = 1;
label.absoluteBox = getAbsoluteBox(label);
}
}
// Prevent a situation in a gradually rising slope, that each label will
// hide the previous one because the previous one always has lower rank.
labels.sort(function (a, b) {
return (b.labelrank || 0) - (a.labelrank || 0);
});
// Detect overlapping labels
for (i = 0; i < len; i++) {
label1 = labels[i];
box1 = label1 && label1.absoluteBox;
for (j = i + 1; j < len; ++j) {
label2 = labels[j];
box2 = label2 && label2.absoluteBox;
if (box1 &&
box2 &&
label1 !== label2 && // #6465, polar chart with connectEnds
label1.newOpacity !== 0 &&
label2.newOpacity !== 0) {
if (isIntersectRect(box1, box2)) {
(label1.labelrank < label2.labelrank ? label1 : label2)
.newOpacity = 0;
}
}
}
}
// Hide or show
labels.forEach(function (label) {
var complete, newOpacity;
if (label) {
newOpacity = label.newOpacity;
if (label.oldOpacity !== newOpacity) {
// Make sure the label is completely hidden to avoid catching
// clicks (#4362)
if (label.alignAttr && label.placed) { // data labels
label[newOpacity ? 'removeClass' : 'addClass']('highcharts-data-label-hidden');
complete = function () {
if (!chart.styledMode) {
label.css({ pointerEvents: newOpacity ? 'auto' : 'none' });
}
label.visibility = newOpacity ? 'inherit' : 'hidden';
};
isLabelAffected = true;
// Animate or set the opacity
label.alignAttr.opacity = newOpacity;
label[label.isOld ? 'animate' : 'attr'](label.alignAttr, null, complete);
fireEvent(chart, 'afterHideOverlappingLabel');
}
else { // other labels, tick labels
label.attr({
opacity: newOpacity
});
}
}
label.isOld = true;
}
});
if (isLabelAffected) {
fireEvent(chart, 'afterHideAllOverlappingLabels');
}
};
+402
View File
@@ -0,0 +1,402 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Chart from '../Core/Chart/Chart.js';
import H from '../Core/Globals.js';
import Pointer from '../Core/Pointer.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, extend = U.extend, merge = U.merge, pick = U.pick, splat = U.splat;
/**
* @typedef {"arc"|"circle"|"solid"} Highcharts.PaneBackgroundShapeValue
*/
import centeredSeriesMixin from '../Mixins/CenteredSeries.js';
/* eslint-disable no-invalid-this, valid-jsdoc */
Chart.prototype.collectionsWithUpdate.push('pane');
/**
* The Pane object allows options that are common to a set of X and Y axes.
*
* In the future, this can be extended to basic Highcharts and Highstock.
*
* @private
* @class
* @name Highcharts.Pane
* @param {Highcharts.PaneOptions} options
* @param {Highcharts.Chart} chart
*/
var Pane = /** @class */ (function () {
function Pane(options, chart) {
this.background = void 0;
this.center = void 0;
this.chart = void 0;
this.options = void 0;
this.coll = 'pane'; // Member of chart.pane
/**
* The pane serves as a container for axes and backgrounds for circular
* gauges and polar charts.
*
* @since 2.3.0
* @product highcharts
* @requires highcharts-more
* @optionparent pane
*/
this.defaultOptions = {
/**
* The end angle of the polar X axis or gauge value axis, given in
* degrees where 0 is north. Defaults to [startAngle](#pane.startAngle)
* + 360.
*
* @sample {highcharts} highcharts/demo/gauge-vu-meter/
* VU-meter with custom start and end angle
*
* @type {number}
* @since 2.3.0
* @product highcharts
* @apioption pane.endAngle
*/
/**
* The center of a polar chart or angular gauge, given as an array
* of [x, y] positions. Positions can be given as integers that
* transform to pixels, or as percentages of the plot area size.
*
* @sample {highcharts} highcharts/demo/gauge-vu-meter/
* Two gauges with different center
*
* @type {Array<string|number>}
* @default ["50%", "50%"]
* @since 2.3.0
* @product highcharts
*/
center: ['50%', '50%'],
/**
* The size of the pane, either as a number defining pixels, or a
* percentage defining a percentage of the available plot area (the
* smallest of the plot height or plot width).
*
* @sample {highcharts} highcharts/demo/gauge-vu-meter/
* Smaller size
*
* @type {number|string}
* @product highcharts
*/
size: '85%',
/**
* The inner size of the pane, either as a number defining pixels, or a
* percentage defining a percentage of the pane's size.
*
* @sample {highcharts} highcharts/series-polar/column-inverted-inner
* The inner size set to 20%
*
* @type {number|string}
* @product highcharts
*/
innerSize: '0%',
/**
* The start angle of the polar X axis or gauge axis, given in degrees
* where 0 is north. Defaults to 0.
*
* @sample {highcharts} highcharts/demo/gauge-vu-meter/
* VU-meter with custom start and end angle
*
* @since 2.3.0
* @product highcharts
*/
startAngle: 0
};
/**
* An array of background items for the pane.
*
* @sample {highcharts} highcharts/demo/gauge-speedometer/
* Speedometer gauge with multiple backgrounds
*
* @type {Array<*>}
* @optionparent pane.background
*/
this.defaultBackgroundOptions = {
/**
* The class name for this background.
*
* @sample {highcharts} highcharts/css/pane/
* Panes styled by CSS
* @sample {highstock} highcharts/css/pane/
* Panes styled by CSS
* @sample {highmaps} highcharts/css/pane/
* Panes styled by CSS
*
* @type {string}
* @default highcharts-pane
* @since 5.0.0
* @apioption pane.background.className
*/
/**
* The shape of the pane background. When `solid`, the background
* is circular. When `arc`, the background extends only from the min
* to the max of the value axis.
*
* @type {Highcharts.PaneBackgroundShapeValue}
* @since 2.3.0
* @product highcharts
*/
shape: 'circle',
/**
* The pixel border width of the pane background.
*
* @since 2.3.0
* @product highcharts
*/
borderWidth: 1,
/**
* The pane background border color.
*
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
* @since 2.3.0
* @product highcharts
*/
borderColor: '#cccccc',
/**
* The background color or gradient for the pane.
*
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
* @default { linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, stops: [[0, #ffffff], [1, #e6e6e6]] }
* @since 2.3.0
* @product highcharts
*/
backgroundColor: {
/** @ignore-option */
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
/** @ignore-option */
stops: [
[0, '#ffffff'],
[1, '#e6e6e6']
]
},
/** @ignore-option */
from: -Number.MAX_VALUE,
/**
* The inner radius of the pane background. Can be either numeric
* (pixels) or a percentage string.
*
* @type {number|string}
* @since 2.3.0
* @product highcharts
*/
innerRadius: 0,
/** @ignore-option */
to: Number.MAX_VALUE,
/**
* The outer radius of the circular pane background. Can be either
* numeric (pixels) or a percentage string.
*
* @type {number|string}
* @since 2.3.0
* @product highcharts
*/
outerRadius: '105%'
};
this.init(options, chart);
}
/**
* Initialize the Pane object
*
* @private
* @function Highcharts.Pane#init
*
* @param {Highcharts.PaneOptions} options
*
* @param {Highcharts.Chart} chart
*/
Pane.prototype.init = function (options, chart) {
this.chart = chart;
this.background = [];
chart.pane.push(this);
this.setOptions(options);
};
/**
* @private
* @function Highcharts.Pane#setOptions
*
* @param {Highcharts.PaneOptions} options
*/
Pane.prototype.setOptions = function (options) {
// Set options. Angular charts have a default background (#3318)
this.options = options = merge(this.defaultOptions, this.chart.angular ? { background: {} } : void 0, options);
};
/**
* Render the pane with its backgrounds.
*
* @private
* @function Highcharts.Pane#render
*/
Pane.prototype.render = function () {
var options = this.options, backgroundOption = this.options.background, renderer = this.chart.renderer, len, i;
if (!this.group) {
this.group = renderer.g('pane-group')
.attr({ zIndex: options.zIndex || 0 })
.add();
}
this.updateCenter();
// Render the backgrounds
if (backgroundOption) {
backgroundOption = splat(backgroundOption);
len = Math.max(backgroundOption.length, this.background.length || 0);
for (i = 0; i < len; i++) {
// #6641 - if axis exists, chart is circular and apply
// background
if (backgroundOption[i] && this.axis) {
this.renderBackground(merge(this.defaultBackgroundOptions, backgroundOption[i]), i);
}
else if (this.background[i]) {
this.background[i] = this.background[i].destroy();
this.background.splice(i, 1);
}
}
}
};
/**
* Render an individual pane background.
*
* @private
* @function Highcharts.Pane#renderBackground
*
* @param {Highcharts.PaneBackgroundOptions} backgroundOptions
* Background options
*
* @param {number} i
* The index of the background in this.backgrounds
*/
Pane.prototype.renderBackground = function (backgroundOptions, i) {
var method = 'animate', attribs = {
'class': 'highcharts-pane ' + (backgroundOptions.className || '')
};
if (!this.chart.styledMode) {
extend(attribs, {
'fill': backgroundOptions.backgroundColor,
'stroke': backgroundOptions.borderColor,
'stroke-width': backgroundOptions.borderWidth
});
}
if (!this.background[i]) {
this.background[i] = this.chart.renderer
.path()
.add(this.group);
method = 'attr';
}
this.background[i][method]({
'd': this.axis.getPlotBandPath(backgroundOptions.from, backgroundOptions.to, backgroundOptions)
}).attr(attribs);
};
/**
* Gets the center for the pane and its axis.
*
* @private
* @function Highcharts.Pane#updateCenter
* @param {Highcharts.Axis} [axis]
* @return {void}
*/
Pane.prototype.updateCenter = function (axis) {
this.center = (axis ||
this.axis ||
{}).center = centeredSeriesMixin.getCenter.call(this);
};
/**
* Destroy the pane item
*
* @ignore
* @private
* @function Highcharts.Pane#destroy
* /
destroy: function () {
erase(this.chart.pane, this);
this.background.forEach(function (background) {
background.destroy();
});
this.background.length = 0;
this.group = this.group.destroy();
},
*/
/**
* Update the pane item with new options
*
* @private
* @function Highcharts.Pane#update
* @param {Highcharts.PaneOptions} options
* New pane options
* @param {boolean} [redraw]
* @return {void}
*/
Pane.prototype.update = function (options, redraw) {
merge(true, this.options, options);
merge(true, this.chart.options.pane, options); // #9917
this.setOptions(this.options);
this.render();
this.chart.axes.forEach(function (axis) {
if (axis.pane === this) {
axis.pane = null;
axis.update({}, redraw);
}
}, this);
};
return Pane;
}());
/**
* Check whether element is inside or outside pane.
* @private
* @param {number} x Element's x coordinate
* @param {number} y Element's y coordinate
* @param {Array<number>} center Pane's center (x, y) and diameter
* @return {boolean}
*/
function isInsidePane(x, y, center) {
return Math.sqrt(Math.pow(x - center[0], 2) + Math.pow(y - center[1], 2)) <= center[2] / 2;
}
H.Chart.prototype.getHoverPane = function (eventArgs) {
var chart = this;
var hoverPane;
if (eventArgs) {
chart.pane.forEach(function (pane) {
var plotX = eventArgs.chartX - chart.plotLeft, plotY = eventArgs.chartY - chart.plotTop, x = chart.inverted ? plotY : plotX, y = chart.inverted ? plotX : plotY;
if (isInsidePane(x, y, pane.center)) {
hoverPane = pane;
}
});
}
return hoverPane;
};
addEvent(Chart, 'afterIsInsidePlot', function (e) {
var chart = this;
if (chart.polar) {
e.isInsidePlot = chart.pane.some(function (pane) { return isInsidePane(e.x, e.y, pane.center); });
}
});
addEvent(Pointer, 'beforeGetHoverData', function (eventArgs) {
var chart = this.chart;
if (chart.polar) {
// Find pane we are currently hovering over.
chart.hoverPane = chart.getHoverPane(eventArgs);
// Edit filter method to handle polar
eventArgs.filter = function (s) {
return (s.visible &&
!(!eventArgs.shared && s.directTouch) && // #3821
pick(s.options.enableMouseTracking, true) &&
(!chart.hoverPane || s.xAxis.pane === chart.hoverPane));
};
}
});
addEvent(Pointer, 'afterGetHoverData', function (eventArgs) {
var chart = this.chart;
if (eventArgs.hoverPoint &&
eventArgs.hoverPoint.plotX &&
eventArgs.hoverPoint.plotY &&
chart.hoverPane &&
!isInsidePane(eventArgs.hoverPoint.plotX, eventArgs.hoverPoint.plotY, chart.hoverPane.center)) {
eventArgs.hoverPoint = void 0;
}
});
H.Pane = Pane;
export default H.Pane;
@@ -0,0 +1,453 @@
/* *
*
* Parallel coordinates module
*
* (c) 2010-2020 Pawel Fus
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Axis from '../Core/Axis/Axis.js';
import Chart from '../Core/Chart/Chart.js';
import H from '../Core/Globals.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, arrayMax = U.arrayMax, arrayMin = U.arrayMin, defined = U.defined, erase = U.erase, extend = U.extend, format = U.format, merge = U.merge, pick = U.pick, setOptions = U.setOptions, splat = U.splat, wrap = U.wrap;
import '../Core/Series/Series.js';
// Extensions for parallel coordinates plot.
var ChartProto = Chart.prototype;
var defaultXAxisOptions = {
lineWidth: 0,
tickLength: 0,
opposite: true,
type: 'category'
};
/* eslint-disable valid-jsdoc */
/**
* @optionparent chart
*/
var defaultParallelOptions = {
/**
* Flag to render charts as a parallel coordinates plot. In a parallel
* coordinates plot (||-coords) by default all required yAxes are generated
* and the legend is disabled. This feature requires
* `modules/parallel-coordinates.js`.
*
* @sample {highcharts} /highcharts/demo/parallel-coordinates/
* Parallel coordinates demo
* @sample {highcharts} highcharts/parallel-coordinates/polar/
* Star plot, multivariate data in a polar chart
*
* @since 6.0.0
* @product highcharts
* @requires modules/parallel-coordinates
*/
parallelCoordinates: false,
/**
* Common options for all yAxes rendered in a parallel coordinates plot.
* This feature requires `modules/parallel-coordinates.js`.
*
* The default options are:
* ```js
* parallelAxes: {
* lineWidth: 1, // classic mode only
* gridlinesWidth: 0, // classic mode only
* title: {
* text: '',
* reserveSpace: false
* },
* labels: {
* x: 0,
* y: 0,
* align: 'center',
* reserveSpace: false
* },
* offset: 0
* }
* ```
*
* @sample {highcharts} highcharts/parallel-coordinates/parallelaxes/
* Set the same tickAmount for all yAxes
*
* @extends yAxis
* @since 6.0.0
* @product highcharts
* @excluding alternateGridColor, breaks, id, gridLineColor,
* gridLineDashStyle, gridLineWidth, minorGridLineColor,
* minorGridLineDashStyle, minorGridLineWidth, plotBands,
* plotLines, angle, gridLineInterpolation, maxColor, maxZoom,
* minColor, scrollbar, stackLabels, stops
* @requires modules/parallel-coordinates
*/
parallelAxes: {
lineWidth: 1,
/**
* Titles for yAxes are taken from
* [xAxis.categories](#xAxis.categories). All options for `xAxis.labels`
* applies to parallel coordinates titles. For example, to style
* categories, use [xAxis.labels.style](#xAxis.labels.style).
*
* @excluding align, enabled, margin, offset, position3d, reserveSpace,
* rotation, skew3d, style, text, useHTML, x, y
*/
title: {
text: '',
reserveSpace: false
},
labels: {
x: 0,
y: 4,
align: 'center',
reserveSpace: false
},
offset: 0
}
};
setOptions({
chart: defaultParallelOptions
});
/* eslint-disable no-invalid-this */
// Initialize parallelCoordinates
addEvent(Chart, 'init', function (e) {
var options = e.args[0], defaultYAxis = splat(options.yAxis || {}), newYAxes = [];
var yAxisLength = defaultYAxis.length;
/**
* Flag used in parallel coordinates plot to check if chart has ||-coords
* (parallel coords).
*
* @requires module:modules/parallel-coordinates
*
* @name Highcharts.Chart#hasParallelCoordinates
* @type {boolean}
*/
this.hasParallelCoordinates = options.chart &&
options.chart.parallelCoordinates;
if (this.hasParallelCoordinates) {
this.setParallelInfo(options);
// Push empty yAxes in case user did not define them:
for (; yAxisLength <= this.parallelInfo.counter; yAxisLength++) {
newYAxes.push({});
}
if (!options.legend) {
options.legend = {};
}
if (typeof options.legend.enabled === 'undefined') {
options.legend.enabled = false;
}
merge(true, options,
// Disable boost
{
boost: {
seriesThreshold: Number.MAX_VALUE
},
plotOptions: {
series: {
boostThreshold: Number.MAX_VALUE
}
}
});
options.yAxis = defaultYAxis.concat(newYAxes);
options.xAxis = merge(defaultXAxisOptions, // docs
splat(options.xAxis || {})[0]);
}
});
// Initialize parallelCoordinates
addEvent(Chart, 'update', function (e) {
var options = e.options;
if (options.chart) {
if (defined(options.chart.parallelCoordinates)) {
this.hasParallelCoordinates = options.chart.parallelCoordinates;
}
this.options.chart.parallelAxes = merge(this.options.chart.parallelAxes, options.chart.parallelAxes);
}
if (this.hasParallelCoordinates) {
// (#10081)
if (options.series) {
this.setParallelInfo(options);
}
this.yAxis.forEach(function (axis) {
axis.update({}, false);
});
}
});
/* eslint-disable valid-jsdoc */
extend(ChartProto, /** @lends Highcharts.Chart.prototype */ {
/**
* Define how many parellel axes we have according to the longest dataset.
* This is quite heavy - loop over all series and check series.data.length
* Consider:
*
* - make this an option, so user needs to set this to get better
* performance
*
* - check only first series for number of points and assume the rest is the
* same
*
* @private
* @function Highcharts.Chart#setParallelInfo
* @param {Highcharts.Options} options
* User options
* @return {void}
* @requires modules/parallel-coordinates
*/
setParallelInfo: function (options) {
var chart = this, seriesOptions = options.series;
chart.parallelInfo = {
counter: 0
};
seriesOptions.forEach(function (series) {
if (series.data) {
chart.parallelInfo.counter = Math.max(chart.parallelInfo.counter, series.data.length - 1);
}
});
}
});
// Bind each series to each yAxis. yAxis needs a reference to all series to
// calculate extremes.
addEvent(H.Series, 'bindAxes', function (e) {
if (this.chart.hasParallelCoordinates) {
var series = this;
this.chart.axes.forEach(function (axis) {
series.insert(axis.series);
axis.isDirty = true;
});
series.xAxis = this.chart.xAxis[0];
series.yAxis = this.chart.yAxis[0];
e.preventDefault();
}
});
// Translate each point using corresponding yAxis.
addEvent(H.Series, 'afterTranslate', function () {
var series = this, chart = this.chart, points = series.points, dataLength = points && points.length, closestPointRangePx = Number.MAX_VALUE, lastPlotX, point, i;
if (this.chart.hasParallelCoordinates) {
for (i = 0; i < dataLength; i++) {
point = points[i];
if (defined(point.y)) {
if (chart.polar) {
point.plotX = chart.yAxis[i].angleRad || 0;
}
else if (chart.inverted) {
point.plotX = (chart.plotHeight -
chart.yAxis[i].top +
chart.plotTop);
}
else {
point.plotX = chart.yAxis[i].left - chart.plotLeft;
}
point.clientX = point.plotX;
point.plotY = chart.yAxis[i]
.translate(point.y, false, true, null, true);
if (typeof lastPlotX !== 'undefined') {
closestPointRangePx = Math.min(closestPointRangePx, Math.abs(point.plotX - lastPlotX));
}
lastPlotX = point.plotX;
point.isInside = chart.isInsidePlot(point.plotX, point.plotY, chart.inverted);
}
else {
point.isNull = true;
}
}
this.closestPointRangePx = closestPointRangePx;
}
}, { order: 1 });
// On destroy, we need to remove series from each axis.series
addEvent(H.Series, 'destroy', function () {
if (this.chart.hasParallelCoordinates) {
(this.chart.axes || []).forEach(function (axis) {
if (axis && axis.series) {
erase(axis.series, this);
axis.isDirty = axis.forceRedraw = true;
}
}, this);
}
});
/**
* @private
*/
function addFormattedValue(proceed) {
var chart = this.series && this.series.chart, config = proceed.apply(this, Array.prototype.slice.call(arguments, 1)), formattedValue, yAxisOptions, labelFormat, yAxis;
if (chart &&
chart.hasParallelCoordinates &&
!defined(config.formattedValue)) {
yAxis = chart.yAxis[this.x];
yAxisOptions = yAxis.options;
labelFormat = pick(
/**
* Parallel coordinates only. Format that will be used for point.y
* and available in [tooltip.pointFormat](#tooltip.pointFormat) as
* `{point.formattedValue}`. If not set, `{point.formattedValue}`
* will use other options, in this order:
*
* 1. [yAxis.labels.format](#yAxis.labels.format) will be used if
* set
*
* 2. If yAxis is a category, then category name will be displayed
*
* 3. If yAxis is a datetime, then value will use the same format as
* yAxis labels
*
* 4. If yAxis is linear/logarithmic type, then simple value will be
* used
*
* @sample {highcharts}
* /highcharts/parallel-coordinates/tooltipvalueformat/
* Different tooltipValueFormats's
*
* @type {string}
* @default undefined
* @since 6.0.0
* @product highcharts
* @requires modules/parallel-coordinates
* @apioption yAxis.tooltipValueFormat
*/
yAxisOptions.tooltipValueFormat, yAxisOptions.labels.format);
if (labelFormat) {
formattedValue = format(labelFormat, extend(this, { value: this.y }), chart);
}
else if (yAxis.dateTime) {
formattedValue = chart.time.dateFormat(chart.time.resolveDTLFormat(yAxisOptions.dateTimeLabelFormats[yAxis.tickPositions.info.unitName]).main, this.y);
}
else if (yAxisOptions.categories) {
formattedValue = yAxisOptions.categories[this.y];
}
else {
formattedValue = this.y;
}
config.formattedValue = config.point.formattedValue = formattedValue;
}
return config;
}
['line', 'spline'].forEach(function (seriesName) {
wrap(H.seriesTypes[seriesName].prototype.pointClass.prototype, 'getLabelConfig', addFormattedValue);
});
/**
* Support for parallel axes.
* @private
* @class
*/
var ParallelAxisAdditions = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function ParallelAxisAdditions(axis) {
this.axis = axis;
}
/* *
*
* Functions
*
* */
/**
* Set predefined left+width and top+height (inverted) for yAxes.
* This method modifies options param.
*
* @private
*
* @param {Array<string>} axisPosition
* ['left', 'width', 'height', 'top'] or ['top', 'height', 'width', 'left']
* for an inverted chart.
*
* @param {Highcharts.AxisOptions} options
* Axis options.
*/
ParallelAxisAdditions.prototype.setPosition = function (axisPosition, options) {
var parallel = this, axis = parallel.axis, chart = axis.chart, fraction = ((parallel.position || 0) + 0.5) / (chart.parallelInfo.counter + 1);
if (chart.polar) {
options.angle = 360 * fraction;
}
else {
options[axisPosition[0]] = 100 * fraction + '%';
axis[axisPosition[1]] = options[axisPosition[1]] = 0;
// In case of chart.update(inverted), remove old options:
axis[axisPosition[2]] = options[axisPosition[2]] = null;
axis[axisPosition[3]] = options[axisPosition[3]] = null;
}
};
return ParallelAxisAdditions;
}());
/**
* Axis with parallel support.
* @private
*/
var ParallelAxis;
(function (ParallelAxis) {
/**
* Adds support for parallel axes.
* @private
*/
function compose(AxisClass) {
/* eslint-disable no-invalid-this */
// On update, keep parallel additions.
AxisClass.keepProps.push('parallel');
addEvent(AxisClass, 'init', onInit);
addEvent(AxisClass, 'afterSetOptions', onAfterSetOptions);
addEvent(AxisClass, 'getSeriesExtremes', onGetSeriesExtremes);
}
ParallelAxis.compose = compose;
/**
* Update default options with predefined for a parallel coords.
* @private
*/
function onAfterSetOptions(e) {
var axis = this, chart = axis.chart, parallelCoordinates = axis.parallelCoordinates;
var axisPosition = ['left', 'width', 'height', 'top'];
if (chart.hasParallelCoordinates) {
if (chart.inverted) {
axisPosition = axisPosition.reverse();
}
if (axis.isXAxis) {
axis.options = merge(axis.options, defaultXAxisOptions, e.userOptions);
}
else {
var axisIndex = chart.yAxis.indexOf(axis); // #13608
axis.options = merge(axis.options, axis.chart.options.chart.parallelAxes, e.userOptions);
parallelCoordinates.position = pick(parallelCoordinates.position, axisIndex >= 0 ? axisIndex : chart.yAxis.length);
parallelCoordinates.setPosition(axisPosition, axis.options);
}
}
}
/**
* Each axis should gather extremes from points on a particular position in
* series.data. Not like the default one, which gathers extremes from all
* series bind to this axis. Consider using series.points instead of
* series.yData.
* @private
*/
function onGetSeriesExtremes(e) {
var axis = this;
var chart = axis.chart;
var parallelCoordinates = axis.parallelCoordinates;
if (!parallelCoordinates) {
return;
}
if (chart && chart.hasParallelCoordinates && !axis.isXAxis) {
var index = parallelCoordinates.position, currentPoints = [];
axis.series.forEach(function (series) {
if (series.visible &&
defined(series.yData[index])) {
// We need to use push() beacause of null points
currentPoints.push(series.yData[index]);
}
});
axis.dataMin = arrayMin(currentPoints);
axis.dataMax = arrayMax(currentPoints);
e.preventDefault();
}
}
/**
* Add parallel addition
* @private
*/
function onInit() {
var axis = this;
if (!axis.parallelCoordinates) {
axis.parallelCoordinates = new ParallelAxisAdditions(axis);
}
}
})(ParallelAxis || (ParallelAxis = {}));
ParallelAxis.compose(Axis);
export default ParallelAxis;
@@ -0,0 +1,549 @@
/* *
*
* Module for using patterns or images as point fills.
*
* (c) 2010-2020 Highsoft AS
* Author: Torstein Hønsi, Øystein Moseng
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../Core/Globals.js';
import Point from '../Core/Series/Point.js';
import SVGRenderer from '../Core/Renderer/SVG/SVGRenderer.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, animObject = U.animObject, erase = U.erase, getOptions = U.getOptions, merge = U.merge, pick = U.pick, removeEvent = U.removeEvent, wrap = U.wrap;
import '../Core/Series/Series.js';
/**
* Pattern options
*
* @interface Highcharts.PatternOptionsObject
*/ /**
* Background color for the pattern if a `path` is set (not images).
* @name Highcharts.PatternOptionsObject#backgroundColor
* @type {Highcharts.ColorString}
*/ /**
* URL to an image to use as the pattern.
* @name Highcharts.PatternOptionsObject#image
* @type {string}
*/ /**
* Width of the pattern. For images this is automatically set to the width of
* the element bounding box if not supplied. For non-image patterns the default
* is 32px. Note that automatic resizing of image patterns to fill a bounding
* box dynamically is only supported for patterns with an automatically
* calculated ID.
* @name Highcharts.PatternOptionsObject#width
* @type {number}
*/ /**
* Analogous to pattern.width.
* @name Highcharts.PatternOptionsObject#height
* @type {number}
*/ /**
* For automatically calculated width and height on images, it is possible to
* set an aspect ratio. The image will be zoomed to fill the bounding box,
* maintaining the aspect ratio defined.
* @name Highcharts.PatternOptionsObject#aspectRatio
* @type {number}
*/ /**
* Horizontal offset of the pattern. Defaults to 0.
* @name Highcharts.PatternOptionsObject#x
* @type {number|undefined}
*/ /**
* Vertical offset of the pattern. Defaults to 0.
* @name Highcharts.PatternOptionsObject#y
* @type {number|undefined}
*/ /**
* Either an SVG path as string, or an object. As an object, supply the path
* string in the `path.d` property. Other supported properties are standard SVG
* attributes like `path.stroke` and `path.fill`. If a path is supplied for the
* pattern, the `image` property is ignored.
* @name Highcharts.PatternOptionsObject#path
* @type {string|Highcharts.SVGAttributes}
*/ /**
* SVG `patternTransform` to apply to the entire pattern.
* @name Highcharts.PatternOptionsObject#patternTransform
* @type {string}
* @see [patternTransform demo](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/series/pattern-fill-transform)
*/ /**
* Pattern color, used as default path stroke.
* @name Highcharts.PatternOptionsObject#color
* @type {Highcharts.ColorString}
*/ /**
* Opacity of the pattern as a float value from 0 to 1.
* @name Highcharts.PatternOptionsObject#opacity
* @type {number}
*/ /**
* ID to assign to the pattern. This is automatically computed if not added, and
* identical patterns are reused. To refer to an existing pattern for a
* Highcharts color, use `color: "url(#pattern-id)"`.
* @name Highcharts.PatternOptionsObject#id
* @type {string|undefined}
*/
/**
* Holds a pattern definition.
*
* @sample highcharts/series/pattern-fill-area/
* Define a custom path pattern
* @sample highcharts/series/pattern-fill-pie/
* Default patterns and a custom image pattern
* @sample maps/demo/pattern-fill-map/
* Custom images on map
*
* @example
* // Pattern used as a color option
* color: {
* pattern: {
* path: {
* d: 'M 3 3 L 8 3 L 8 8 Z',
* fill: '#102045'
* },
* width: 12,
* height: 12,
* color: '#907000',
* opacity: 0.5
* }
* }
*
* @interface Highcharts.PatternObject
*/ /**
* Pattern options
* @name Highcharts.PatternObject#pattern
* @type {Highcharts.PatternOptionsObject}
*/ /**
* Animation options for the image pattern loading.
* @name Highcharts.PatternObject#animation
* @type {boolean|Partial<Highcharts.AnimationOptionsObject>|undefined}
*/ /**
* Optionally an index referencing which pattern to use. Highcharts adds
* 10 default patterns to the `Highcharts.patterns` array. Additional
* pattern definitions can be pushed to this array if desired. This option
* is an index into this array.
* @name Highcharts.PatternObject#patternIndex
* @type {number|undefined}
*/
''; // detach doclets above
// Add the predefined patterns
H.patterns = (function () {
var patterns = [], colors = getOptions().colors;
[
'M 0 0 L 10 10 M 9 -1 L 11 1 M -1 9 L 1 11',
'M 0 10 L 10 0 M -1 1 L 1 -1 M 9 11 L 11 9',
'M 3 0 L 3 10 M 8 0 L 8 10',
'M 0 3 L 10 3 M 0 8 L 10 8',
'M 0 3 L 5 3 L 5 0 M 5 10 L 5 7 L 10 7',
'M 3 3 L 8 3 L 8 8 L 3 8 Z',
'M 5 5 m -4 0 a 4 4 0 1 1 8 0 a 4 4 0 1 1 -8 0',
'M 10 3 L 5 3 L 5 0 M 5 10 L 5 7 L 0 7',
'M 2 5 L 5 2 L 8 5 L 5 8 Z',
'M 0 0 L 5 10 L 10 0'
].forEach(function (pattern, i) {
patterns.push({
path: pattern,
color: colors[i],
width: 10,
height: 10
});
});
return patterns;
})();
/**
* Utility function to compute a hash value from an object. Modified Java
* String.hashCode implementation in JS. Use the preSeed parameter to add an
* additional seeding step.
*
* @private
* @function hashFromObject
*
* @param {object} obj
* The javascript object to compute the hash from.
*
* @param {boolean} [preSeed=false]
* Add an optional preSeed stage.
*
* @return {string}
* The computed hash.
*/
function hashFromObject(obj, preSeed) {
var str = JSON.stringify(obj), strLen = str.length || 0, hash = 0, i = 0, char, seedStep;
if (preSeed) {
seedStep = Math.max(Math.floor(strLen / 500), 1);
for (var a = 0; a < strLen; a += seedStep) {
hash += str.charCodeAt(a);
}
hash = hash & hash;
}
for (; i < strLen; ++i) {
char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash.toString(16).replace('-', '1');
}
/**
* Set dimensions on pattern from point. This function will set internal
* pattern._width/_height properties if width and height are not both already
* set. We only do this on image patterns. The _width/_height properties are set
* to the size of the bounding box of the point, optionally taking aspect ratio
* into account. If only one of width or height are supplied as options, the
* undefined option is calculated as above.
*
* @private
* @function Highcharts.Point#calculatePatternDimensions
*
* @param {Highcharts.PatternOptionsObject} pattern
* The pattern to set dimensions on.
*
* @return {void}
*
* @requires modules/pattern-fill
*/
Point.prototype.calculatePatternDimensions = function (pattern) {
if (pattern.width && pattern.height) {
return;
}
var bBox = this.graphic && (this.graphic.getBBox &&
this.graphic.getBBox(true) ||
this.graphic.element &&
this.graphic.element.getBBox()) || {}, shapeArgs = this.shapeArgs;
// Prefer using shapeArgs, as it is animation agnostic
if (shapeArgs) {
bBox.width = shapeArgs.width || bBox.width;
bBox.height = shapeArgs.height || bBox.height;
bBox.x = shapeArgs.x || bBox.x;
bBox.y = shapeArgs.y || bBox.y;
}
// For images we stretch to bounding box
if (pattern.image) {
// If we do not have a bounding box at this point, simply add a defer
// key and pick this up in the fillSetter handler, where the bounding
// box should exist.
if (!bBox.width || !bBox.height) {
pattern._width = 'defer';
pattern._height = 'defer';
return;
}
// Handle aspect ratio filling
if (pattern.aspectRatio) {
bBox.aspectRatio = bBox.width / bBox.height;
if (pattern.aspectRatio > bBox.aspectRatio) {
// Height of bBox will determine width
bBox.aspectWidth = bBox.height * pattern.aspectRatio;
}
else {
// Width of bBox will determine height
bBox.aspectHeight = bBox.width / pattern.aspectRatio;
}
}
// We set the width/height on internal properties to differentiate
// between the options set by a user and by this function.
pattern._width = pattern.width ||
Math.ceil(bBox.aspectWidth || bBox.width);
pattern._height = pattern.height ||
Math.ceil(bBox.aspectHeight || bBox.height);
}
// Set x/y accordingly, centering if using aspect ratio, otherwise adjusting
// so bounding box corner is 0,0 of pattern.
if (!pattern.width) {
pattern._x = pattern.x || 0;
pattern._x += bBox.x - Math.round(bBox.aspectWidth ?
Math.abs(bBox.aspectWidth - bBox.width) / 2 :
0);
}
if (!pattern.height) {
pattern._y = pattern.y || 0;
pattern._y += bBox.y - Math.round(bBox.aspectHeight ?
Math.abs(bBox.aspectHeight - bBox.height) / 2 :
0);
}
};
/* eslint-disable no-invalid-this */
/**
* Add a pattern to the renderer.
*
* @private
* @function Highcharts.SVGRenderer#addPattern
*
* @param {Highcharts.PatternObject} options
* The pattern options.
*
* @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
* The animation options.
*
* @return {Highcharts.SVGElement|undefined}
* The added pattern. Undefined if the pattern already exists.
*
* @requires modules/pattern-fill
*/
SVGRenderer.prototype.addPattern = function (options, animation) {
var pattern, animate = pick(animation, true), animationOptions = animObject(animate), path, defaultSize = 32, width = options.width || options._width || defaultSize, height = (options.height || options._height || defaultSize), color = options.color || '#343434', id = options.id, ren = this, rect = function (fill) {
ren.rect(0, 0, width, height)
.attr({ fill: fill })
.add(pattern);
}, attribs;
if (!id) {
this.idCounter = this.idCounter || 0;
id = 'highcharts-pattern-' + this.idCounter + '-' + (this.chartIndex || 0);
++this.idCounter;
}
if (this.forExport) {
id += '-export';
}
// Do nothing if ID already exists
this.defIds = this.defIds || [];
if (this.defIds.indexOf(id) > -1) {
return;
}
// Store ID in list to avoid duplicates
this.defIds.push(id);
// Calculate pattern element attributes
var attrs = {
id: id,
patternUnits: 'userSpaceOnUse',
patternContentUnits: options.patternContentUnits || 'userSpaceOnUse',
width: width,
height: height,
x: options._x || options.x || 0,
y: options._y || options.y || 0
};
if (options.patternTransform) {
attrs.patternTransform = options.patternTransform;
}
pattern = this.createElement('pattern').attr(attrs).add(this.defs);
// Set id on the SVGRenderer object
pattern.id = id;
// Use an SVG path for the pattern
if (options.path) {
path = options.path;
// The background
if (options.backgroundColor) {
rect(options.backgroundColor);
}
// The pattern
attribs = {
'd': path.d || path
};
if (!this.styledMode) {
attribs.stroke = path.stroke || color;
attribs['stroke-width'] = pick(path.strokeWidth, 2);
attribs.fill = path.fill || 'none';
}
if (path.transform) {
attribs.transform = path.transform;
}
this.createElement('path').attr(attribs).add(pattern);
pattern.color = color;
// Image pattern
}
else if (options.image) {
if (animate) {
this.image(options.image, 0, 0, width, height, function () {
// Onload
this.animate({
opacity: pick(options.opacity, 1)
}, animationOptions);
removeEvent(this.element, 'load');
}).attr({ opacity: 0 }).add(pattern);
}
else {
this.image(options.image, 0, 0, width, height).add(pattern);
}
}
// For non-animated patterns, set opacity now
if (!(options.image && animate) && typeof options.opacity !== 'undefined') {
[].forEach.call(pattern.element.childNodes, function (child) {
child.setAttribute('opacity', options.opacity);
});
}
// Store for future reference
this.patternElements = this.patternElements || {};
this.patternElements[id] = pattern;
return pattern;
};
// Make sure we have a series color
wrap(H.Series.prototype, 'getColor', function (proceed) {
var oldColor = this.options.color;
// Temporarely remove color options to get defaults
if (oldColor &&
oldColor.pattern &&
!oldColor.pattern.color) {
delete this.options.color;
// Get default
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
// Replace with old, but add default color
oldColor.pattern.color =
this.color;
this.color = this.options.color = oldColor;
}
else {
// We have a color, no need to do anything special
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
}
});
// Calculate pattern dimensions on points that have their own pattern.
addEvent(H.Series, 'render', function () {
var isResizing = this.chart.isResizing;
if (this.isDirtyData || isResizing || !this.chart.hasRendered) {
(this.points || []).forEach(function (point) {
var colorOptions = point.options && point.options.color;
if (colorOptions &&
colorOptions.pattern) {
// For most points we want to recalculate the dimensions on
// render, where we have the shape args and bbox. But if we
// are resizing and don't have the shape args, defer it, since
// the bounding box is still not resized.
if (isResizing &&
!(point.shapeArgs &&
point.shapeArgs.width &&
point.shapeArgs.height)) {
colorOptions.pattern._width =
'defer';
colorOptions.pattern._height =
'defer';
}
else {
point.calculatePatternDimensions(colorOptions.pattern);
}
}
});
}
});
// Merge series color options to points
addEvent(Point, 'afterInit', function () {
var point = this, colorOptions = point.options.color;
// Only do this if we have defined a specific color on this point. Otherwise
// we will end up trying to re-add the series color for each point.
if (colorOptions && colorOptions.pattern) {
// Move path definition to object, allows for merge with series path
// definition
if (typeof colorOptions.pattern.path === 'string') {
colorOptions.pattern.path = {
d: colorOptions.pattern.path
};
}
// Merge with series options
point.color = point.options.color = merge(point.series.options.color, colorOptions);
}
});
// Add functionality to SVG renderer to handle patterns as complex colors
addEvent(SVGRenderer, 'complexColor', function (args) {
var color = args.args[0], prop = args.args[1], element = args.args[2], chartIndex = (this.chartIndex || 0);
var pattern = color.pattern, value = '#343434';
// Handle patternIndex
if (typeof color.patternIndex !== 'undefined' && H.patterns) {
pattern = H.patterns[color.patternIndex];
}
// Skip and call default if there is no pattern
if (!pattern) {
return true;
}
// We have a pattern.
if (pattern.image ||
typeof pattern.path === 'string' ||
pattern.path && pattern.path.d) {
// Real pattern. Add it and set the color value to be a reference.
// Force Hash-based IDs for legend items, as they are drawn before
// point render, meaning they are drawn before autocalculated image
// width/heights. We don't want them to highjack the width/height for
// this ID if it is defined by users.
var forceHashId = element.parentNode &&
element.parentNode.getAttribute('class');
forceHashId = forceHashId &&
forceHashId.indexOf('highcharts-legend') > -1;
// If we don't have a width/height yet, handle it. Try faking a point
// and running the algorithm again.
if (pattern._width === 'defer' || pattern._height === 'defer') {
Point.prototype.calculatePatternDimensions.call({ graphic: { element: element } }, pattern);
}
// If we don't have an explicit ID, compute a hash from the
// definition and use that as the ID. This ensures that points with
// the same pattern definition reuse existing pattern elements by
// default. We combine two hashes, the second with an additional
// preSeed algorithm, to minimize collision probability.
if (forceHashId || !pattern.id) {
// Make a copy so we don't accidentally edit options when setting ID
pattern = merge({}, pattern);
pattern.id = 'highcharts-pattern-' + chartIndex + '-' +
hashFromObject(pattern) + hashFromObject(pattern, true);
}
// Add it. This function does nothing if an element with this ID
// already exists.
this.addPattern(pattern, !this.forExport && pick(pattern.animation, this.globalAnimation, { duration: 100 }));
value = "url(" + this.url + "#" + (pattern.id + (this.forExport ? '-export' : '')) + ")";
}
else {
// Not a full pattern definition, just add color
value = pattern.color || value;
}
// Set the fill/stroke prop on the element
element.setAttribute(prop, value);
// Allow the color to be concatenated into tooltips formatters etc.
color.toString = function () {
return value;
};
// Skip default handler
return false;
});
// When animation is used, we have to recalculate pattern dimensions after
// resize, as the bounding boxes are not available until then.
addEvent(H.Chart, 'endResize', function () {
if ((this.renderer && this.renderer.defIds || []).filter(function (id) {
return (id &&
id.indexOf &&
id.indexOf('highcharts-pattern-') === 0);
}).length) {
// We have non-default patterns to fix. Find them by looping through
// all points.
this.series.forEach(function (series) {
series.points.forEach(function (point) {
var colorOptions = point.options && point.options.color;
if (colorOptions &&
colorOptions.pattern) {
colorOptions.pattern._width =
'defer';
colorOptions.pattern._height =
'defer';
}
});
});
// Redraw without animation
this.redraw(false);
}
});
// Add a garbage collector to delete old patterns with autogenerated hashes that
// are no longer being referenced.
addEvent(H.Chart, 'redraw', function () {
var usedIds = {}, renderer = this.renderer,
// Get the autocomputed patterns - these are the ones we might delete
patterns = (renderer.defIds || []).filter(function (pattern) {
return (pattern.indexOf &&
pattern.indexOf('highcharts-pattern-') === 0);
});
if (patterns.length) {
// Look through the DOM for usage of the patterns. This can be points,
// series, tooltips etc.
[].forEach.call(this.renderTo.querySelectorAll('[color^="url("], [fill^="url("], [stroke^="url("]'), function (node) {
var id = node.getAttribute('fill') ||
node.getAttribute('color') ||
node.getAttribute('stroke');
if (id) {
var sanitizedId = id.replace(renderer.url, '').replace('url(#', '').replace(')', '');
usedIds[sanitizedId] = true;
}
});
// Loop through the patterns that exist and see if they are used
patterns.forEach(function (id) {
if (!usedIds[id]) {
// Remove id from used id list
erase(renderer.defIds, id);
// Remove pattern element
if (renderer.patternElements[id]) {
renderer.patternElements[id].destroy();
delete renderer.patternElements[id];
}
}
});
}
});
+724
View File
@@ -0,0 +1,724 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Chart from '../Core/Chart/Chart.js';
import H from '../Core/Globals.js';
import Pane from './Pane.js';
import Pointer from '../Core/Pointer.js';
import SVGRenderer from '../Core/Renderer/SVG/SVGRenderer.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, animObject = U.animObject, defined = U.defined, find = U.find, isNumber = U.isNumber, pick = U.pick, splat = U.splat, uniqueKey = U.uniqueKey, wrap = U.wrap;
import '../Core/Series/Series.js';
// Extensions for polar charts. Additionally, much of the geometry required for
// polar charts is gathered in RadialAxes.js.
var Series = H.Series, seriesTypes = H.seriesTypes, seriesProto = Series.prototype, pointerProto = Pointer.prototype, colProto, arearangeProto;
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* Search a k-d tree by the point angle, used for shared tooltips in polar
* charts
* @private
*/
seriesProto.searchPointByAngle = function (e) {
var series = this, chart = series.chart, xAxis = series.xAxis, center = xAxis.pane.center, plotX = e.chartX - center[0] - chart.plotLeft, plotY = e.chartY - center[1] - chart.plotTop;
return this.searchKDTree({
clientX: 180 + (Math.atan2(plotX, plotY) * (-180 / Math.PI))
});
};
/**
* #6212 Calculate connectors for spline series in polar chart.
* @private
* @param {boolean} calculateNeighbours
* Check if connectors should be calculated for neighbour points as
* well allows short recurence
*/
seriesProto.getConnectors = function (segment, index, calculateNeighbours, connectEnds) {
var i, prevPointInd, nextPointInd, previousPoint, nextPoint, previousX, previousY, nextX, nextY, plotX, plotY, ret,
// 1 means control points midway between points, 2 means 1/3 from
// the point, 3 is 1/4 etc;
smoothing = 1.5, denom = smoothing + 1, leftContX, leftContY, rightContX, rightContY, dLControlPoint, // distance left control point
dRControlPoint, leftContAngle, rightContAngle, jointAngle, addedNumber = connectEnds ? 1 : 0;
// Calculate final index of points depending on the initial index value.
// Because of calculating neighbours, index may be outisde segment
// array.
if (index >= 0 && index <= segment.length - 1) {
i = index;
}
else if (index < 0) {
i = segment.length - 1 + index;
}
else {
i = 0;
}
prevPointInd = (i - 1 < 0) ? segment.length - (1 + addedNumber) : i - 1;
nextPointInd = (i + 1 > segment.length - 1) ? addedNumber : i + 1;
previousPoint = segment[prevPointInd];
nextPoint = segment[nextPointInd];
previousX = previousPoint.plotX;
previousY = previousPoint.plotY;
nextX = nextPoint.plotX;
nextY = nextPoint.plotY;
plotX = segment[i].plotX; // actual point
plotY = segment[i].plotY;
leftContX = (smoothing * plotX + previousX) / denom;
leftContY = (smoothing * plotY + previousY) / denom;
rightContX = (smoothing * plotX + nextX) / denom;
rightContY = (smoothing * plotY + nextY) / denom;
dLControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2));
dRControlPoint = Math.sqrt(Math.pow(rightContX - plotX, 2) + Math.pow(rightContY - plotY, 2));
leftContAngle = Math.atan2(leftContY - plotY, leftContX - plotX);
rightContAngle = Math.atan2(rightContY - plotY, rightContX - plotX);
jointAngle = (Math.PI / 2) + ((leftContAngle + rightContAngle) / 2);
// Ensure the right direction, jointAngle should be in the same quadrant
// as leftContAngle
if (Math.abs(leftContAngle - jointAngle) > Math.PI / 2) {
jointAngle -= Math.PI;
}
// Find the corrected control points for a spline straight through the
// point
leftContX = plotX + Math.cos(jointAngle) * dLControlPoint;
leftContY = plotY + Math.sin(jointAngle) * dLControlPoint;
rightContX = plotX + Math.cos(Math.PI + jointAngle) * dRControlPoint;
rightContY = plotY + Math.sin(Math.PI + jointAngle) * dRControlPoint;
// push current point's connectors into returned object
ret = {
rightContX: rightContX,
rightContY: rightContY,
leftContX: leftContX,
leftContY: leftContY,
plotX: plotX,
plotY: plotY
};
// calculate connectors for previous and next point and push them inside
// returned object
if (calculateNeighbours) {
ret.prevPointCont = this.getConnectors(segment, prevPointInd, false, connectEnds);
}
return ret;
};
/**
* Translate a point's plotX and plotY from the internal angle and radius
* measures to true plotX, plotY coordinates
* @private
*/
seriesProto.toXY = function (point) {
var xy, chart = this.chart, xAxis = this.xAxis, yAxis = this.yAxis, plotX = point.plotX, plotY = point.plotY, series = point.series, inverted = chart.inverted, pointY = point.y, radius = inverted ? plotX : yAxis.len - plotY, clientX;
// Corrected y position of inverted series other than column
if (inverted && series && !series.isRadialBar) {
point.plotY = plotY =
typeof pointY === 'number' ? (yAxis.translate(pointY) || 0) : 0;
}
// Save rectangular plotX, plotY for later computation
point.rectPlotX = plotX;
point.rectPlotY = plotY;
if (yAxis.center) {
radius += yAxis.center[3] / 2;
}
// Find the polar plotX and plotY
xy = inverted ? yAxis.postTranslate(plotY, radius) :
xAxis.postTranslate(plotX, radius);
point.plotX = point.polarPlotX = xy.x - chart.plotLeft;
point.plotY = point.polarPlotY = xy.y - chart.plotTop;
// If shared tooltip, record the angle in degrees in order to align X
// points. Otherwise, use a standard k-d tree to get the nearest point
// in two dimensions.
if (this.kdByAngle) {
clientX = ((plotX / Math.PI * 180) +
xAxis.pane.options.startAngle) % 360;
if (clientX < 0) { // #2665
clientX += 360;
}
point.clientX = clientX;
}
else {
point.clientX = point.plotX;
}
};
if (seriesTypes.spline) {
/**
* Overridden method for calculating a spline from one point to the next
* @private
*/
wrap(seriesTypes.spline.prototype, 'getPointSpline', function (proceed, segment, point, i) {
var ret, connectors;
if (this.chart.polar) {
// moveTo or lineTo
if (!i) {
ret = ['M', point.plotX, point.plotY];
}
else { // curve from last point to this
connectors = this.getConnectors(segment, i, true, this.connectEnds);
ret = [
'C',
connectors.prevPointCont.rightContX,
connectors.prevPointCont.rightContY,
connectors.leftContX,
connectors.leftContY,
connectors.plotX,
connectors.plotY
];
}
}
else {
ret = proceed.call(this, segment, point, i);
}
return ret;
});
// #6430 Areasplinerange series use unwrapped getPointSpline method, so
// we need to set this method again.
if (seriesTypes.areasplinerange) {
seriesTypes.areasplinerange.prototype.getPointSpline =
seriesTypes.spline.prototype.getPointSpline;
}
}
/**
* Extend translate. The plotX and plotY values are computed as if the polar
* chart were a cartesian plane, where plotX denotes the angle in radians
* and (yAxis.len - plotY) is the pixel distance from center.
* @private
*/
addEvent(Series, 'afterTranslate', function () {
var series = this;
var chart = series.chart;
if (chart.polar && series.xAxis) {
// Prepare k-d-tree handling. It searches by angle (clientX) in
// case of shared tooltip, and by two dimensional distance in case
// of non-shared.
series.kdByAngle = chart.tooltip && chart.tooltip.shared;
if (series.kdByAngle) {
series.searchPoint = series.searchPointByAngle;
}
else {
series.options.findNearestPointBy = 'xy';
}
// Postprocess plot coordinates
if (!series.preventPostTranslate) {
var points = series.points;
var i = points.length;
while (i--) {
// Translate plotX, plotY from angle and radius to true plot
// coordinates
series.toXY(points[i]);
// Treat points below Y axis min as null (#10082)
if (!chart.hasParallelCoordinates &&
!series.yAxis.reversed &&
points[i].y < series.yAxis.min) {
points[i].isNull = true;
}
}
}
// Perform clip after render
if (!this.hasClipCircleSetter) {
this.hasClipCircleSetter = !!series.eventsToUnbind.push(addEvent(series, 'afterRender', function () {
var circ;
if (chart.polar) {
// For clipping purposes there is a need for
// coordinates from the absolute center
circ = this.yAxis.pane.center;
if (!this.clipCircle) {
this.clipCircle = chart.renderer.clipCircle(circ[0], circ[1], circ[2] / 2, circ[3] / 2);
}
else {
this.clipCircle.animate({
x: circ[0],
y: circ[1],
r: circ[2] / 2,
innerR: circ[3] / 2
});
}
this.group.clip(this.clipCircle);
this.setClip = H.noop;
}
}));
}
}
}, { order: 2 }); // Run after translation of ||-coords
/**
* Extend getSegmentPath to allow connecting ends across 0 to provide a
* closed circle in line-like series.
* @private
*/
wrap(seriesProto, 'getGraphPath', function (proceed, points) {
var series = this, i, firstValid, popLastPoint;
// Connect the path
if (this.chart.polar) {
points = points || this.points;
// Append first valid point in order to connect the ends
for (i = 0; i < points.length; i++) {
if (!points[i].isNull) {
firstValid = i;
break;
}
}
/**
* Polar charts only. Whether to connect the ends of a line series
* plot across the extremes.
*
* @sample {highcharts} highcharts/plotoptions/line-connectends-false/
* Do not connect
*
* @type {boolean}
* @since 2.3.0
* @product highcharts
* @apioption plotOptions.series.connectEnds
*/
if (this.options.connectEnds !== false &&
typeof firstValid !== 'undefined') {
this.connectEnds = true; // re-used in splines
points.splice(points.length, 0, points[firstValid]);
popLastPoint = true;
}
// For area charts, pseudo points are added to the graph, now we
// need to translate these
points.forEach(function (point) {
if (typeof point.polarPlotY === 'undefined') {
series.toXY(point);
}
});
}
// Run uber method
var ret = proceed.apply(this, [].slice.call(arguments, 1));
// #6212 points.splice method is adding points to an array. In case of
// areaspline getGraphPath method is used two times and in both times
// points are added to an array. That is why points.pop is used, to get
// unmodified points.
if (popLastPoint) {
points.pop();
}
return ret;
});
var polarAnimate = function (proceed, init) {
var series = this, chart = this.chart, animation = this.options.animation, group = this.group, markerGroup = this.markerGroup, center = this.xAxis.center, plotLeft = chart.plotLeft, plotTop = chart.plotTop, attribs, paneInnerR, graphic, shapeArgs, r, innerR;
// Specific animation for polar charts
if (chart.polar) {
if (series.isRadialBar) {
if (!init) {
// Run the pie animation for radial bars
series.startAngleRad = pick(series.translatedThreshold, series.xAxis.startAngleRad);
H.seriesTypes.pie.prototype.animate.call(series, init);
}
}
else {
// Enable animation on polar charts only in SVG. In VML, the scaling
// is different, plus animation would be so slow it would't matter.
if (chart.renderer.isSVG) {
animation = animObject(animation);
// A different animation needed for column like series
if (series.is('column')) {
if (!init) {
paneInnerR = center[3] / 2;
series.points.forEach(function (point) {
graphic = point.graphic;
shapeArgs = point.shapeArgs;
r = shapeArgs && shapeArgs.r;
innerR = shapeArgs && shapeArgs.innerR;
if (graphic && shapeArgs) {
// start values
graphic.attr({
r: paneInnerR,
innerR: paneInnerR
});
// animate
graphic.animate({
r: r,
innerR: innerR
}, series.options.animation);
}
});
}
}
else {
// Initialize the animation
if (init) {
// Scale down the group and place it in the center
attribs = {
translateX: center[0] + plotLeft,
translateY: center[1] + plotTop,
scaleX: 0.001,
scaleY: 0.001
};
group.attr(attribs);
if (markerGroup) {
markerGroup.attr(attribs);
}
// Run the animation
}
else {
attribs = {
translateX: plotLeft,
translateY: plotTop,
scaleX: 1,
scaleY: 1
};
group.animate(attribs, animation);
if (markerGroup) {
markerGroup.animate(attribs, animation);
}
}
}
}
}
// For non-polar charts, revert to the basic animation
}
else {
proceed.call(this, init);
}
};
// Define the animate method for regular series
wrap(seriesProto, 'animate', polarAnimate);
if (seriesTypes.column) {
arearangeProto = seriesTypes.arearange.prototype;
colProto = seriesTypes.column.prototype;
colProto.polarArc = function (low, high, start, end) {
var center = this.xAxis.center, len = this.yAxis.len, paneInnerR = center[3] / 2, r = len - high + paneInnerR, innerR = len - pick(low, len) + paneInnerR;
// Prevent columns from shooting through the pane's center
if (this.yAxis.reversed) {
if (r < 0) {
r = paneInnerR;
}
if (innerR < 0) {
innerR = paneInnerR;
}
}
// Return a new shapeArgs
return {
x: center[0],
y: center[1],
r: r,
innerR: innerR,
start: start,
end: end
};
};
/**
* Define the animate method for columnseries
* @private
*/
wrap(colProto, 'animate', polarAnimate);
/**
* Extend the column prototype's translate method
* @private
*/
wrap(colProto, 'translate', function (proceed) {
var series = this, options = series.options, threshold = options.threshold, stacking = options.stacking, chart = series.chart, xAxis = series.xAxis, yAxis = series.yAxis, reversed = yAxis.reversed, center = yAxis.center, startAngleRad = xAxis.startAngleRad, endAngleRad = xAxis.endAngleRad, visibleRange = endAngleRad - startAngleRad, thresholdAngleRad, points, point, i, yMin, yMax, start, end, tooltipPos, pointX, pointY, stackValues, stack, barX, innerR, r;
series.preventPostTranslate = true;
// Run uber method
proceed.call(series);
// Postprocess plot coordinates
if (xAxis.isRadial) {
points = series.points;
i = points.length;
yMin = yAxis.translate(yAxis.min);
yMax = yAxis.translate(yAxis.max);
threshold = options.threshold || 0;
if (chart.inverted) {
// Finding a correct threshold
if (isNumber(threshold)) {
thresholdAngleRad = yAxis.translate(threshold);
// Checks if threshold is outside the visible range
if (defined(thresholdAngleRad)) {
if (thresholdAngleRad < 0) {
thresholdAngleRad = 0;
}
else if (thresholdAngleRad > visibleRange) {
thresholdAngleRad = visibleRange;
}
// Adding start angle offset
series.translatedThreshold =
thresholdAngleRad + startAngleRad;
}
}
}
while (i--) {
point = points[i];
barX = point.barX;
pointX = point.x;
pointY = point.y;
point.shapeType = 'arc';
if (chart.inverted) {
point.plotY = yAxis.translate(pointY);
if (stacking && yAxis.stacking) {
stack = yAxis.stacking.stacks[(pointY < 0 ? '-' : '') +
series.stackKey];
if (series.visible && stack && stack[pointX]) {
if (!point.isNull) {
stackValues = stack[pointX].points[series.getStackIndicator(void 0, pointX, series.index).key];
// Translating to radial values
start = yAxis.translate(stackValues[0]);
end = yAxis.translate(stackValues[1]);
// If starting point is beyond the
// range, set it to 0
if (defined(start)) {
start = U.clamp(start, 0, visibleRange);
}
}
}
}
else {
// Initial start and end angles for radial bar
start = thresholdAngleRad;
end = point.plotY;
}
if (start > end) {
// Swapping start and end
end = [start, start = end][0];
}
// Prevent from rendering point outside the
// acceptable circular range
if (!reversed) {
if (start < yMin) {
start = yMin;
}
else if (end > yMax) {
end = yMax;
}
else if (end < yMin || start > yMax) {
start = end = 0;
}
}
else {
if (end > yMin) {
end = yMin;
}
else if (start < yMax) {
start = yMax;
}
else if (start > yMin || end < yMax) {
start = end = visibleRange;
}
}
if (yAxis.min > yAxis.max) {
start = end = reversed ? visibleRange : 0;
}
start += startAngleRad;
end += startAngleRad;
if (center) {
point.barX = barX += center[3] / 2;
}
// In case when radius, inner radius or both are
// negative, a point is rendered but partially or as
// a center point
innerR = Math.max(barX, 0);
r = Math.max(barX + point.pointWidth, 0);
point.shapeArgs = {
x: center && center[0],
y: center && center[1],
r: r,
innerR: innerR,
start: start,
end: end
};
// Fade out the points if not inside the polar "plot area"
point.opacity = start === end ? 0 : void 0;
// A correct value for stacked or not fully visible
// point
point.plotY = (defined(series.translatedThreshold) &&
(start < series.translatedThreshold ? start : end)) -
startAngleRad;
}
else {
start = barX + startAngleRad;
// Changed the way polar columns are drawn in order to make
// it more consistent with the drawing of inverted columns
// (they are using the same function now). Also, it was
// essential to make the animation work correctly (the
// scaling of the group) is replaced by animating each
// element separately.
point.shapeArgs = series.polarArc(point.yBottom, point.plotY, start, start + point.pointWidth);
}
// Provided a correct coordinates for the tooltip
series.toXY(point);
if (chart.inverted) {
tooltipPos = yAxis.postTranslate(point.rectPlotY, barX + point.pointWidth / 2);
point.tooltipPos = [
tooltipPos.x - chart.plotLeft,
tooltipPos.y - chart.plotTop
];
}
else {
point.tooltipPos = [point.plotX, point.plotY];
}
if (center) {
point.ttBelow = point.plotY > center[1];
}
}
}
});
/**
* Find correct align and vertical align based on an angle in polar chart
* @private
*/
colProto.findAlignments = function (angle, options) {
var align, verticalAlign;
if (options.align === null) {
if (angle > 20 && angle < 160) {
align = 'left'; // right hemisphere
}
else if (angle > 200 && angle < 340) {
align = 'right'; // left hemisphere
}
else {
align = 'center'; // top or bottom
}
options.align = align;
}
if (options.verticalAlign === null) {
if (angle < 45 || angle > 315) {
verticalAlign = 'bottom'; // top part
}
else if (angle > 135 && angle < 225) {
verticalAlign = 'top'; // bottom part
}
else {
verticalAlign = 'middle'; // left or right
}
options.verticalAlign = verticalAlign;
}
return options;
};
if (arearangeProto) {
arearangeProto.findAlignments = colProto.findAlignments;
}
/**
* Align column data labels outside the columns. #1199.
* @private
*/
wrap(colProto, 'alignDataLabel', function (proceed, point, dataLabel, options, alignTo, isNew) {
var chart = this.chart, inside = pick(options.inside, !!this.options.stacking), angle, shapeArgs, labelPos;
if (chart.polar) {
angle = point.rectPlotX / Math.PI * 180;
if (!chart.inverted) {
// Align nicely outside the perimeter of the columns
if (this.findAlignments) {
options = this.findAlignments(angle, options);
}
}
else { // Required corrections for data labels of inverted bars
// The plotX and plotY are correctly set therefore they
// don't need to be swapped (inverted argument is false)
this.forceDL = chart.isInsidePlot(point.plotX, Math.round(point.plotY), false);
// Checks if labels should be positioned inside
if (inside && point.shapeArgs) {
shapeArgs = point.shapeArgs;
// Calculates pixel positions for a data label to be
// inside
labelPos =
this.yAxis.postTranslate(
// angle
(shapeArgs.start + shapeArgs.end) / 2 -
this
.xAxis.startAngleRad,
// radius
point.barX +
point.pointWidth / 2);
alignTo = {
x: labelPos.x - chart.plotLeft,
y: labelPos.y - chart.plotTop
};
}
else if (point.tooltipPos) {
alignTo = {
x: point.tooltipPos[0],
y: point.tooltipPos[1]
};
}
options.align = pick(options.align, 'center');
options.verticalAlign =
pick(options.verticalAlign, 'middle');
}
seriesProto.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
// Hide label of a point (only inverted) that is outside the
// visible y range
if (this.isRadialBar && point.shapeArgs &&
point.shapeArgs.start === point.shapeArgs.end) {
dataLabel.hide(true);
}
}
else {
proceed.call(this, point, dataLabel, options, alignTo, isNew);
}
});
}
/**
* Extend getCoordinates to prepare for polar axis values
* @private
*/
wrap(pointerProto, 'getCoordinates', function (proceed, e) {
var chart = this.chart, ret = {
xAxis: [],
yAxis: []
};
if (chart.polar) {
chart.axes.forEach(function (axis) {
var isXAxis = axis.isXAxis, center = axis.center, x, y;
// Skip colorAxis
if (axis.coll === 'colorAxis') {
return;
}
x = e.chartX - center[0] - chart.plotLeft;
y = e.chartY - center[1] - chart.plotTop;
ret[isXAxis ? 'xAxis' : 'yAxis'].push({
axis: axis,
value: axis.translate(isXAxis ?
Math.PI - Math.atan2(x, y) : // angle
// distance from center
Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), true)
});
});
}
else {
ret = proceed.call(this, e);
}
return ret;
});
SVGRenderer.prototype.clipCircle = function (x, y, r, innerR) {
var wrapper, id = uniqueKey(), clipPath = this.createElement('clipPath').attr({
id: id
}).add(this.defs);
wrapper = innerR ?
this.arc(x, y, r, innerR, 0, 2 * Math.PI).add(clipPath) :
this.circle(x, y, r).add(clipPath);
wrapper.id = id;
wrapper.clipPath = clipPath;
return wrapper;
};
addEvent(Chart, 'getAxes', function () {
if (!this.pane) {
this.pane = [];
}
splat(this.options.pane).forEach(function (paneOptions) {
new Pane(// eslint-disable-line no-new
paneOptions, this);
}, this);
});
addEvent(Chart, 'afterDrawChartBox', function () {
this.pane.forEach(function (pane) {
pane.render();
});
});
addEvent(H.Series, 'afterInit', function () {
var chart = this.chart;
// Add flags that identifies radial inverted series
if (chart.inverted && chart.polar) {
this.isRadialSeries = true;
if (this.is('column')) {
this.isRadialBar = true;
}
}
});
/**
* Extend chart.get to also search in panes. Used internally in
* responsiveness and chart.update.
* @private
*/
wrap(Chart.prototype, 'get', function (proceed, id) {
return find(this.pane, function (pane) {
return pane.options.id === id;
}) || proceed.call(this, id);
});
@@ -0,0 +1,126 @@
/**
* (c) 2009-2020 Sebastian Bochann
*
* Price indicator for Highcharts
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*/
'use strict';
import H from '../Core/Globals.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, isArray = U.isArray, merge = U.merge;
/**
* The line marks the last price from visible range of points.
*
* @sample {highstock} stock/indicators/last-visible-price
* Last visible price
*
* @declare Highcharts.SeriesLastVisiblePriceOptionsObject
* @product highstock
* @requires modules/price-indicator
* @apioption plotOptions.series.lastVisiblePrice
*/
/**
* Enable or disable the indicator.
*
* @type {boolean}
* @product highstock
* @default true
* @apioption plotOptions.series.lastVisiblePrice.enabled
*/
/**
* @declare Highcharts.SeriesLastVisiblePriceLabelOptionsObject
* @apioption plotOptions.series.lastVisiblePrice.label
*/
/**
* Enable or disable the label.
*
* @type {boolean}
* @product highstock
* @default true
* @apioption plotOptions.series.lastVisiblePrice.label.enabled
*
*/
/**
* The line marks the last price from all points.
*
* @sample {highstock} stock/indicators/last-price
* Last price
*
* @declare Highcharts.SeriesLastPriceOptionsObject
* @product highstock
* @requires modules/price-indicator
* @apioption plotOptions.series.lastPrice
*/
/**
* Enable or disable the indicator.
*
* @type {boolean}
* @product highstock
* @default true
* @apioption plotOptions.series.lastPrice.enabled
*/
/**
* The color of the line of last price.
*
* @type {string}
* @product highstock
* @default red
* @apioption plotOptions.series.lastPrice.color
*
*/
/* eslint-disable no-invalid-this */
addEvent(H.Series, 'afterRender', function () {
var serie = this, seriesOptions = serie.options, pointRange = seriesOptions.pointRange, lastVisiblePrice = seriesOptions.lastVisiblePrice, lastPrice = seriesOptions.lastPrice;
if ((lastVisiblePrice || lastPrice) &&
seriesOptions.id !== 'highcharts-navigator-series') {
var xAxis = serie.xAxis, yAxis = serie.yAxis, origOptions = yAxis.crosshair, origGraphic = yAxis.cross, origLabel = yAxis.crossLabel, points = serie.points, yLength = serie.yData.length, pLength = points.length, x = serie.xData[serie.xData.length - 1], y = serie.yData[yLength - 1], lastPoint, yValue, crop;
if (lastPrice && lastPrice.enabled) {
yAxis.crosshair = yAxis.options.crosshair = seriesOptions.lastPrice;
yAxis.cross = serie.lastPrice;
yValue = isArray(y) ? y[3] : y;
yAxis.drawCrosshair(null, ({
x: x,
y: yValue,
plotX: xAxis.toPixels(x, true),
plotY: yAxis.toPixels(yValue, true)
}));
// Save price
if (serie.yAxis.cross) {
serie.lastPrice = serie.yAxis.cross;
serie.lastPrice.y = yValue;
}
}
if (lastVisiblePrice &&
lastVisiblePrice.enabled &&
pLength > 0) {
crop = (points[pLength - 1].x === x) || pointRange === null ? 1 : 2;
yAxis.crosshair = yAxis.options.crosshair = merge({
color: 'transparent'
}, seriesOptions.lastVisiblePrice);
yAxis.cross = serie.lastVisiblePrice;
lastPoint = points[pLength - crop];
if (serie.crossLabel) {
serie.crossLabel.destroy();
// Set to undefined to avoid collision with
// the yAxis crosshair #11480
delete yAxis.crossLabel;
}
// Save price
yAxis.drawCrosshair(null, lastPoint);
if (yAxis.cross) {
serie.lastVisiblePrice = yAxis.cross;
if (typeof lastPoint.y === 'number') {
serie.lastVisiblePrice.y = lastPoint.y;
}
}
serie.crossLabel = yAxis.crossLabel;
}
// Restore crosshair:
yAxis.crosshair = origOptions;
yAxis.cross = origGraphic;
yAxis.crossLabel = origLabel;
}
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,365 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Highcharts feature to make the Y axis stay fixed when scrolling the chart
* horizontally on mobile devices. Supports left and right side axes.
*/
/*
WIP on vertical scrollable plot area (#9378). To do:
- Bottom axis positioning
- Test with Gantt
- Look for size optimizing the code
- API and demos
*/
'use strict';
import Chart from '../Core/Chart/Chart.js';
import H from '../Core/Globals.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, createElement = U.createElement, pick = U.pick, stop = U.stop;
/**
* Options for a scrollable plot area. This feature provides a minimum size for
* the plot area of the chart. If the size gets smaller than this, typically
* on mobile devices, a native browser scrollbar is presented. This scrollbar
* provides smooth scrolling for the contents of the plot area, whereas the
* title, legend and unaffected axes are fixed.
*
* Since v7.1.2, a scrollable plot area can be defined for either horizontal or
* vertical scrolling, depending on whether the `minWidth` or `minHeight`
* option is set.
*
* @sample highcharts/chart/scrollable-plotarea
* Scrollable plot area
* @sample highcharts/chart/scrollable-plotarea-vertical
* Vertically scrollable plot area
* @sample {gantt} highcharts/chart/scrollable-plotarea-vertical
* Gantt chart with vertically scrollable plot area
*
* @since 6.1.0
* @product highcharts gantt
* @apioption chart.scrollablePlotArea
*/
/**
* The minimum height for the plot area. If it gets smaller than this, the plot
* area will become scrollable.
*
* @type {number}
* @apioption chart.scrollablePlotArea.minHeight
*/
/**
* The minimum width for the plot area. If it gets smaller than this, the plot
* area will become scrollable.
*
* @type {number}
* @apioption chart.scrollablePlotArea.minWidth
*/
/**
* The initial scrolling position of the scrollable plot area. Ranges from 0 to
* 1, where 0 aligns the plot area to the left and 1 aligns it to the right.
* Typically we would use 1 if the chart has right aligned Y axes.
*
* @type {number}
* @apioption chart.scrollablePlotArea.scrollPositionX
*/
/**
* The initial scrolling position of the scrollable plot area. Ranges from 0 to
* 1, where 0 aligns the plot area to the top and 1 aligns it to the bottom.
*
* @type {number}
* @apioption chart.scrollablePlotArea.scrollPositionY
*/
/**
* The opacity of mask applied on one of the sides of the plot
* area.
*
* @sample {highcharts} highcharts/chart/scrollable-plotarea-opacity
* Disabled opacity for the mask
*
* @type {number}
* @default 0.85
* @since 7.1.1
* @apioption chart.scrollablePlotArea.opacity
*/
''; // detach API doclets
/* eslint-disable no-invalid-this, valid-jsdoc */
addEvent(Chart, 'afterSetChartSize', function (e) {
var scrollablePlotArea = this.options.chart.scrollablePlotArea, scrollableMinWidth = scrollablePlotArea && scrollablePlotArea.minWidth, scrollableMinHeight = scrollablePlotArea && scrollablePlotArea.minHeight, scrollablePixelsX, scrollablePixelsY, corrections;
if (!this.renderer.forExport) {
// The amount of pixels to scroll, the difference between chart
// width and scrollable width
if (scrollableMinWidth) {
this.scrollablePixelsX = scrollablePixelsX = Math.max(0, scrollableMinWidth - this.chartWidth);
if (scrollablePixelsX) {
this.plotWidth += scrollablePixelsX;
if (this.inverted) {
this.clipBox.height += scrollablePixelsX;
this.plotBox.height += scrollablePixelsX;
}
else {
this.clipBox.width += scrollablePixelsX;
this.plotBox.width += scrollablePixelsX;
}
corrections = {
// Corrections for right side
1: { name: 'right', value: scrollablePixelsX }
};
}
// Currently we can only do either X or Y
}
else if (scrollableMinHeight) {
this.scrollablePixelsY = scrollablePixelsY = Math.max(0, scrollableMinHeight - this.chartHeight);
if (scrollablePixelsY) {
this.plotHeight += scrollablePixelsY;
if (this.inverted) {
this.clipBox.width += scrollablePixelsY;
this.plotBox.width += scrollablePixelsY;
}
else {
this.clipBox.height += scrollablePixelsY;
this.plotBox.height += scrollablePixelsY;
}
corrections = {
2: { name: 'bottom', value: scrollablePixelsY }
};
}
}
if (corrections && !e.skipAxes) {
this.axes.forEach(function (axis) {
// For right and bottom axes, only fix the plot line length
if (corrections[axis.side]) {
// Get the plot lines right in getPlotLinePath,
// temporarily set it to the adjusted plot width.
axis.getPlotLinePath = function () {
var marginName = corrections[axis.side].name, correctionValue = corrections[axis.side].value,
// axis.right or axis.bottom
margin = this[marginName], path;
// Temporarily adjust
this[marginName] = margin - correctionValue;
path = H.Axis.prototype.getPlotLinePath.apply(this, arguments);
// Reset
this[marginName] = margin;
return path;
};
}
else {
// Apply the corrected plotWidth
axis.setAxisSize();
axis.setAxisTranslation();
}
});
}
}
});
addEvent(Chart, 'render', function () {
if (this.scrollablePixelsX || this.scrollablePixelsY) {
if (this.setUpScrolling) {
this.setUpScrolling();
}
this.applyFixed();
}
else if (this.fixedDiv) { // Has been in scrollable mode
this.applyFixed();
}
});
/**
* @private
* @function Highcharts.Chart#setUpScrolling
* @return {void}
*/
Chart.prototype.setUpScrolling = function () {
var _this = this;
var attribs = {
WebkitOverflowScrolling: 'touch',
overflowX: 'hidden',
overflowY: 'hidden'
};
if (this.scrollablePixelsX) {
attribs.overflowX = 'auto';
}
if (this.scrollablePixelsY) {
attribs.overflowY = 'auto';
}
// Insert a container with position relative
// that scrolling and fixed container renders to (#10555)
this.scrollingParent = createElement('div', {
className: 'highcharts-scrolling-parent'
}, {
position: 'relative'
}, this.renderTo);
// Add the necessary divs to provide scrolling
this.scrollingContainer = createElement('div', {
'className': 'highcharts-scrolling'
}, attribs, this.scrollingParent);
// On scroll, reset the chart position because it applies to the scrolled
// container
addEvent(this.scrollingContainer, 'scroll', function () {
if (_this.pointer) {
delete _this.pointer.chartPosition;
}
});
this.innerContainer = createElement('div', {
'className': 'highcharts-inner-container'
}, null, this.scrollingContainer);
// Now move the container inside
this.innerContainer.appendChild(this.container);
// Don't run again
this.setUpScrolling = null;
};
/**
* These elements are moved over to the fixed renderer and stay fixed when the
* user scrolls the chart
* @private
*/
Chart.prototype.moveFixedElements = function () {
var container = this.container, fixedRenderer = this.fixedRenderer, fixedSelectors = [
'.highcharts-contextbutton',
'.highcharts-credits',
'.highcharts-legend',
'.highcharts-legend-checkbox',
'.highcharts-navigator-series',
'.highcharts-navigator-xaxis',
'.highcharts-navigator-yaxis',
'.highcharts-navigator',
'.highcharts-reset-zoom',
'.highcharts-scrollbar',
'.highcharts-subtitle',
'.highcharts-title'
], axisClass;
if (this.scrollablePixelsX && !this.inverted) {
axisClass = '.highcharts-yaxis';
}
else if (this.scrollablePixelsX && this.inverted) {
axisClass = '.highcharts-xaxis';
}
else if (this.scrollablePixelsY && !this.inverted) {
axisClass = '.highcharts-xaxis';
}
else if (this.scrollablePixelsY && this.inverted) {
axisClass = '.highcharts-yaxis';
}
fixedSelectors.push(axisClass, axisClass + '-labels');
fixedSelectors.forEach(function (className) {
[].forEach.call(container.querySelectorAll(className), function (elem) {
(elem.namespaceURI === fixedRenderer.SVG_NS ?
fixedRenderer.box :
fixedRenderer.box.parentNode).appendChild(elem);
elem.style.pointerEvents = 'auto';
});
});
};
/**
* @private
* @function Highcharts.Chart#applyFixed
* @return {void}
*/
Chart.prototype.applyFixed = function () {
var _a, _b;
var fixedRenderer, scrollableWidth, scrollableHeight, firstTime = !this.fixedDiv, scrollableOptions = this.options.chart.scrollablePlotArea;
// First render
if (firstTime) {
this.fixedDiv = createElement('div', {
className: 'highcharts-fixed'
}, {
position: 'absolute',
overflow: 'hidden',
pointerEvents: 'none',
zIndex: 2,
top: 0
}, null, true);
(_a = this.scrollingContainer) === null || _a === void 0 ? void 0 : _a.parentNode.insertBefore(this.fixedDiv, this.scrollingContainer);
this.renderTo.style.overflow = 'visible';
this.fixedRenderer = fixedRenderer = new H.Renderer(this.fixedDiv, this.chartWidth, this.chartHeight, (_b = this.options.chart) === null || _b === void 0 ? void 0 : _b.style);
// Mask
this.scrollableMask = fixedRenderer
.path()
.attr({
fill: this.options.chart.backgroundColor || '#fff',
'fill-opacity': pick(scrollableOptions.opacity, 0.85),
zIndex: -1
})
.addClass('highcharts-scrollable-mask')
.add();
this.moveFixedElements();
addEvent(this, 'afterShowResetZoom', this.moveFixedElements);
addEvent(this, 'afterLayOutTitles', this.moveFixedElements);
}
else {
// Set the size of the fixed renderer to the visible width
this.fixedRenderer.setSize(this.chartWidth, this.chartHeight);
}
// Increase the size of the scrollable renderer and background
scrollableWidth = this.chartWidth + (this.scrollablePixelsX || 0);
scrollableHeight = this.chartHeight + (this.scrollablePixelsY || 0);
stop(this.container);
this.container.style.width = scrollableWidth + 'px';
this.container.style.height = scrollableHeight + 'px';
this.renderer.boxWrapper.attr({
width: scrollableWidth,
height: scrollableHeight,
viewBox: [0, 0, scrollableWidth, scrollableHeight].join(' ')
});
this.chartBackground.attr({
width: scrollableWidth,
height: scrollableHeight
});
this.scrollingContainer.style.height = this.chartHeight + 'px';
// Set scroll position
if (firstTime) {
if (scrollableOptions.scrollPositionX) {
this.scrollingContainer.scrollLeft =
this.scrollablePixelsX *
scrollableOptions.scrollPositionX;
}
if (scrollableOptions.scrollPositionY) {
this.scrollingContainer.scrollTop =
this.scrollablePixelsY *
scrollableOptions.scrollPositionY;
}
}
// Mask behind the left and right side
var axisOffset = this.axisOffset, maskTop = this.plotTop - axisOffset[0] - 1, maskLeft = this.plotLeft - axisOffset[3] - 1, maskBottom = this.plotTop + this.plotHeight + axisOffset[2] + 1, maskRight = this.plotLeft + this.plotWidth + axisOffset[1] + 1, maskPlotRight = this.plotLeft + this.plotWidth -
(this.scrollablePixelsX || 0), maskPlotBottom = this.plotTop + this.plotHeight -
(this.scrollablePixelsY || 0), d;
if (this.scrollablePixelsX) {
d = [
// Left side
['M', 0, maskTop],
['L', this.plotLeft - 1, maskTop],
['L', this.plotLeft - 1, maskBottom],
['L', 0, maskBottom],
['Z'],
// Right side
['M', maskPlotRight, maskTop],
['L', this.chartWidth, maskTop],
['L', this.chartWidth, maskBottom],
['L', maskPlotRight, maskBottom],
['Z']
];
}
else if (this.scrollablePixelsY) {
d = [
// Top side
['M', maskLeft, 0],
['L', maskLeft, this.plotTop - 1],
['L', maskRight, this.plotTop - 1],
['L', maskRight, 0],
['Z'],
// Bottom side
['M', maskLeft, maskPlotBottom],
['L', maskLeft, this.chartHeight],
['L', maskRight, this.chartHeight],
['L', maskRight, maskPlotBottom],
['Z']
];
}
else {
d = [['M', 0, 0]];
}
if (this.redrawTrigger !== 'adjustHeight') {
this.scrollableMask.attr({ d: d });
}
};
@@ -0,0 +1,757 @@
/* *
*
* (c) 2009-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Chart from '../Core/Chart/Chart.js';
import H from '../Core/Globals.js';
import SVGRenderer from '../Core/Renderer/SVG/SVGRenderer.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, animObject = U.animObject, extend = U.extend, fireEvent = U.fireEvent, format = U.format, isNumber = U.isNumber, pick = U.pick, setOptions = U.setOptions, syncTimeout = U.syncTimeout;
/**
* Containing the position of a box that should be avoided by labels.
*
* @interface Highcharts.LabelIntersectBoxObject
*/ /**
* @name Highcharts.LabelIntersectBoxObject#bottom
* @type {number}
*/ /**
* @name Highcharts.LabelIntersectBoxObject#left
* @type {number}
*/ /**
* @name Highcharts.LabelIntersectBoxObject#right
* @type {number}
*/ /**
* @name Highcharts.LabelIntersectBoxObject#top
* @type {number}
*/
/*
* Highcharts module to place labels next to a series in a natural position.
*
* TODO:
* - add column support (box collision detection, boxesToAvoid logic)
* - avoid data labels, when data labels above, show series label below.
* - add more options (connector, format, formatter)
*
* https://jsfiddle.net/highcharts/L2u9rpwr/
* https://jsfiddle.net/highcharts/y5A37/
* https://jsfiddle.net/highcharts/264Nm/
* https://jsfiddle.net/highcharts/y5A37/
*/
''; // detach doclets above
import '../Core/Series/Series.js';
var labelDistance = 3, Series = H.Series;
setOptions({
/**
* @optionparent plotOptions
*
* @private
*/
plotOptions: {
series: {
/**
* Series labels are placed as close to the series as possible in a
* natural way, seeking to avoid other series. The goal of this
* feature is to make the chart more easily readable, like if a
* human designer placed the labels in the optimal position.
*
* The series labels currently work with series types having a
* `graph` or an `area`.
*
* @sample highcharts/series-label/line-chart
* Line chart
* @sample highcharts/demo/streamgraph
* Stream graph
* @sample highcharts/series-label/stock-chart
* Stock chart
*
* @declare Highcharts.SeriesLabelOptionsObject
* @since 6.0.0
* @product highcharts highstock gantt
* @requires modules/series-label
*/
label: {
/**
* Enable the series label per series.
*/
enabled: true,
/**
* Allow labels to be placed distant to the graph if necessary,
* and draw a connector line to the graph. Setting this option
* to true may decrease the performance significantly, since the
* algorithm with systematically search for open spaces in the
* whole plot area. Visually, it may also result in a more
* cluttered chart, though more of the series will be labeled.
*/
connectorAllowed: false,
/**
* If the label is closer than this to a neighbour graph, draw a
* connector.
*/
connectorNeighbourDistance: 24,
/**
* A format string for the label, with support for a subset of
* HTML. Variables are enclosed by curly brackets. Available
* variables are `name`, `options.xxx`, `color` and other
* members from the `series` object. Use this option also to set
* a static text for the label.
*
* @type string
* @since 8.1.0
*/
format: void 0,
/**
* Callback function to format each of the series' labels. The
* `this` keyword refers to the series object. By default the
* `formatter` is undefined and the `series.name` is rendered.
*
* @type {Highcharts.FormatterCallbackFunction<Series>}
* @since 8.1.0
*/
formatter: void 0,
/**
* For area-like series, allow the font size to vary so that
* small areas get a smaller font size. The default applies this
* effect to area-like series but not line-like series.
*
* @type {number|null}
*/
minFontSize: null,
/**
* For area-like series, allow the font size to vary so that
* small areas get a smaller font size. The default applies this
* effect to area-like series but not line-like series.
*
* @type {number|null}
*/
maxFontSize: null,
/**
* Draw the label on the area of an area series. By default it
* is drawn on the area. Set it to `false` to draw it next to
* the graph instead.
*
* @type {boolean|null}
*/
onArea: null,
/**
* Styles for the series label. The color defaults to the series
* color, or a contrast color if `onArea`.
*
* @type {Highcharts.CSSObject}
*/
style: {
/** @internal */
fontWeight: 'bold'
},
/**
* An array of boxes to avoid when laying out the labels. Each
* item has a `left`, `right`, `top` and `bottom` property.
*
* @type {Array<Highcharts.LabelIntersectBoxObject>}
*/
boxesToAvoid: []
}
}
}
});
/* eslint-disable valid-jsdoc */
/**
* Counter-clockwise, part of the fast line intersection logic.
*
* @private
* @function ccw
*/
function ccw(x1, y1, x2, y2, x3, y3) {
var cw = ((y3 - y1) * (x2 - x1)) - ((y2 - y1) * (x3 - x1));
return cw > 0 ? true : !(cw < 0);
}
/**
* Detect if two lines intersect.
*
* @private
* @function intersectLine
*/
function intersectLine(x1, y1, x2, y2, x3, y3, x4, y4) {
return ccw(x1, y1, x3, y3, x4, y4) !== ccw(x2, y2, x3, y3, x4, y4) &&
ccw(x1, y1, x2, y2, x3, y3) !== ccw(x1, y1, x2, y2, x4, y4);
}
/**
* Detect if a box intersects with a line.
*
* @private
* @function boxIntersectLine
*/
function boxIntersectLine(x, y, w, h, x1, y1, x2, y2) {
return (intersectLine(x, y, x + w, y, x1, y1, x2, y2) || // top of label
intersectLine(x + w, y, x + w, y + h, x1, y1, x2, y2) || // right
intersectLine(x, y + h, x + w, y + h, x1, y1, x2, y2) || // bottom
intersectLine(x, y, x, y + h, x1, y1, x2, y2) // left of label
);
}
/**
* General symbol definition for labels with connector.
*
* @private
* @function Highcharts.SVGRenderer#symbols.connector
*/
SVGRenderer.prototype.symbols.connector = function (x, y, w, h, options) {
var anchorX = options && options.anchorX, anchorY = options && options.anchorY, path, yOffset, lateral = w / 2;
if (isNumber(anchorX) && isNumber(anchorY)) {
path = [['M', anchorX, anchorY]];
// Prefer 45 deg connectors
yOffset = y - anchorY;
if (yOffset < 0) {
yOffset = -h - yOffset;
}
if (yOffset < w) {
lateral = anchorX < x + (w / 2) ? yOffset : w - yOffset;
}
// Anchor below label
if (anchorY > y + h) {
path.push(['L', x + lateral, y + h]);
// Anchor above label
}
else if (anchorY < y) {
path.push(['L', x + lateral, y]);
// Anchor left of label
}
else if (anchorX < x) {
path.push(['L', x, y + h / 2]);
// Anchor right of label
}
else if (anchorX > x + w) {
path.push(['L', x + w, y + h / 2]);
}
}
return path || [];
};
/**
* Points to avoid. In addition to actual data points, the label should avoid
* interpolated positions.
*
* @private
* @function Highcharts.Series#getPointsOnGraph
*/
Series.prototype.getPointsOnGraph = function () {
if (!this.xAxis && !this.yAxis) {
return;
}
var distance = 16, points = this.points, point, last, interpolated = [], i, deltaX, deltaY, delta, len, n, j, d, graph = this.graph || this.area, node = graph.element, inverted = this.chart.inverted, xAxis = this.xAxis, yAxis = this.yAxis, paneLeft = inverted ? yAxis.pos : xAxis.pos, paneTop = inverted ? xAxis.pos : yAxis.pos, onArea = pick(this.options.label.onArea, !!this.area), translatedThreshold = yAxis.getThreshold(this.options.threshold), grid = {};
/**
* Push the point to the interpolated points, but only if that position in
* the grid has not been occupied. As a performance optimization, we divide
* the plot area into a grid and only add one point per series (#9815).
* @private
*/
function pushDiscrete(point) {
var cellSize = 8, key = Math.round(point.plotX / cellSize) + ',' +
Math.round(point.plotY / cellSize);
if (!grid[key]) {
grid[key] = 1;
interpolated.push(point);
}
}
// For splines, get the point at length (possible caveat: peaks are not
// correctly detected)
if (this.getPointSpline &&
node.getPointAtLength &&
!onArea &&
// Not performing well on complex series, node.getPointAtLength is too
// heavy (#9815)
points.length < this.chart.plotSizeX / distance) {
// If it is animating towards a path definition, use that briefly, and
// reset
if (graph.toD) {
d = graph.attr('d');
graph.attr({ d: graph.toD });
}
len = node.getTotalLength();
for (i = 0; i < len; i += distance) {
point = node.getPointAtLength(i);
pushDiscrete({
chartX: paneLeft + point.x,
chartY: paneTop + point.y,
plotX: point.x,
plotY: point.y
});
}
if (d) {
graph.attr({ d: d });
}
// Last point
point = points[points.length - 1];
point.chartX = paneLeft + point.plotX;
point.chartY = paneTop + point.plotY;
pushDiscrete(point);
// Interpolate
}
else {
len = points.length;
for (i = 0; i < len; i += 1) {
point = points[i];
last = points[i - 1];
// Absolute coordinates so we can compare different panes
point.chartX = paneLeft + point.plotX;
point.chartY = paneTop + point.plotY;
if (onArea) {
// Vertically centered inside area
point.chartCenterY = paneTop + (point.plotY +
pick(point.yBottom, translatedThreshold)) / 2;
}
// Add interpolated points
if (i > 0) {
deltaX = Math.abs(point.chartX - last.chartX);
deltaY = Math.abs(point.chartY - last.chartY);
delta = Math.max(deltaX, deltaY);
if (delta > distance) {
n = Math.ceil(delta / distance);
for (j = 1; j < n; j += 1) {
pushDiscrete({
chartX: last.chartX +
(point.chartX - last.chartX) *
(j / n),
chartY: last.chartY +
(point.chartY - last.chartY) *
(j / n),
chartCenterY: last.chartCenterY +
(point.chartCenterY -
last.chartCenterY) * (j / n),
plotX: last.plotX +
(point.plotX - last.plotX) *
(j / n),
plotY: last.plotY +
(point.plotY - last.plotY) *
(j / n)
});
}
}
}
// Add the real point in order to find positive and negative peaks
if (isNumber(point.plotY)) {
pushDiscrete(point);
}
}
}
// Get the bounding box so we can do a quick check first if the bounding
// boxes overlap.
/*
interpolated.bBox = node.getBBox();
interpolated.bBox.x += paneLeft;
interpolated.bBox.y += paneTop;
*/
return interpolated;
};
/**
* Overridable function to return series-specific font sizes for the labels. By
* default it returns bigger font sizes for series with the greater sum of y
* values.
*
* @private
* @function Highcharts.Series#labelFontSize
*/
Series.prototype.labelFontSize = function (minFontSize, maxFontSize) {
return minFontSize + ((this.sum / this.chart.labelSeriesMaxSum) *
(maxFontSize - minFontSize)) + 'px';
};
/**
* Check whether a proposed label position is clear of other elements.
*
* @private
* @function Highcharts.Series#checkClearPoint
*/
Series.prototype.checkClearPoint = function (x, y, bBox, checkDistance) {
var distToOthersSquared = Number.MAX_VALUE, // distance to other graphs
distToPointSquared = Number.MAX_VALUE, dist, connectorPoint, onArea = pick(this.options.label.onArea, !!this.area), findDistanceToOthers = (onArea || this.options.label.connectorAllowed), chart = this.chart, series, points, leastDistance = 16, withinRange, xDist, yDist, i, j;
/**
* @private
*/
function intersectRect(r1, r2) {
return !(r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top);
}
/**
* Get the weight in order to determine the ideal position. Larger distance
* to other series gives more weight. Smaller distance to the actual point
* (connector points only) gives more weight.
* @private
*/
function getWeight(distToOthersSquared, distToPointSquared) {
return distToOthersSquared - distToPointSquared;
}
// First check for collision with existing labels
for (i = 0; i < chart.boxesToAvoid.length; i += 1) {
if (intersectRect(chart.boxesToAvoid[i], {
left: x,
right: x + bBox.width,
top: y,
bottom: y + bBox.height
})) {
return false;
}
}
// For each position, check if the lines around the label intersect with any
// of the graphs.
for (i = 0; i < chart.series.length; i += 1) {
series = chart.series[i];
points = series.interpolatedPoints;
if (series.visible && points) {
for (j = 1; j < points.length; j += 1) {
if (
// To avoid processing, only check intersection if the X
// values are close to the box.
points[j].chartX >= x - leastDistance &&
points[j - 1].chartX <= x + bBox.width +
leastDistance
/* @todo condition above is not the same as below
(
(points[j].chartX as any) >=
(x - leastDistance)
) && (
(points[j - 1].chartX as any) <=
(x + bBox.width + leastDistance)
) */
) {
// If any of the box sides intersect with the line, return.
if (boxIntersectLine(x, y, bBox.width, bBox.height, points[j - 1].chartX, points[j - 1].chartY, points[j].chartX, points[j].chartY)) {
return false;
}
// But if it is too far away (a padded box doesn't
// intersect), also return.
if (this === series && !withinRange && checkDistance) {
withinRange = boxIntersectLine(x - leastDistance, y - leastDistance, bBox.width + 2 * leastDistance, bBox.height + 2 * leastDistance, points[j - 1].chartX, points[j - 1].chartY, points[j].chartX, points[j].chartY);
}
}
// Find the squared distance from the center of the label. On
// area series, avoid its own graph.
if ((findDistanceToOthers || withinRange) &&
(this !== series || onArea)) {
xDist = x + bBox.width / 2 - points[j].chartX;
yDist = y + bBox.height / 2 - points[j].chartY;
distToOthersSquared = Math.min(distToOthersSquared, xDist * xDist + yDist * yDist);
}
}
// Do we need a connector?
if (!onArea &&
findDistanceToOthers &&
this === series &&
((checkDistance && !withinRange) ||
distToOthersSquared < Math.pow(this.options.label.connectorNeighbourDistance, 2))) {
for (j = 1; j < points.length; j += 1) {
dist = Math.min((Math.pow(x + bBox.width / 2 - points[j].chartX, 2) +
Math.pow(y + bBox.height / 2 - points[j].chartY, 2)), (Math.pow(x - points[j].chartX, 2) +
Math.pow(y - points[j].chartY, 2)), (Math.pow(x + bBox.width - points[j].chartX, 2) +
Math.pow(y - points[j].chartY, 2)), (Math.pow(x + bBox.width - points[j].chartX, 2) +
Math.pow(y + bBox.height - points[j].chartY, 2)), (Math.pow(x - points[j].chartX, 2) +
Math.pow(y + bBox.height - points[j].chartY, 2)));
if (dist < distToPointSquared) {
distToPointSquared = dist;
connectorPoint = points[j];
}
}
withinRange = true;
}
}
}
return !checkDistance || withinRange ? {
x: x,
y: y,
weight: getWeight(distToOthersSquared, connectorPoint ? distToPointSquared : 0),
connectorPoint: connectorPoint
} : false;
};
/**
* The main initialize method that runs on chart level after initialization and
* redraw. It runs in a timeout to prevent locking, and loops over all series,
* taking all series and labels into account when placing the labels.
*
* @private
* @function Highcharts.Chart#drawSeriesLabels
*/
Chart.prototype.drawSeriesLabels = function () {
// console.time('drawSeriesLabels');
var chart = this, labelSeries = this.labelSeries;
chart.boxesToAvoid = [];
// Build the interpolated points
labelSeries.forEach(function (series) {
series.interpolatedPoints = series.getPointsOnGraph();
(series.options.label.boxesToAvoid || []).forEach(function (box) {
chart.boxesToAvoid.push(box);
});
});
chart.series.forEach(function (series) {
var labelOptions = series.options.label;
if (!labelOptions || (!series.xAxis && !series.yAxis)) {
return;
}
var bBox, x, y, results = [], clearPoint, i, best, inverted = chart.inverted, paneLeft = (inverted ? series.yAxis.pos : series.xAxis.pos), paneTop = (inverted ? series.xAxis.pos : series.yAxis.pos), paneWidth = chart.inverted ? series.yAxis.len : series.xAxis.len, paneHeight = chart.inverted ? series.xAxis.len : series.yAxis.len, points = series.interpolatedPoints, onArea = pick(labelOptions.onArea, !!series.area), label = series.labelBySeries, isNew = !label, minFontSize = labelOptions.minFontSize, maxFontSize = labelOptions.maxFontSize, dataExtremes, areaMin, areaMax, colorClass = 'highcharts-color-' + pick(series.colorIndex, 'none');
// Stay within the area data bounds (#10038)
if (onArea && !inverted) {
dataExtremes = [
series.xAxis.toPixels(series.xData[0]),
series.xAxis.toPixels(series.xData[series.xData.length - 1])
];
areaMin = Math.min.apply(Math, dataExtremes);
areaMax = Math.max.apply(Math, dataExtremes);
}
/**
* @private
*/
function insidePane(x, y, bBox) {
var leftBound = Math.max(paneLeft, pick(areaMin, -Infinity)), rightBound = Math.min(paneLeft + paneWidth, pick(areaMax, Infinity));
return (x > leftBound &&
x <= rightBound - bBox.width &&
y >= paneTop &&
y <= paneTop + paneHeight - bBox.height);
}
/**
* @private
*/
function destroyLabel() {
if (label) {
series.labelBySeries = label.destroy();
}
}
if (series.visible && !series.isSeriesBoosting && points) {
if (!label) {
var labelText = series.name;
if (typeof labelOptions.format === 'string') {
labelText = format(labelOptions.format, series, chart);
}
else if (labelOptions.formatter) {
labelText = labelOptions.formatter.call(series);
}
series.labelBySeries = label = chart.renderer
.label(labelText, 0, -9999, 'connector')
.addClass('highcharts-series-label ' +
'highcharts-series-label-' + series.index + ' ' +
(series.options.className || '') + ' ' +
colorClass);
if (!chart.renderer.styledMode) {
label.css(extend({
color: onArea ?
chart.renderer.getContrast(series.color) :
series.color
}, labelOptions.style || {}));
label.attr({
opacity: chart.renderer.forExport ? 1 : 0,
stroke: series.color,
'stroke-width': 1
});
}
// Adapt label sizes to the sum of the data
if (minFontSize && maxFontSize) {
label.css({
fontSize: series.labelFontSize(minFontSize, maxFontSize)
});
}
label
.attr({
padding: 0,
zIndex: 3
})
.add();
}
bBox = label.getBBox();
bBox.width = Math.round(bBox.width);
// Ideal positions are centered above or below a point on right side
// of chart
for (i = points.length - 1; i > 0; i -= 1) {
if (onArea) {
// Centered
x = points[i].chartX - bBox.width / 2;
y = points[i].chartCenterY - bBox.height / 2;
if (insidePane(x, y, bBox)) {
best = series.checkClearPoint(x, y, bBox);
}
if (best) {
results.push(best);
}
}
else {
// Right - up
x = points[i].chartX + labelDistance;
y = points[i].chartY - bBox.height - labelDistance;
if (insidePane(x, y, bBox)) {
best = series.checkClearPoint(x, y, bBox, true);
}
if (best) {
results.push(best);
}
// Right - down
x = points[i].chartX + labelDistance;
y = points[i].chartY + labelDistance;
if (insidePane(x, y, bBox)) {
best = series.checkClearPoint(x, y, bBox, true);
}
if (best) {
results.push(best);
}
// Left - down
x = points[i].chartX - bBox.width - labelDistance;
y = points[i].chartY + labelDistance;
if (insidePane(x, y, bBox)) {
best = series.checkClearPoint(x, y, bBox, true);
}
if (best) {
results.push(best);
}
// Left - up
x = points[i].chartX - bBox.width - labelDistance;
y = points[i].chartY - bBox.height - labelDistance;
if (insidePane(x, y, bBox)) {
best = series.checkClearPoint(x, y, bBox, true);
}
if (best) {
results.push(best);
}
}
}
// Brute force, try all positions on the chart in a 16x16 grid
if (labelOptions.connectorAllowed && !results.length && !onArea) {
for (x = paneLeft + paneWidth - bBox.width; x >= paneLeft; x -= 16) {
for (y = paneTop; y < paneTop + paneHeight - bBox.height; y += 16) {
clearPoint = series.checkClearPoint(x, y, bBox, true);
if (clearPoint) {
results.push(clearPoint);
}
}
}
}
if (results.length) {
results.sort(function (a, b) {
return b.weight - a.weight;
});
best = results[0];
chart.boxesToAvoid.push({
left: best.x,
right: best.x + bBox.width,
top: best.y,
bottom: best.y + bBox.height
});
// Move it if needed
var dist = Math.sqrt(Math.pow(Math.abs(best.x - (label.x || 0)), 2) +
Math.pow(Math.abs(best.y - (label.y || 0)), 2));
if (dist && series.labelBySeries) {
// Move fast and fade in - pure animation movement is
// distractive...
var attr = {
opacity: chart.renderer.forExport ? 1 : 0,
x: best.x,
y: best.y
}, anim = {
opacity: 1
};
// ... unless we're just moving a short distance
if (dist <= 10) {
anim = {
x: attr.x,
y: attr.y
};
attr = {};
}
// Default initial animation to a fraction of the series
// animation (#9396)
var animationOptions = void 0;
if (isNew) {
animationOptions = animObject(series.options.animation);
// @todo: Safely remove any cast after merging #13005
animationOptions.duration *= 0.2;
}
series.labelBySeries
.attr(extend(attr, {
anchorX: best.connectorPoint &&
best.connectorPoint.plotX + paneLeft,
anchorY: best.connectorPoint &&
best.connectorPoint.plotY + paneTop
}))
.animate(anim, animationOptions);
// Record closest point to stick to for sync redraw
series.options.kdNow = true;
series.buildKDTree();
var closest = series.searchPoint({
chartX: best.x,
chartY: best.y
}, true);
if (closest) {
label.closest = [
closest,
best.x - (closest.plotX || 0),
best.y - (closest.plotY || 0)
];
}
}
}
else {
destroyLabel();
}
}
else {
destroyLabel();
}
});
fireEvent(chart, 'afterDrawSeriesLabels');
// console.timeEnd('drawSeriesLabels');
};
/* eslint-disable no-invalid-this */
/**
* Prepare drawing series labels.
*
* @private
* @function drawLabels
*/
function drawLabels(e) {
if (this.renderer) {
var chart = this, delay = animObject(chart.renderer.globalAnimation).duration;
chart.labelSeries = [];
chart.labelSeriesMaxSum = 0;
U.clearTimeout(chart.seriesLabelTimer);
// Which series should have labels
chart.series.forEach(function (series) {
var options = series.options.label, label = series.labelBySeries, closest = label && label.closest;
if (options.enabled &&
series.visible &&
(series.graph || series.area) &&
!series.isSeriesBoosting) {
chart.labelSeries.push(series);
if (options.minFontSize && options.maxFontSize) {
series.sum = series.yData.reduce(function (pv, cv) {
return (pv || 0) + (cv || 0);
}, 0);
chart.labelSeriesMaxSum = Math.max(chart.labelSeriesMaxSum, series.sum);
}
// The labels are processing heavy, wait until the animation is
// done
if (e.type === 'load') {
delay = Math.max(delay, animObject(series.options.animation).duration);
}
// Keep the position updated to the axis while redrawing
if (closest) {
if (typeof closest[0].plotX !== 'undefined') {
label.animate({
x: closest[0].plotX + closest[1],
y: closest[0].plotY + closest[2]
});
}
else {
label.attr({ opacity: 0 });
}
}
}
});
chart.seriesLabelTimer = syncTimeout(function () {
if (chart.series && chart.labelSeries) { // #7931, chart destroyed
chart.drawSeriesLabels();
}
}, chart.renderer.forExport || !delay ? 0 : delay);
}
}
// Leave both events, we handle animation differently (#9815)
addEvent(Chart, 'load', drawLabels);
addEvent(Chart, 'redraw', drawLabels);
@@ -0,0 +1,514 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Axis from '../Core/Axis/Axis.js';
import Chart from '../Core/Chart/Chart.js';
import H from '../Core/Globals.js';
import StackingAxis from '../Core/Axis/StackingAxis.js';
import U from '../Core/Utilities.js';
var correctFloat = U.correctFloat, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, format = U.format, isNumber = U.isNumber, pick = U.pick;
/**
* Stack of data points
*
* @product highcharts
*
* @interface Highcharts.StackItemObject
*/ /**
* Alignment settings
* @name Highcharts.StackItemObject#alignOptions
* @type {Highcharts.AlignObject}
*/ /**
* Related axis
* @name Highcharts.StackItemObject#axis
* @type {Highcharts.Axis}
*/ /**
* Cumulative value of the stacked data points
* @name Highcharts.StackItemObject#cumulative
* @type {number}
*/ /**
* True if on the negative side
* @name Highcharts.StackItemObject#isNegative
* @type {boolean}
*/ /**
* Related SVG element
* @name Highcharts.StackItemObject#label
* @type {Highcharts.SVGElement}
*/ /**
* Related stack options
* @name Highcharts.StackItemObject#options
* @type {Highcharts.YAxisStackLabelsOptions}
*/ /**
* Total value of the stacked data points
* @name Highcharts.StackItemObject#total
* @type {number}
*/ /**
* Shared x value of the stack
* @name Highcharts.StackItemObject#x
* @type {number}
*/
''; // detached doclets above
import '../Core/Series/Series.js';
var Series = H.Series;
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* The class for stacks. Each stack, on a specific X value and either negative
* or positive, has its own stack item.
*
* @private
* @class
* @name Highcharts.StackItem
* @param {Highcharts.Axis} axis
* @param {Highcharts.YAxisStackLabelsOptions} options
* @param {boolean} isNegative
* @param {number} x
* @param {Highcharts.OptionsStackingValue} [stackOption]
*/
var StackItem = /** @class */ (function () {
function StackItem(axis, options, isNegative, x, stackOption) {
var inverted = axis.chart.inverted;
this.axis = axis;
// Tells if the stack is negative
this.isNegative = isNegative;
// Save the options to be able to style the label
this.options = options = options || {};
// Save the x value to be able to position the label later
this.x = x;
// Initialize total value
this.total = null;
// This will keep each points' extremes stored by series.index and point
// index
this.points = {};
this.hasValidPoints = false;
// Save the stack option on the series configuration object,
// and whether to treat it as percent
this.stack = stackOption;
this.leftCliff = 0;
this.rightCliff = 0;
// The align options and text align varies on whether the stack is
// negative and if the chart is inverted or not.
// First test the user supplied value, then use the dynamic.
this.alignOptions = {
align: options.align ||
(inverted ? (isNegative ? 'left' : 'right') : 'center'),
verticalAlign: options.verticalAlign ||
(inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
y: options.y,
x: options.x
};
this.textAlign = options.textAlign ||
(inverted ? (isNegative ? 'right' : 'left') : 'center');
}
/**
* @private
* @function Highcharts.StackItem#destroy
*/
StackItem.prototype.destroy = function () {
destroyObjectProperties(this, this.axis);
};
/**
* Renders the stack total label and adds it to the stack label group.
*
* @private
* @function Highcharts.StackItem#render
* @param {Highcharts.SVGElement} group
*/
StackItem.prototype.render = function (group) {
var chart = this.axis.chart, options = this.options, formatOption = options.format, attr = {}, str = formatOption ? // format the text in the label
format(formatOption, this, chart) :
options.formatter.call(this);
// Change the text to reflect the new total and set visibility to hidden
// in case the serie is hidden
if (this.label) {
this.label.attr({ text: str, visibility: 'hidden' });
}
else {
// Create new label
this.label = chart.renderer
.label(str, null, null, options.shape, null, null, options.useHTML, false, 'stack-labels');
attr = {
r: options.borderRadius || 0,
text: str,
rotation: options.rotation,
padding: pick(options.padding, 5),
visibility: 'hidden' // hidden until setOffset is called
};
if (!chart.styledMode) {
attr.fill = options.backgroundColor;
attr.stroke = options.borderColor;
attr['stroke-width'] = options.borderWidth;
this.label.css(options.style);
}
this.label.attr(attr);
if (!this.label.added) {
this.label.add(group); // add to the labels-group
}
}
// Rank it higher than data labels (#8742)
this.label.labelrank = chart.plotHeight;
};
/**
* Sets the offset that the stack has from the x value and repositions the
* label.
*
* @private
* @function Highcarts.StackItem#setOffset
* @param {number} xOffset
* @param {number} xWidth
* @param {number} [boxBottom]
* @param {number} [boxTop]
* @param {number} [defaultX]
*/
StackItem.prototype.setOffset = function (xOffset, xWidth, boxBottom, boxTop, defaultX) {
var stackItem = this, axis = stackItem.axis, chart = axis.chart,
// stack value translated mapped to chart coordinates
y = axis.translate(axis.stacking.usePercentage ?
100 :
(boxTop ?
boxTop :
stackItem.total), 0, 0, 0, 1), yZero = axis.translate(boxBottom ? boxBottom : 0), // stack origin
// stack height:
h = defined(y) && Math.abs(y - yZero),
// x position:
x = pick(defaultX, chart.xAxis[0].translate(stackItem.x)) +
xOffset, stackBox = defined(y) && stackItem.getStackBox(chart, stackItem, x, y, xWidth, h, axis), label = stackItem.label, isNegative = stackItem.isNegative, isJustify = pick(stackItem.options.overflow, 'justify') === 'justify', textAlign = stackItem.textAlign, visible;
if (label && stackBox) {
var bBox = label.getBBox(), padding = label.padding, boxOffsetX, boxOffsetY;
if (textAlign === 'left') {
boxOffsetX = chart.inverted ? -padding : padding;
}
else if (textAlign === 'right') {
boxOffsetX = bBox.width;
}
else {
if (chart.inverted && textAlign === 'center') {
boxOffsetX = bBox.width / 2;
}
else {
boxOffsetX = chart.inverted ?
(isNegative ? bBox.width + padding : -padding) : bBox.width / 2;
}
}
boxOffsetY = chart.inverted ?
bBox.height / 2 : (isNegative ? -padding : bBox.height);
// Reset alignOptions property after justify #12337
stackItem.alignOptions.x = pick(stackItem.options.x, 0);
stackItem.alignOptions.y = pick(stackItem.options.y, 0);
// Set the stackBox position
stackBox.x -= boxOffsetX;
stackBox.y -= boxOffsetY;
// Align the label to the box
label.align(stackItem.alignOptions, null, stackBox);
// Check if label is inside the plotArea #12294
if (chart.isInsidePlot(label.alignAttr.x + boxOffsetX - stackItem.alignOptions.x, label.alignAttr.y + boxOffsetY - stackItem.alignOptions.y)) {
label.show();
}
else {
// Move label away to avoid the overlapping issues
label.alignAttr.y = -9999;
isJustify = false;
}
if (isJustify) {
// Justify stackLabel into the stackBox
Series.prototype.justifyDataLabel.call(this.axis, label, stackItem.alignOptions, label.alignAttr, bBox, stackBox);
}
label.attr({
x: label.alignAttr.x,
y: label.alignAttr.y
});
if (pick(!isJustify && stackItem.options.crop, true)) {
visible =
isNumber(label.x) &&
isNumber(label.y) &&
chart.isInsidePlot(label.x - padding + label.width, label.y) &&
chart.isInsidePlot(label.x + padding, label.y);
if (!visible) {
label.hide();
}
}
}
};
/**
* @private
* @function Highcharts.StackItem#getStackBox
*
* @param {Highcharts.Chart} chart
*
* @param {Highcharts.StackItem} stackItem
*
* @param {number} x
*
* @param {number} y
*
* @param {number} xWidth
*
* @param {number} h
*
* @param {Highcharts.Axis} axis
*
* @return {Highcharts.BBoxObject}
*/
StackItem.prototype.getStackBox = function (chart, stackItem, x, y, xWidth, h, axis) {
var reversed = stackItem.axis.reversed, inverted = chart.inverted, axisPos = axis.height + axis.pos -
(inverted ? chart.plotLeft : chart.plotTop), neg = (stackItem.isNegative && !reversed) ||
(!stackItem.isNegative && reversed); // #4056
return {
x: inverted ? (neg ? y - axis.right : y - h + axis.pos - chart.plotLeft) :
x + chart.xAxis[0].transB - chart.plotLeft,
y: inverted ?
axis.height - x - xWidth :
(neg ?
(axisPos - y - h) :
axisPos - y),
width: inverted ? h : xWidth,
height: inverted ? xWidth : h
};
};
return StackItem;
}());
/**
* Generate stacks for each series and calculate stacks total values
*
* @private
* @function Highcharts.Chart#getStacks
*/
Chart.prototype.getStacks = function () {
var chart = this, inverted = chart.inverted;
// reset stacks for each yAxis
chart.yAxis.forEach(function (axis) {
if (axis.stacking && axis.stacking.stacks && axis.hasVisibleSeries) {
axis.stacking.oldStacks = axis.stacking.stacks;
}
});
chart.series.forEach(function (series) {
var xAxisOptions = series.xAxis && series.xAxis.options || {};
if (series.options.stacking &&
(series.visible === true ||
chart.options.chart.ignoreHiddenSeries === false)) {
series.stackKey = [
series.type,
pick(series.options.stack, ''),
inverted ? xAxisOptions.top : xAxisOptions.left,
inverted ? xAxisOptions.height : xAxisOptions.width
].join(',');
}
});
};
// Stacking methods defined on the Axis prototype
StackingAxis.compose(Axis);
// Stacking methods defined for Series prototype
/**
* Set grouped points in a stack-like object. When `centerInCategory` is true,
* and `stacking` is not enabled, we need a pseudo (horizontal) stack in order
* to handle grouping of points within the same category.
*
* @private
* @function Highcharts.Series#setStackedPoints
* @return {void}
*/
Series.prototype.setGroupedPoints = function () {
if (this.options.centerInCategory &&
(this.is('column') || this.is('columnrange')) &&
// With stacking enabled, we already have stacks that we can compute
// from
!this.options.stacking &&
// With only one series, we don't need to consider centerInCategory
this.chart.series.length > 1) {
Series.prototype.setStackedPoints.call(this, 'group');
}
};
/**
* Adds series' points value to corresponding stack
*
* @private
* @function Highcharts.Series#setStackedPoints
*/
Series.prototype.setStackedPoints = function (stackingParam) {
var stacking = stackingParam || this.options.stacking;
if (!stacking ||
(this.visible !== true &&
this.chart.options.chart.ignoreHiddenSeries !== false)) {
return;
}
var series = this, xData = series.processedXData, yData = series.processedYData, stackedYData = [], yDataLength = yData.length, seriesOptions = series.options, threshold = seriesOptions.threshold, stackThreshold = pick(seriesOptions.startFromThreshold && threshold, 0), stackOption = seriesOptions.stack, stackKey = stackingParam ? series.type + "," + stacking : series.stackKey, negKey = '-' + stackKey, negStacks = series.negStacks, yAxis = series.yAxis, stacks = yAxis.stacking.stacks, oldStacks = yAxis.stacking.oldStacks, stackIndicator, isNegative, stack, other, key, pointKey, i, x, y;
yAxis.stacking.stacksTouched += 1;
// loop over the non-null y values and read them into a local array
for (i = 0; i < yDataLength; i++) {
x = xData[i];
y = yData[i];
stackIndicator = series.getStackIndicator(stackIndicator, x, series.index);
pointKey = stackIndicator.key;
// Read stacked values into a stack based on the x value,
// the sign of y and the stack key. Stacking is also handled for null
// values (#739)
isNegative = negStacks && y < (stackThreshold ? 0 : threshold);
key = isNegative ? negKey : stackKey;
// Create empty object for this stack if it doesn't exist yet
if (!stacks[key]) {
stacks[key] =
{};
}
// Initialize StackItem for this x
if (!stacks[key][x]) {
if (oldStacks[key] &&
oldStacks[key][x]) {
stacks[key][x] = oldStacks[key][x];
stacks[key][x].total = null;
}
else {
stacks[key][x] = new StackItem(yAxis, yAxis.options.stackLabels, isNegative, x, stackOption);
}
}
// If the StackItem doesn't exist, create it first
stack = stacks[key][x];
if (y !== null) {
stack.points[pointKey] = stack.points[series.index] =
[pick(stack.cumulative, stackThreshold)];
// Record the base of the stack
if (!defined(stack.cumulative)) {
stack.base = pointKey;
}
stack.touched = yAxis.stacking.stacksTouched;
// In area charts, if there are multiple points on the same X value,
// let the area fill the full span of those points
if (stackIndicator.index > 0 && series.singleStacks === false) {
stack.points[pointKey][0] =
stack.points[series.index + ',' + x + ',0'][0];
}
// When updating to null, reset the point stack (#7493)
}
else {
stack.points[pointKey] = stack.points[series.index] =
null;
}
// Add value to the stack total
if (stacking === 'percent') {
// Percent stacked column, totals are the same for the positive and
// negative stacks
other = isNegative ? stackKey : negKey;
if (negStacks && stacks[other] && stacks[other][x]) {
other = stacks[other][x];
stack.total = other.total =
Math.max(other.total, stack.total) +
Math.abs(y) ||
0;
// Percent stacked areas
}
else {
stack.total =
correctFloat(stack.total + (Math.abs(y) || 0));
}
}
else if (stacking === 'group') {
// In this stack, the total is the number of valid points
if (y !== null) {
stack.total = (stack.total || 0) + 1;
}
}
else {
stack.total = correctFloat(stack.total + (y || 0));
}
if (stacking === 'group') {
// This point's index within the stack, pushed to stack.points[1]
stack.cumulative = (stack.total || 1) - 1;
}
else {
stack.cumulative =
pick(stack.cumulative, stackThreshold) + (y || 0);
}
if (y !== null) {
stack.points[pointKey].push(stack.cumulative);
stackedYData[i] = stack.cumulative;
stack.hasValidPoints = true;
}
}
if (stacking === 'percent') {
yAxis.stacking.usePercentage = true;
}
if (stacking !== 'group') {
this.stackedYData = stackedYData; // To be used in getExtremes
}
// Reset old stacks
yAxis.stacking.oldStacks = {};
};
/**
* Iterate over all stacks and compute the absolute values to percent
*
* @private
* @function Highcharts.Series#modifyStacks
*/
Series.prototype.modifyStacks = function () {
var series = this, yAxis = series.yAxis, stackKey = series.stackKey, stacks = yAxis.stacking.stacks, processedXData = series.processedXData, stackIndicator, stacking = series.options.stacking;
if (series[stacking + 'Stacker']) { // Modifier function exists
[stackKey, '-' + stackKey].forEach(function (key) {
var i = processedXData.length, x, stack, pointExtremes;
while (i--) {
x = processedXData[i];
stackIndicator = series.getStackIndicator(stackIndicator, x, series.index, key);
stack = stacks[key] && stacks[key][x];
pointExtremes =
stack && stack.points[stackIndicator.key];
if (pointExtremes) {
series[stacking + 'Stacker'](pointExtremes, stack, i);
}
}
});
}
};
/**
* Modifier function for percent stacks. Blows up the stack to 100%.
*
* @private
* @function Highcharts.Series#percentStacker
* @param {Array<number>} pointExtremes
* @param {Highcharts.StackItem} stack
* @param {number} i
*/
Series.prototype.percentStacker = function (pointExtremes, stack, i) {
var totalFactor = stack.total ? 100 / stack.total : 0;
// Y bottom value
pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor);
// Y value
pointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor);
this.stackedYData[i] = pointExtremes[1];
};
/**
* Get stack indicator, according to it's x-value, to determine points with the
* same x-value
*
* @private
* @function Highcharts.Series#getStackIndicator
* @param {Highcharts.StackItemIndicatorObject|undefined} stackIndicator
* @param {number} x
* @param {number} index
* @param {string} [key]
* @return {Highcharts.StackItemIndicatorObject}
*/
Series.prototype.getStackIndicator = function (stackIndicator, x, index, key) {
// Update stack indicator, when:
// first point in a stack || x changed || stack type (negative vs positive)
// changed:
if (!defined(stackIndicator) ||
stackIndicator.x !== x ||
(key && stackIndicator.key !== key)) {
stackIndicator = {
x: x,
index: 0,
key: key
};
}
else {
(stackIndicator).index++;
}
stackIndicator.key =
[index, x, stackIndicator.index].join(',');
return stackIndicator;
};
H.StackItem = StackItem;
export default H.StackItem;
@@ -0,0 +1,74 @@
/* *
*
* (c) 2016-2020 Torstein Honsi, Lars Cabrera
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from '../Core/Globals.js';
import U from '../Core/Utilities.js';
var addEvent = U.addEvent, defined = U.defined, isNumber = U.isNumber, pick = U.pick;
var Chart = H.Chart;
/* eslint-disable no-invalid-this */
/**
* For vertical axes only. Setting the static scale ensures that each tick unit
* is translated into a fixed pixel height. For example, setting the static
* scale to 24 results in each Y axis category taking up 24 pixels, and the
* height of the chart adjusts. Adding or removing items will make the chart
* resize.
*
* @sample gantt/xrange-series/demo/
* X-range series with static scale
*
* @type {number}
* @default 50
* @since 6.2.0
* @product gantt
* @apioption yAxis.staticScale
*/
addEvent(H.Axis, 'afterSetOptions', function () {
var chartOptions = this.chart.options && this.chart.options.chart;
if (!this.horiz &&
isNumber(this.options.staticScale) &&
(!chartOptions.height ||
(chartOptions.scrollablePlotArea &&
chartOptions.scrollablePlotArea.minHeight))) {
this.staticScale = this.options.staticScale;
}
});
Chart.prototype.adjustHeight = function () {
if (this.redrawTrigger !== 'adjustHeight') {
(this.axes || []).forEach(function (axis) {
var chart = axis.chart, animate = !!chart.initiatedScale &&
chart.options.animation, staticScale = axis.options.staticScale, height, diff;
if (axis.staticScale && defined(axis.min)) {
height = pick(axis.brokenAxis && axis.brokenAxis.unitLength, axis.max + axis.tickInterval - axis.min) * staticScale;
// Minimum height is 1 x staticScale.
height = Math.max(height, staticScale);
diff = height - chart.plotHeight;
if (Math.abs(diff) >= 1) {
chart.plotHeight = height;
chart.redrawTrigger = 'adjustHeight';
chart.setSize(void 0, chart.chartHeight + diff, animate);
}
// Make sure clip rects have the right height before initial
// animation.
axis.series.forEach(function (series) {
var clipRect = series.sharedClipKey &&
chart[series.sharedClipKey];
if (clipRect) {
clipRect.attr({
height: chart.plotHeight
});
}
});
}
});
this.initiatedScale = true;
}
this.redrawTrigger = null;
};
addEvent(Chart, 'render', Chart.prototype.adjustHeight);
@@ -0,0 +1,39 @@
/* *
*
* (c) 2010-2020 Highsoft AS
*
* Author: Øystein Moseng
*
* License: www.highcharts.com/license
*
* Accessible high-contrast theme for Highcharts. Considers colorblindness and
* monochrome rendering.
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Highcharts from '../../Core/Globals.js';
import U from '../../Core/Utilities.js';
var setOptions = U.setOptions;
Highcharts.theme = {
colors: ['#F3E796', '#95C471', '#35729E', '#251735'],
colorAxis: {
maxColor: '#05426E',
minColor: '#F3E796'
},
plotOptions: {
map: {
nullColor: '#FCFEFE'
}
},
navigator: {
maskFill: 'rgba(170, 205, 170, 0.5)',
series: {
color: '#95C471',
lineColor: '#35729E'
}
}
};
// Apply the theme
setOptions(Highcharts.theme);
@@ -0,0 +1,256 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* Dark blue theme for Highcharts JS
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Highcharts from '../../Core/Globals.js';
import U from '../../Core/Utilities.js';
var setOptions = U.setOptions;
Highcharts.theme = {
colors: ['#DDDF0D', '#55BF3B', '#DF5353', '#7798BF', '#aaeeee',
'#ff0066', '#eeaaee', '#55BF3B', '#DF5353', '#7798BF', '#aaeeee'],
chart: {
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 1, y2: 1 },
stops: [
[0, 'rgb(48, 48, 96)'],
[1, 'rgb(0, 0, 0)']
]
},
borderColor: '#000000',
borderWidth: 2,
className: 'dark-container',
plotBackgroundColor: 'rgba(255, 255, 255, .1)',
plotBorderColor: '#CCCCCC',
plotBorderWidth: 1
},
title: {
style: {
color: '#C0C0C0',
font: 'bold 16px "Trebuchet MS", Verdana, sans-serif'
}
},
subtitle: {
style: {
color: '#666666',
font: 'bold 12px "Trebuchet MS", Verdana, sans-serif'
}
},
xAxis: {
gridLineColor: '#333333',
gridLineWidth: 1,
labels: {
style: {
color: '#A0A0A0'
}
},
lineColor: '#A0A0A0',
tickColor: '#A0A0A0',
title: {
style: {
color: '#CCC',
fontWeight: 'bold',
fontSize: '12px',
fontFamily: 'Trebuchet MS, Verdana, sans-serif'
}
}
},
yAxis: {
gridLineColor: '#333333',
labels: {
style: {
color: '#A0A0A0'
}
},
lineColor: '#A0A0A0',
minorTickInterval: null,
tickColor: '#A0A0A0',
tickWidth: 1,
title: {
style: {
color: '#CCC',
fontWeight: 'bold',
fontSize: '12px',
fontFamily: 'Trebuchet MS, Verdana, sans-serif'
}
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.75)',
style: {
color: '#F0F0F0'
}
},
toolbar: {
itemStyle: {
color: 'silver'
}
},
plotOptions: {
line: {
dataLabels: {
color: '#CCC'
},
marker: {
lineColor: '#333'
}
},
spline: {
marker: {
lineColor: '#333'
}
},
scatter: {
marker: {
lineColor: '#333'
}
},
candlestick: {
lineColor: 'white'
}
},
legend: {
backgroundColor: 'rgba(0, 0, 0, 0.5)',
itemStyle: {
font: '9pt Trebuchet MS, Verdana, sans-serif',
color: '#A0A0A0'
},
itemHoverStyle: {
color: '#FFF'
},
itemHiddenStyle: {
color: '#444'
},
title: {
style: {
color: '#C0C0C0'
}
}
},
credits: {
style: {
color: '#666'
}
},
labels: {
style: {
color: '#CCC'
}
},
navigation: {
buttonOptions: {
symbolStroke: '#DDDDDD',
theme: {
fill: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.4, '#606060'],
[0.6, '#333333']
]
},
stroke: '#000000'
}
}
},
// scroll charts
rangeSelector: {
buttonTheme: {
fill: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.4, '#888'],
[0.6, '#555']
]
},
stroke: '#000000',
style: {
color: '#CCC',
fontWeight: 'bold'
},
states: {
hover: {
fill: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.4, '#BBB'],
[0.6, '#888']
]
},
stroke: '#000000',
style: {
color: 'white'
}
},
select: {
fill: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.1, '#000'],
[0.3, '#333']
]
},
stroke: '#000000',
style: {
color: 'yellow'
}
}
}
},
inputStyle: {
backgroundColor: '#333',
color: 'silver'
},
labelStyle: {
color: 'silver'
}
},
navigator: {
handles: {
backgroundColor: '#666',
borderColor: '#AAA'
},
outlineColor: '#CCC',
maskFill: 'rgba(16, 16, 16, 0.5)',
series: {
color: '#7798BF',
lineColor: '#A6C7ED'
}
},
scrollbar: {
barBackgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.4, '#888'],
[0.6, '#555']
]
},
barBorderColor: '#CCC',
buttonArrowColor: '#CCC',
buttonBackgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.4, '#888'],
[0.6, '#555']
]
},
buttonBorderColor: '#CCC',
rifleColor: '#FFF',
trackBackgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, '#000'],
[1, '#333']
]
},
trackBorderColor: '#666'
}
};
// Apply the theme
setOptions(Highcharts.theme);
@@ -0,0 +1,256 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* Dark blue theme for Highcharts JS
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Highcharts from '../../Core/Globals.js';
import U from '../../Core/Utilities.js';
var setOptions = U.setOptions;
Highcharts.theme = {
colors: ['#DDDF0D', '#55BF3B', '#DF5353', '#7798BF', '#aaeeee',
'#ff0066', '#eeaaee', '#55BF3B', '#DF5353', '#7798BF', '#aaeeee'],
chart: {
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 1, y2: 1 },
stops: [
[0, 'rgb(48, 96, 48)'],
[1, 'rgb(0, 0, 0)']
]
},
borderColor: '#000000',
borderWidth: 2,
className: 'dark-container',
plotBackgroundColor: 'rgba(255, 255, 255, .1)',
plotBorderColor: '#CCCCCC',
plotBorderWidth: 1
},
title: {
style: {
color: '#C0C0C0',
font: 'bold 16px "Trebuchet MS", Verdana, sans-serif'
}
},
subtitle: {
style: {
color: '#666666',
font: 'bold 12px "Trebuchet MS", Verdana, sans-serif'
}
},
xAxis: {
gridLineColor: '#333333',
gridLineWidth: 1,
labels: {
style: {
color: '#A0A0A0'
}
},
lineColor: '#A0A0A0',
tickColor: '#A0A0A0',
title: {
style: {
color: '#CCC',
fontWeight: 'bold',
fontSize: '12px',
fontFamily: 'Trebuchet MS, Verdana, sans-serif'
}
}
},
yAxis: {
gridLineColor: '#333333',
labels: {
style: {
color: '#A0A0A0'
}
},
lineColor: '#A0A0A0',
minorTickInterval: null,
tickColor: '#A0A0A0',
tickWidth: 1,
title: {
style: {
color: '#CCC',
fontWeight: 'bold',
fontSize: '12px',
fontFamily: 'Trebuchet MS, Verdana, sans-serif'
}
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.75)',
style: {
color: '#F0F0F0'
}
},
toolbar: {
itemStyle: {
color: 'silver'
}
},
plotOptions: {
line: {
dataLabels: {
color: '#CCC'
},
marker: {
lineColor: '#333'
}
},
spline: {
marker: {
lineColor: '#333'
}
},
scatter: {
marker: {
lineColor: '#333'
}
},
candlestick: {
lineColor: 'white'
}
},
legend: {
backgroundColor: 'rgba(0, 0, 0, 0.5)',
itemStyle: {
font: '9pt Trebuchet MS, Verdana, sans-serif',
color: '#A0A0A0'
},
itemHoverStyle: {
color: '#FFF'
},
itemHiddenStyle: {
color: '#444'
},
title: {
style: {
color: '#C0C0C0'
}
}
},
credits: {
style: {
color: '#666'
}
},
labels: {
style: {
color: '#CCC'
}
},
navigation: {
buttonOptions: {
symbolStroke: '#DDDDDD',
theme: {
fill: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.4, '#606060'],
[0.6, '#333333']
]
},
stroke: '#000000'
}
}
},
// scroll charts
rangeSelector: {
buttonTheme: {
fill: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.4, '#888'],
[0.6, '#555']
]
},
stroke: '#000000',
style: {
color: '#CCC',
fontWeight: 'bold'
},
states: {
hover: {
fill: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.4, '#BBB'],
[0.6, '#888']
]
},
stroke: '#000000',
style: {
color: 'white'
}
},
select: {
fill: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.1, '#000'],
[0.3, '#333']
]
},
stroke: '#000000',
style: {
color: 'yellow'
}
}
}
},
inputStyle: {
backgroundColor: '#333',
color: 'silver'
},
labelStyle: {
color: 'silver'
}
},
navigator: {
handles: {
backgroundColor: '#666',
borderColor: '#AAA'
},
outlineColor: '#CCC',
maskFill: 'rgba(16, 16, 16, 0.5)',
series: {
color: '#7798BF',
lineColor: '#A6C7ED'
}
},
scrollbar: {
barBackgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.4, '#888'],
[0.6, '#555']
]
},
barBorderColor: '#CCC',
buttonArrowColor: '#CCC',
buttonBackgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.4, '#888'],
[0.6, '#555']
]
},
buttonBorderColor: '#CCC',
rifleColor: '#FFF',
trackBackgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, '#000'],
[1, '#333']
]
},
trackBorderColor: '#666'
}
};
// Apply the theme
setOptions(Highcharts.theme);
@@ -0,0 +1,217 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* Dark theme for Highcharts JS
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
/* global document */
// Load the fonts
import Highcharts from '../../Core/Globals.js';
import U from '../../Core/Utilities.js';
var setOptions = U.setOptions;
Highcharts.createElement('link', {
href: 'https://fonts.googleapis.com/css?family=Unica+One',
rel: 'stylesheet',
type: 'text/css'
}, null, document.getElementsByTagName('head')[0]);
Highcharts.theme = {
colors: ['#2b908f', '#90ee7e', '#f45b5b', '#7798BF', '#aaeeee', '#ff0066',
'#eeaaee', '#55BF3B', '#DF5353', '#7798BF', '#aaeeee'],
chart: {
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 1, y2: 1 },
stops: [
[0, '#2a2a2b'],
[1, '#3e3e40']
]
},
style: {
fontFamily: '\'Unica One\', sans-serif'
},
plotBorderColor: '#606063'
},
title: {
style: {
color: '#E0E0E3',
textTransform: 'uppercase',
fontSize: '20px'
}
},
subtitle: {
style: {
color: '#E0E0E3',
textTransform: 'uppercase'
}
},
xAxis: {
gridLineColor: '#707073',
labels: {
style: {
color: '#E0E0E3'
}
},
lineColor: '#707073',
minorGridLineColor: '#505053',
tickColor: '#707073',
title: {
style: {
color: '#A0A0A3'
}
}
},
yAxis: {
gridLineColor: '#707073',
labels: {
style: {
color: '#E0E0E3'
}
},
lineColor: '#707073',
minorGridLineColor: '#505053',
tickColor: '#707073',
tickWidth: 1,
title: {
style: {
color: '#A0A0A3'
}
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.85)',
style: {
color: '#F0F0F0'
}
},
plotOptions: {
series: {
dataLabels: {
color: '#F0F0F3',
style: {
fontSize: '13px'
}
},
marker: {
lineColor: '#333'
}
},
boxplot: {
fillColor: '#505053'
},
candlestick: {
lineColor: 'white'
},
errorbar: {
color: 'white'
}
},
legend: {
backgroundColor: 'rgba(0, 0, 0, 0.5)',
itemStyle: {
color: '#E0E0E3'
},
itemHoverStyle: {
color: '#FFF'
},
itemHiddenStyle: {
color: '#606063'
},
title: {
style: {
color: '#C0C0C0'
}
}
},
credits: {
style: {
color: '#666'
}
},
labels: {
style: {
color: '#707073'
}
},
drilldown: {
activeAxisLabelStyle: {
color: '#F0F0F3'
},
activeDataLabelStyle: {
color: '#F0F0F3'
}
},
navigation: {
buttonOptions: {
symbolStroke: '#DDDDDD',
theme: {
fill: '#505053'
}
}
},
// scroll charts
rangeSelector: {
buttonTheme: {
fill: '#505053',
stroke: '#000000',
style: {
color: '#CCC'
},
states: {
hover: {
fill: '#707073',
stroke: '#000000',
style: {
color: 'white'
}
},
select: {
fill: '#000003',
stroke: '#000000',
style: {
color: 'white'
}
}
}
},
inputBoxBorderColor: '#505053',
inputStyle: {
backgroundColor: '#333',
color: 'silver'
},
labelStyle: {
color: 'silver'
}
},
navigator: {
handles: {
backgroundColor: '#666',
borderColor: '#AAA'
},
outlineColor: '#CCC',
maskFill: 'rgba(255,255,255,0.1)',
series: {
color: '#7798BF',
lineColor: '#A6C7ED'
},
xAxis: {
gridLineColor: '#505053'
}
},
scrollbar: {
barBackgroundColor: '#808083',
barBorderColor: '#808083',
buttonArrowColor: '#CCC',
buttonBackgroundColor: '#606063',
buttonBorderColor: '#606063',
rifleColor: '#FFF',
trackBackgroundColor: '#404043',
trackBorderColor: '#404043'
}
};
// Apply the theme
setOptions(Highcharts.theme);
@@ -0,0 +1,264 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* Gray theme for Highcharts JS
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Highcharts from '../../Core/Globals.js';
import U from '../../Core/Utilities.js';
var setOptions = U.setOptions;
Highcharts.theme = {
colors: ['#DDDF0D', '#7798BF', '#55BF3B', '#DF5353', '#aaeeee',
'#ff0066', '#eeaaee', '#55BF3B', '#DF5353', '#7798BF', '#aaeeee'],
chart: {
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, 'rgb(96, 96, 96)'],
[1, 'rgb(16, 16, 16)']
]
},
borderWidth: 0,
borderRadius: 0,
plotBackgroundColor: null,
plotShadow: false,
plotBorderWidth: 0
},
title: {
style: {
color: '#FFF',
font: '16px Lucida Grande, Lucida Sans Unicode,' +
' Verdana, Arial, Helvetica, sans-serif'
}
},
subtitle: {
style: {
color: '#DDD',
font: '12px Lucida Grande, Lucida Sans Unicode,' +
' Verdana, Arial, Helvetica, sans-serif'
}
},
xAxis: {
gridLineWidth: 0,
lineColor: '#999',
tickColor: '#999',
labels: {
style: {
color: '#999',
fontWeight: 'bold'
}
},
title: {
style: {
color: '#AAA',
font: 'bold 12px Lucida Grande, Lucida Sans Unicode,' +
' Verdana, Arial, Helvetica, sans-serif'
}
}
},
yAxis: {
alternateGridColor: null,
minorTickInterval: null,
gridLineColor: 'rgba(255, 255, 255, .1)',
minorGridLineColor: 'rgba(255,255,255,0.07)',
lineWidth: 0,
tickWidth: 0,
labels: {
style: {
color: '#999',
fontWeight: 'bold'
}
},
title: {
style: {
color: '#AAA',
font: 'bold 12px Lucida Grande, Lucida Sans Unicode,' +
' Verdana, Arial, Helvetica, sans-serif'
}
}
},
legend: {
backgroundColor: 'rgba(48, 48, 48, 0.8)',
itemStyle: {
color: '#CCC'
},
itemHoverStyle: {
color: '#FFF'
},
itemHiddenStyle: {
color: '#333'
},
title: {
style: {
color: '#E0E0E0'
}
}
},
labels: {
style: {
color: '#CCC'
}
},
tooltip: {
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, 'rgba(96, 96, 96, .8)'],
[1, 'rgba(16, 16, 16, .8)']
]
},
borderWidth: 0,
style: {
color: '#FFF'
}
},
plotOptions: {
series: {
dataLabels: {
color: '#444'
},
nullColor: '#444444'
},
line: {
dataLabels: {
color: '#CCC'
},
marker: {
lineColor: '#333'
}
},
spline: {
marker: {
lineColor: '#333'
}
},
scatter: {
marker: {
lineColor: '#333'
}
},
candlestick: {
lineColor: 'white'
}
},
toolbar: {
itemStyle: {
color: '#CCC'
}
},
navigation: {
buttonOptions: {
symbolStroke: '#DDDDDD',
theme: {
fill: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.4, '#606060'],
[0.6, '#333333']
]
},
stroke: '#000000'
}
}
},
// scroll charts
rangeSelector: {
buttonTheme: {
fill: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.4, '#888'],
[0.6, '#555']
]
},
stroke: '#000000',
style: {
color: '#CCC',
fontWeight: 'bold'
},
states: {
hover: {
fill: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.4, '#BBB'],
[0.6, '#888']
]
},
stroke: '#000000',
style: {
color: 'white'
}
},
select: {
fill: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.1, '#000'],
[0.3, '#333']
]
},
stroke: '#000000',
style: {
color: 'yellow'
}
}
}
},
inputStyle: {
backgroundColor: '#333',
color: 'silver'
},
labelStyle: {
color: 'silver'
}
},
navigator: {
handles: {
backgroundColor: '#666',
borderColor: '#AAA'
},
outlineColor: '#CCC',
maskFill: 'rgba(16, 16, 16, 0.5)',
series: {
color: '#7798BF',
lineColor: '#A6C7ED'
}
},
scrollbar: {
barBackgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.4, '#888'],
[0.6, '#555']
]
},
barBorderColor: '#CCC',
buttonArrowColor: '#CCC',
buttonBackgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0.4, '#888'],
[0.6, '#555']
]
},
buttonBorderColor: '#CCC',
rifleColor: '#FFF',
trackBackgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
stops: [
[0, '#000'],
[1, '#333']
]
},
trackBorderColor: '#666'
}
};
// Apply the theme
setOptions(Highcharts.theme);
@@ -0,0 +1,110 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* Grid theme for Highcharts JS
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Highcharts from '../../Core/Globals.js';
import U from '../../Core/Utilities.js';
var setOptions = U.setOptions;
Highcharts.theme = {
colors: ['#058DC7', '#50B432', '#ED561B', '#DDDF00', '#24CBE5', '#64E572',
'#FF9655', '#FFF263', '#6AF9C4'],
chart: {
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 1, y2: 1 },
stops: [
[0, 'rgb(255, 255, 255)'],
[1, 'rgb(240, 240, 255)']
]
},
borderWidth: 2,
plotBackgroundColor: 'rgba(255, 255, 255, .9)',
plotShadow: true,
plotBorderWidth: 1
},
title: {
style: {
color: '#000',
font: 'bold 16px "Trebuchet MS", Verdana, sans-serif'
}
},
subtitle: {
style: {
color: '#666666',
font: 'bold 12px "Trebuchet MS", Verdana, sans-serif'
}
},
xAxis: {
gridLineWidth: 1,
lineColor: '#000',
tickColor: '#000',
labels: {
style: {
color: '#000',
font: '11px Trebuchet MS, Verdana, sans-serif'
}
},
title: {
style: {
color: '#333',
fontWeight: 'bold',
fontSize: '12px',
fontFamily: 'Trebuchet MS, Verdana, sans-serif'
}
}
},
yAxis: {
minorTickInterval: 'auto',
lineColor: '#000',
lineWidth: 1,
tickWidth: 1,
tickColor: '#000',
labels: {
style: {
color: '#000',
font: '11px Trebuchet MS, Verdana, sans-serif'
}
},
title: {
style: {
color: '#333',
fontWeight: 'bold',
fontSize: '12px',
fontFamily: 'Trebuchet MS, Verdana, sans-serif'
}
}
},
legend: {
itemStyle: {
font: '9pt Trebuchet MS, Verdana, sans-serif',
color: 'black'
},
itemHoverStyle: {
color: '#039'
},
itemHiddenStyle: {
color: 'gray'
}
},
labels: {
style: {
color: '#99b'
}
},
navigation: {
buttonOptions: {
theme: {
stroke: '#CCCCCC'
}
}
}
};
// Apply the theme
setOptions(Highcharts.theme);
@@ -0,0 +1,79 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* Grid-light theme for Highcharts JS
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
/* global document */
import Highcharts from '../../Core/Globals.js';
import U from '../../Core/Utilities.js';
var setOptions = U.setOptions;
// Load the fonts
Highcharts.createElement('link', {
href: 'https://fonts.googleapis.com/css?family=Dosis:400,600',
rel: 'stylesheet',
type: 'text/css'
}, null, document.getElementsByTagName('head')[0]);
Highcharts.theme = {
colors: ['#7cb5ec', '#f7a35c', '#90ee7e', '#7798BF', '#aaeeee', '#ff0066',
'#eeaaee', '#55BF3B', '#DF5353', '#7798BF', '#aaeeee'],
chart: {
backgroundColor: null,
style: {
fontFamily: 'Dosis, sans-serif'
}
},
title: {
style: {
fontSize: '16px',
fontWeight: 'bold',
textTransform: 'uppercase'
}
},
tooltip: {
borderWidth: 0,
backgroundColor: 'rgba(219,219,216,0.8)',
shadow: false
},
legend: {
backgroundColor: '#F0F0EA',
itemStyle: {
fontWeight: 'bold',
fontSize: '13px'
}
},
xAxis: {
gridLineWidth: 1,
labels: {
style: {
fontSize: '12px'
}
}
},
yAxis: {
minorTickInterval: 'auto',
title: {
style: {
textTransform: 'uppercase'
}
},
labels: {
style: {
fontSize: '12px'
}
}
},
plotOptions: {
candlestick: {
lineColor: '#404048'
}
}
};
// Apply the theme
setOptions(Highcharts.theme);
@@ -0,0 +1,211 @@
/* *
*
* (c) 2010-2020 Highsoft AS
*
* Author: Øystein Moseng
*
* License: www.highcharts.com/license
*
* Accessible high-contrast dark theme for Highcharts. Specifically tailored
* towards 3:1 contrast against black/off-black backgrounds. Neighboring
* colors are tested for color blindness.
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Highcharts from '../../Core/Globals.js';
import U from '../../Core/Utilities.js';
var setOptions = U.setOptions;
var textBright = '#F0F0F3';
Highcharts.theme = {
colors: [
'#a6f0ff',
'#70d49e',
'#e898a5',
'#007faa',
'#f9db72',
'#f45b5b',
'#1e824c',
'#e7934c',
'#dadfe1',
'#a0618b'
],
chart: {
backgroundColor: '#1f1f20',
plotBorderColor: '#606063'
},
title: {
style: {
color: textBright
}
},
subtitle: {
style: {
color: textBright
}
},
xAxis: {
gridLineColor: '#707073',
labels: {
style: {
color: textBright
}
},
lineColor: '#707073',
minorGridLineColor: '#505053',
tickColor: '#707073',
title: {
style: {
color: textBright
}
}
},
yAxis: {
gridLineColor: '#707073',
labels: {
style: {
color: textBright
}
},
lineColor: '#707073',
minorGridLineColor: '#505053',
tickColor: '#707073',
title: {
style: {
color: textBright
}
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.85)',
style: {
color: textBright
}
},
plotOptions: {
series: {
dataLabels: {
color: textBright
},
marker: {
lineColor: '#333'
}
},
boxplot: {
fillColor: '#505053'
},
candlestick: {
lineColor: 'white'
},
errorbar: {
color: 'white'
},
map: {
nullColor: '#353535'
}
},
legend: {
backgroundColor: 'transparent',
itemStyle: {
color: textBright
},
itemHoverStyle: {
color: '#FFF'
},
itemHiddenStyle: {
color: '#606063'
},
title: {
style: {
color: '#D0D0D0'
}
}
},
credits: {
style: {
color: textBright
}
},
labels: {
style: {
color: '#707073'
}
},
drilldown: {
activeAxisLabelStyle: {
color: textBright
},
activeDataLabelStyle: {
color: textBright
}
},
navigation: {
buttonOptions: {
symbolStroke: '#DDDDDD',
theme: {
fill: '#505053'
}
}
},
rangeSelector: {
buttonTheme: {
fill: '#505053',
stroke: '#000000',
style: {
color: '#eee'
},
states: {
hover: {
fill: '#707073',
stroke: '#000000',
style: {
color: textBright
}
},
select: {
fill: '#303030',
stroke: '#101010',
style: {
color: textBright
}
}
}
},
inputBoxBorderColor: '#505053',
inputStyle: {
backgroundColor: '#333',
color: textBright
},
labelStyle: {
color: textBright
}
},
navigator: {
handles: {
backgroundColor: '#666',
borderColor: '#AAA'
},
outlineColor: '#CCC',
maskFill: 'rgba(180,180,255,0.2)',
series: {
color: '#7798BF',
lineColor: '#A6C7ED'
},
xAxis: {
gridLineColor: '#505053'
}
},
scrollbar: {
barBackgroundColor: '#808083',
barBorderColor: '#808083',
buttonArrowColor: '#CCC',
buttonBackgroundColor: '#606063',
buttonBorderColor: '#606063',
rifleColor: '#FFF',
trackBackgroundColor: '#404043',
trackBorderColor: '#404043'
}
};
// Apply the theme
setOptions(Highcharts.theme);
@@ -0,0 +1,41 @@
/* *
*
* (c) 2010-2020 Highsoft AS
*
* Author: Øystein Moseng
*
* License: www.highcharts.com/license
*
* Accessible high-contrast theme for Highcharts. Specifically tailored
* towards 3:1 contrast against white/off-white backgrounds. Neighboring
* colors are tested for color blindness.
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Highcharts from '../../Core/Globals.js';
import U from '../../Core/Utilities.js';
var setOptions = U.setOptions;
Highcharts.theme = {
colors: [
'#5f98cf',
'#434348',
'#49a65e',
'#f45b5b',
'#708090',
'#b68c51',
'#397550',
'#c0493d',
'#4f4a7a',
'#b381b3'
],
navigator: {
series: {
color: '#5f98cf',
lineColor: '#5f98cf'
}
}
};
// Apply the theme
setOptions(Highcharts.theme);
@@ -0,0 +1,113 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* Sand-Signika theme for Highcharts JS
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
/* global document */
import Highcharts from '../../Core/Globals.js';
import U from '../../Core/Utilities.js';
var setOptions = U.setOptions;
// Load the fonts
Highcharts.createElement('link', {
href: 'https://fonts.googleapis.com/css?family=Signika:400,700',
rel: 'stylesheet',
type: 'text/css'
}, null, document.getElementsByTagName('head')[0]);
// Add the background image to the container
Highcharts.addEvent(Highcharts.Chart, 'afterGetContainer', function () {
// eslint-disable-next-line no-invalid-this
this.container.style.background =
'url(https://www.highcharts.com/samples/graphics/sand.png)';
});
Highcharts.theme = {
colors: ['#f45b5b', '#8085e9', '#8d4654', '#7798BF', '#aaeeee',
'#ff0066', '#eeaaee', '#55BF3B', '#DF5353', '#7798BF', '#aaeeee'],
chart: {
backgroundColor: null,
style: {
fontFamily: 'Signika, serif'
}
},
title: {
style: {
color: 'black',
fontSize: '16px',
fontWeight: 'bold'
}
},
subtitle: {
style: {
color: 'black'
}
},
tooltip: {
borderWidth: 0
},
labels: {
style: {
color: '#6e6e70'
}
},
legend: {
backgroundColor: '#E0E0E8',
itemStyle: {
fontWeight: 'bold',
fontSize: '13px'
}
},
xAxis: {
labels: {
style: {
color: '#6e6e70'
}
}
},
yAxis: {
labels: {
style: {
color: '#6e6e70'
}
}
},
plotOptions: {
series: {
shadow: true
},
candlestick: {
lineColor: '#404048'
},
map: {
shadow: false
}
},
// Highstock specific
navigator: {
xAxis: {
gridLineColor: '#D0D0D8'
}
},
rangeSelector: {
buttonTheme: {
fill: 'white',
stroke: '#C0C0C8',
'stroke-width': 1,
states: {
select: {
fill: '#D0D0D8'
}
}
}
},
scrollbar: {
trackBorderColor: '#C0C0C8'
}
};
// Apply the theme
setOptions(Highcharts.theme);
@@ -0,0 +1,103 @@
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* Skies theme for Highcharts JS
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Highcharts from '../../Core/Globals.js';
import U from '../../Core/Utilities.js';
var setOptions = U.setOptions;
Highcharts.theme = {
colors: ['#514F78', '#42A07B', '#9B5E4A', '#72727F', '#1F949A',
'#82914E', '#86777F', '#42A07B'],
chart: {
className: 'skies',
borderWidth: 0,
plotShadow: true,
plotBackgroundImage: 'https://www.highcharts.com/samples/graphics/skies.jpg',
plotBackgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 1, y2: 1 },
stops: [
[0, 'rgba(255, 255, 255, 1)'],
[1, 'rgba(255, 255, 255, 0)']
]
},
plotBorderWidth: 1
},
title: {
style: {
color: '#3E576F',
font: '16px Lucida Grande, Lucida Sans Unicode,' +
' Verdana, Arial, Helvetica, sans-serif'
}
},
subtitle: {
style: {
color: '#6D869F',
font: '12px Lucida Grande, Lucida Sans Unicode,' +
' Verdana, Arial, Helvetica, sans-serif'
}
},
xAxis: {
gridLineWidth: 0,
lineColor: '#C0D0E0',
tickColor: '#C0D0E0',
labels: {
style: {
color: '#666',
fontWeight: 'bold'
}
},
title: {
style: {
color: '#666',
font: '12px Lucida Grande, Lucida Sans Unicode,' +
' Verdana, Arial, Helvetica, sans-serif'
}
}
},
yAxis: {
alternateGridColor: 'rgba(255, 255, 255, .5)',
lineColor: '#C0D0E0',
tickColor: '#C0D0E0',
tickWidth: 1,
labels: {
style: {
color: '#666',
fontWeight: 'bold'
}
},
title: {
style: {
color: '#666',
font: '12px Lucida Grande, Lucida Sans Unicode,' +
' Verdana, Arial, Helvetica, sans-serif'
}
}
},
legend: {
itemStyle: {
font: '9pt Trebuchet MS, Verdana, sans-serif',
color: '#3E576F'
},
itemHoverStyle: {
color: 'black'
},
itemHiddenStyle: {
color: 'silver'
}
},
labels: {
style: {
color: '#3E576F'
}
}
};
// Apply the theme
setOptions(Highcharts.theme);
@@ -0,0 +1,38 @@
/* *
*
* (c) 2010-2020 Highsoft AS
*
* Author: Øystein Moseng
*
* License: www.highcharts.com/license
*
* Accessible high-contrast theme for Highcharts. Considers colorblindness and
* monochrome rendering.
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Highcharts from '../../Core/Globals.js';
import U from '../../Core/Utilities.js';
var setOptions = U.setOptions;
Highcharts.theme = {
colors: ['#FDD089', '#FF7F79', '#A0446E', '#251535'],
colorAxis: {
maxColor: '#60042E',
minColor: '#FDD089'
},
plotOptions: {
map: {
nullColor: '#fefefc'
}
},
navigator: {
series: {
color: '#FF7F79',
lineColor: '#A0446E'
}
}
};
// Apply the theme
setOptions(Highcharts.theme);