chore: version inicial del codigo fuente del LMS Estafeta
This commit is contained in:
@@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/\//g, '/');
|
||||
}
|
||||
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);
|
||||
});
|
||||
@@ -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
@@ -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');
|
||||
}
|
||||
};
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user