tupali/librerias/gantt/code/es-modules/parts/Dynamics.js

1319 lines
51 KiB
JavaScript
Raw Normal View History

2020-05-23 20:45:54 +00:00
/* *
*
* (c) 2010-2020 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import H from './Globals.js';
import Point from './Point.js';
import Time from './Time.js';
import U from './Utilities.js';
var addEvent = U.addEvent, animate = U.animate, createElement = U.createElement, css = U.css, defined = U.defined, erase = U.erase, error = U.error, extend = U.extend, fireEvent = U.fireEvent, isArray = U.isArray, isNumber = U.isNumber, isObject = U.isObject, isString = U.isString, merge = U.merge, objectEach = U.objectEach, pick = U.pick, relativeLength = U.relativeLength, setAnimation = U.setAnimation, splat = U.splat;
import './Axis.js';
import './Chart.js';
import './Series.js';
var Axis = H.Axis, Chart = H.Chart, Series = H.Series, seriesTypes = H.seriesTypes;
/* eslint-disable valid-jsdoc */
/**
* Remove settings that have not changed, to avoid unnecessary rendering or
* computing (#9197).
* @private
*/
H.cleanRecursively = function (newer, older) {
var result = {};
objectEach(newer, function (val, key) {
var ob;
// Dive into objects (except DOM nodes)
if (isObject(newer[key], true) &&
!newer.nodeType && // #10044
older[key]) {
ob = H.cleanRecursively(newer[key], older[key]);
if (Object.keys(ob).length) {
result[key] = ob;
}
// Arrays, primitives and DOM nodes are copied directly
}
else if (isObject(newer[key]) ||
newer[key] !== older[key]) {
result[key] = newer[key];
}
});
return result;
};
// Extend the Chart prototype for dynamic methods
extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {
/**
* Add a series to the chart after render time. Note that this method should
* never be used when adding data synchronously at chart render time, as it
* adds expense to the calculations and rendering. When adding data at the
* same time as the chart is initialized, add the series as a configuration
* option instead. With multiple axes, the `offset` is dynamically adjusted.
*
* @sample highcharts/members/chart-addseries/
* Add a series from a button
* @sample stock/members/chart-addseries/
* Add a series in Highstock
*
* @function Highcharts.Chart#addSeries
*
* @param {Highcharts.SeriesOptionsType} options
* The config options for the series.
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart after adding.
*
* @param {boolean|Highcharts.AnimationOptionsObject} [animation]
* Whether to apply animation, and optionally animation
* configuration.
*
* @return {Highcharts.Series}
* The newly created series object.
*
* @fires Highcharts.Chart#event:addSeries
* @fires Highcharts.Chart#event:afterAddSeries
*/
addSeries: function (options, redraw, animation) {
var series, chart = this;
if (options) { // <- not necessary
redraw = pick(redraw, true); // defaults to true
fireEvent(chart, 'addSeries', { options: options }, function () {
series = chart.initSeries(options);
chart.isDirtyLegend = true;
chart.linkSeries();
if (series.enabledDataSorting) {
// We need to call `setData` after `linkSeries`
series.setData(options.data, false);
}
fireEvent(chart, 'afterAddSeries', { series: series });
if (redraw) {
chart.redraw(animation);
}
});
}
return series;
},
/**
* Add an axis to the chart after render time. Note that this method should
* never be used when adding data synchronously at chart render time, as it
* adds expense to the calculations and rendering. When adding data at the
* same time as the chart is initialized, add the axis as a configuration
* option instead.
*
* @sample highcharts/members/chart-addaxis/
* Add and remove axes
*
* @function Highcharts.Chart#addAxis
*
* @param {Highcharts.AxisOptions} options
* The axis options.
*
* @param {boolean} [isX=false]
* Whether it is an X axis or a value axis.
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart after adding.
*
* @param {boolean|Highcharts.AnimationOptionsObject} [animation=true]
* Whether and how to apply animation in the redraw.
*
* @return {Highcharts.Axis}
* The newly generated Axis object.
*/
addAxis: function (options, isX, redraw, animation) {
return this.createAxis(isX ? 'xAxis' : 'yAxis', { axis: options, redraw: redraw, animation: animation });
},
/**
* Add a color axis to the chart after render time. Note that this method
* should never be used when adding data synchronously at chart render time,
* as it adds expense to the calculations and rendering. When adding data at
* the same time as the chart is initialized, add the axis as a
* configuration option instead.
*
* @sample highcharts/members/chart-addaxis/
* Add and remove axes
*
* @function Highcharts.Chart#addColorAxis
*
* @param {Highcharts.ColorAxisOptions} options
* The axis options.
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart after adding.
*
* @param {boolean|Highcharts.AnimationOptionsObject} [animation=true]
* Whether and how to apply animation in the redraw.
*
* @return {Highcharts.ColorAxis}
* The newly generated Axis object.
*/
addColorAxis: function (options, redraw, animation) {
return this.createAxis('colorAxis', { axis: options, redraw: redraw, animation: animation });
},
/**
* Factory for creating different axis types.
*
* @private
* @function Highcharts.Chart#createAxis
*
* @param {string} type
* An axis type.
*
* @param {...Array<*>} arguments
* All arguments for the constructor.
*
* @return {Highcharts.Axis | Highcharts.ColorAxis}
* The newly generated Axis object.
*/
createAxis: function (type, options) {
var chartOptions = this.options, isColorAxis = type === 'colorAxis', axisOptions = options.axis, redraw = options.redraw, animation = options.animation, userOptions = merge(axisOptions, {
index: this[type].length,
isX: type === 'xAxis'
}), axis;
if (isColorAxis) {
axis = new H.ColorAxis(this, userOptions);
}
else {
axis = new Axis(this, userOptions);
}
// Push the new axis options to the chart options
chartOptions[type] = splat(chartOptions[type] || {});
chartOptions[type].push(userOptions);
if (isColorAxis) {
this.isDirtyLegend = true;
// Clear before 'bindAxes' (#11924)
this.axes.forEach(function (axis) {
axis.series = [];
});
this.series.forEach(function (series) {
series.bindAxes();
series.isDirtyData = true;
});
}
if (pick(redraw, true)) {
this.redraw(animation);
}
return axis;
},
/**
* Dim the chart and show a loading text or symbol. Options for the loading
* screen are defined in {@link
* https://api.highcharts.com/highcharts/loading|the loading options}.
*
* @sample highcharts/members/chart-hideloading/
* Show and hide loading from a button
* @sample highcharts/members/chart-showloading/
* Apply different text labels
* @sample stock/members/chart-show-hide-loading/
* Toggle loading in Highstock
*
* @function Highcharts.Chart#showLoading
*
* @param {string} [str]
* An optional text to show in the loading label instead of the
* default one. The default text is set in
* [lang.loading](https://api.highcharts.com/highcharts/lang.loading).
*
* @return {void}
*/
showLoading: function (str) {
var chart = this, options = chart.options, loadingDiv = chart.loadingDiv, loadingOptions = options.loading, setLoadingSize = function () {
if (loadingDiv) {
css(loadingDiv, {
left: chart.plotLeft + 'px',
top: chart.plotTop + 'px',
width: chart.plotWidth + 'px',
height: chart.plotHeight + 'px'
});
}
};
// create the layer at the first call
if (!loadingDiv) {
chart.loadingDiv = loadingDiv = createElement('div', {
className: 'highcharts-loading highcharts-loading-hidden'
}, null, chart.container);
chart.loadingSpan = createElement('span', { className: 'highcharts-loading-inner' }, null, loadingDiv);
addEvent(chart, 'redraw', setLoadingSize); // #1080
}
loadingDiv.className = 'highcharts-loading';
// Update text
chart.loadingSpan.innerHTML =
pick(str, options.lang.loading, '');
if (!chart.styledMode) {
// Update visuals
css(loadingDiv, extend(loadingOptions.style, {
zIndex: 10
}));
css(chart.loadingSpan, loadingOptions.labelStyle);
// Show it
if (!chart.loadingShown) {
css(loadingDiv, {
opacity: 0,
display: ''
});
animate(loadingDiv, {
opacity: loadingOptions.style.opacity || 0.5
}, {
duration: loadingOptions.showDuration || 0
});
}
}
chart.loadingShown = true;
setLoadingSize();
},
/**
* Hide the loading layer.
*
* @see Highcharts.Chart#showLoading
*
* @sample highcharts/members/chart-hideloading/
* Show and hide loading from a button
* @sample stock/members/chart-show-hide-loading/
* Toggle loading in Highstock
*
* @function Highcharts.Chart#hideLoading
*
* @return {void}
*/
hideLoading: function () {
var options = this.options, loadingDiv = this.loadingDiv;
if (loadingDiv) {
loadingDiv.className =
'highcharts-loading highcharts-loading-hidden';
if (!this.styledMode) {
animate(loadingDiv, {
opacity: 0
}, {
duration: options.loading.hideDuration || 100,
complete: function () {
css(loadingDiv, { display: 'none' });
}
});
}
}
this.loadingShown = false;
},
/**
* These properties cause isDirtyBox to be set to true when updating. Can be
* extended from plugins.
*/
propsRequireDirtyBox: [
'backgroundColor',
'borderColor',
'borderWidth',
'borderRadius',
'plotBackgroundColor',
'plotBackgroundImage',
'plotBorderColor',
'plotBorderWidth',
'plotShadow',
'shadow'
],
/**
* These properties require a full reflow of chart elements, best
* implemented through running `Chart.setSize` internally (#8190).
* @type {Array}
*/
propsRequireReflow: [
'margin',
'marginTop',
'marginRight',
'marginBottom',
'marginLeft',
'spacing',
'spacingTop',
'spacingRight',
'spacingBottom',
'spacingLeft'
],
/**
* These properties cause all series to be updated when updating. Can be
* extended from plugins.
*/
propsRequireUpdateSeries: [
'chart.inverted',
'chart.polar',
'chart.ignoreHiddenSeries',
'chart.type',
'colors',
'plotOptions',
'time',
'tooltip'
],
/**
* These collections (arrays) implement update() methods with support for
* one-to-one option.
*/
collectionsWithUpdate: [
'xAxis',
'yAxis',
'zAxis',
'series'
],
/**
* A generic function to update any element of the chart. Elements can be
* enabled and disabled, moved, re-styled, re-formatted etc.
*
* A special case is configuration objects that take arrays, for example
* [xAxis](https://api.highcharts.com/highcharts/xAxis),
* [yAxis](https://api.highcharts.com/highcharts/yAxis) or
* [series](https://api.highcharts.com/highcharts/series). For these
* collections, an `id` option is used to map the new option set to an
* existing object. If an existing object of the same id is not found, the
* corresponding item is updated. So for example, running `chart.update`
* with a series item without an id, will cause the existing chart's series
* with the same index in the series array to be updated. When the
* `oneToOne` parameter is true, `chart.update` will also take care of
* adding and removing items from the collection. Read more under the
* parameter description below.
*
* Note that when changing series data, `chart.update` may mutate the passed
* data options.
*
* See also the
* [responsive option set](https://api.highcharts.com/highcharts/responsive).
* Switching between `responsive.rules` basically runs `chart.update` under
* the hood.
*
* @sample highcharts/members/chart-update/
* Update chart geometry
*
* @function Highcharts.Chart#update
*
* @param {Highcharts.Options} options
* A configuration object for the new chart options.
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart.
*
* @param {boolean} [oneToOne=false]
* When `true`, the `series`, `xAxis`, `yAxis` and `annotations`
* collections will be updated one to one, and items will be either
* added or removed to match the new updated options. For example,
* if the chart has two series and we call `chart.update` with a
* configuration containing three series, one will be added. If we
* call `chart.update` with one series, one will be removed. Setting
* an empty `series` array will remove all series, but leaving out
* the`series` property will leave all series untouched. If the
* series have id's, the new series options will be matched by id,
* and the remaining ones removed.
*
* @param {boolean|Highcharts.AnimationOptionsObject} [animation=true]
* Whether to apply animation, and optionally animation
* configuration.
*
* @return {void}
*
* @fires Highcharts.Chart#event:update
* @fires Highcharts.Chart#event:afterUpdate
*/
update: function (options, redraw, oneToOne, animation) {
var chart = this, adders = {
credits: 'addCredits',
title: 'setTitle',
subtitle: 'setSubtitle',
caption: 'setCaption'
}, optionsChart, updateAllAxes, updateAllSeries, newWidth, newHeight, runSetSize, isResponsiveOptions = options.isResponsiveOptions, itemsForRemoval = [];
fireEvent(chart, 'update', { options: options });
// If there are responsive rules in action, undo the responsive rules
// before we apply the updated options and replay the responsive rules
// on top from the chart.redraw function (#9617).
if (!isResponsiveOptions) {
chart.setResponsive(false, true);
}
options = H.cleanRecursively(options, chart.options);
merge(true, chart.userOptions, options);
// If the top-level chart option is present, some special updates are
// required
optionsChart = options.chart;
if (optionsChart) {
merge(true, chart.options.chart, optionsChart);
// Setter function
if ('className' in optionsChart) {
chart.setClassName(optionsChart.className);
}
if ('reflow' in optionsChart) {
chart.setReflow(optionsChart.reflow);
}
if ('inverted' in optionsChart ||
'polar' in optionsChart ||
'type' in optionsChart) {
// Parse options.chart.inverted and options.chart.polar together
// with the available series.
chart.propFromSeries();
updateAllAxes = true;
}
if ('alignTicks' in optionsChart) { // #6452
updateAllAxes = true;
}
objectEach(optionsChart, function (val, key) {
if (chart.propsRequireUpdateSeries.indexOf('chart.' + key) !==
-1) {
updateAllSeries = true;
}
// Only dirty box
if (chart.propsRequireDirtyBox.indexOf(key) !== -1) {
chart.isDirtyBox = true;
}
// Chart setSize
if (!isResponsiveOptions &&
chart.propsRequireReflow.indexOf(key) !== -1) {
runSetSize = true;
}
});
if (!chart.styledMode && 'style' in optionsChart) {
chart.renderer.setStyle(optionsChart.style);
}
}
// Moved up, because tooltip needs updated plotOptions (#6218)
if (!chart.styledMode && options.colors) {
this.options.colors = options.colors;
}
if (options.plotOptions) {
merge(true, this.options.plotOptions, options.plotOptions);
}
// Maintaining legacy global time. If the chart is instanciated first
// with global time, then updated with time options, we need to create a
// new Time instance to avoid mutating the global time (#10536).
if (options.time && this.time === H.time) {
this.time = new Time(options.time);
}
// Some option stuctures correspond one-to-one to chart objects that
// have update methods, for example
// options.credits => chart.credits
// options.legend => chart.legend
// options.title => chart.title
// options.tooltip => chart.tooltip
// options.subtitle => chart.subtitle
// options.mapNavigation => chart.mapNavigation
// options.navigator => chart.navigator
// options.scrollbar => chart.scrollbar
objectEach(options, function (val, key) {
if (chart[key] &&
typeof chart[key].update === 'function') {
chart[key].update(val, false);
// If a one-to-one object does not exist, look for an adder function
}
else if (typeof chart[adders[key]] === 'function') {
chart[adders[key]](val);
}
if (key !== 'chart' &&
chart.propsRequireUpdateSeries.indexOf(key) !== -1) {
updateAllSeries = true;
}
});
// Setters for collections. For axes and series, each item is referred
// by an id. If the id is not found, it defaults to the corresponding
// item in the collection, so setting one series without an id, will
// update the first series in the chart. Setting two series without
// an id will update the first and the second respectively (#6019)
// chart.update and responsive.
this.collectionsWithUpdate.forEach(function (coll) {
var indexMap;
if (options[coll]) {
// In stock charts, the navigator series are also part of the
// chart.series array, but those series should not be handled
// here (#8196).
if (coll === 'series') {
indexMap = [];
chart[coll].forEach(function (s, i) {
if (!s.options.isInternal) {
indexMap.push(pick(s.options.index, i));
}
});
}
splat(options[coll]).forEach(function (newOptions, i) {
var item = (defined(newOptions.id) &&
chart.get(newOptions.id)) || chart[coll][indexMap ? indexMap[i] : i];
if (item && item.coll === coll) {
item.update(newOptions, false);
if (oneToOne) {
item.touched = true;
}
}
// If oneToOne and no matching item is found, add one
if (!item && oneToOne && chart.collectionsWithInit[coll]) {
chart.collectionsWithInit[coll][0].apply(chart,
// [newOptions, ...extraArguments, redraw=false]
[
newOptions
].concat(
// Not all initializers require extra args
chart.collectionsWithInit[coll][1] || []).concat([
false
])).touched = true;
}
});
// Add items for removal
if (oneToOne) {
chart[coll].forEach(function (item) {
if (!item.touched && !item.options.isInternal) {
itemsForRemoval.push(item);
}
else {
delete item.touched;
}
});
}
}
});
itemsForRemoval.forEach(function (item) {
if (item.remove) {
item.remove(false);
}
});
if (updateAllAxes) {
chart.axes.forEach(function (axis) {
axis.update({}, false);
});
}
// Certain options require the whole series structure to be thrown away
// and rebuilt
if (updateAllSeries) {
chart.getSeriesOrderByLinks().forEach(function (series) {
// Avoid removed navigator series
if (series.chart) {
series.update({}, false);
}
}, this);
}
// For loading, just update the options, do not redraw
if (options.loading) {
merge(true, chart.options.loading, options.loading);
}
// Update size. Redraw is forced.
newWidth = optionsChart && optionsChart.width;
newHeight = optionsChart && optionsChart.height;
if (isString(newHeight)) {
newHeight = relativeLength(newHeight, newWidth || chart.chartWidth);
}
if (
// In this case, run chart.setSize with newWidth and newHeight which
// are undefined, only for reflowing chart elements because margin
// or spacing has been set (#8190)
runSetSize ||
// In this case, the size is actually set
(isNumber(newWidth) && newWidth !== chart.chartWidth) ||
(isNumber(newHeight) && newHeight !== chart.chartHeight)) {
chart.setSize(newWidth, newHeight, animation);
}
else if (pick(redraw, true)) {
chart.redraw(animation);
}
fireEvent(chart, 'afterUpdate', {
options: options,
redraw: redraw,
animation: animation
});
},
/**
* Shortcut to set the subtitle options. This can also be done from {@link
* Chart#update} or {@link Chart#setTitle}.
*
* @function Highcharts.Chart#setSubtitle
*
* @param {Highcharts.SubtitleOptions} options
* New subtitle options. The subtitle text itself is set by the
* `options.text` property.
*
* @return {void}
*/
setSubtitle: function (options, redraw) {
this.applyDescription('subtitle', options);
this.layOutTitles(redraw);
},
/**
* Set the caption options. This can also be done from {@link
* Chart#update}.
*
* @function Highcharts.Chart#setCaption
*
* @param {Highcharts.CaptionOptions} options
* New caption options. The caption text itself is set by the
* `options.text` property.
*
* @return {void}
*/
setCaption: function (options, redraw) {
this.applyDescription('caption', options);
this.layOutTitles(redraw);
}
});
/**
* These collections (arrays) implement `Chart.addSomethig` method used in
* chart.update() to create new object in the collection. Equivalent for
* deleting is resolved by simple `Somethig.remove()`.
*
* Note: We need to define these references after initializers are bound to
* chart's prototype.
*/
Chart.prototype.collectionsWithInit = {
// collectionName: [ initializingMethod, [extraArguments] ]
xAxis: [Chart.prototype.addAxis, [true]],
yAxis: [Chart.prototype.addAxis, [false]],
series: [Chart.prototype.addSeries]
};
// extend the Point prototype for dynamic methods
extend(Point.prototype, /** @lends Highcharts.Point.prototype */ {
/**
* Update point with new options (typically x/y data) and optionally redraw
* the series.
*
* @sample highcharts/members/point-update-column/
* Update column value
* @sample highcharts/members/point-update-pie/
* Update pie slice
* @sample maps/members/point-update/
* Update map area value in Highmaps
*
* @function Highcharts.Point#update
*
* @param {Highcharts.PointOptionsType} options
* The point options. Point options are handled as described under
* the `series.type.data` item for each series type. For example
* for a line series, if options is a single number, the point will
* be given that number as the marin y value. If it is an array, it
* will be interpreted as x and y values respectively. If it is an
* object, advanced options are applied.
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart after the point is updated. If doing
* more operations on the chart, it is best practice to set
* `redraw` to false and call `chart.redraw()` after.
*
* @param {boolean|Highcharts.AnimationOptionsObject} [animation=true]
* Whether to apply animation, and optionally animation
* configuration.
*
* @return {void}
*
* @fires Highcharts.Point#event:update
*/
update: function (options, redraw, animation, runEvent) {
var point = this, series = point.series, graphic = point.graphic, i, chart = series.chart, seriesOptions = series.options;
redraw = pick(redraw, true);
/**
* @private
*/
function update() {
point.applyOptions(options);
// Update visuals, #4146
// Handle dummy graphic elements for a11y, #12718
var hasDummyGraphic = graphic && point.hasDummyGraphic;
var shouldDestroyGraphic = point.y === null ? !hasDummyGraphic : hasDummyGraphic;
if (graphic && shouldDestroyGraphic) {
point.graphic = graphic.destroy();
delete point.hasDummyGraphic;
}
if (isObject(options, true)) {
// Destroy so we can get new elements
if (graphic && graphic.element) {
// "null" is also a valid symbol
if (options &&
options.marker &&
typeof options.marker.symbol !== 'undefined') {
point.graphic = graphic.destroy();
}
}
if (options && options.dataLabels && point.dataLabel) {
point.dataLabel = point.dataLabel.destroy(); // #2468
}
if (point.connector) {
point.connector = point.connector.destroy(); // #7243
}
}
// record changes in the parallel arrays
i = point.index;
series.updateParallelArrays(point, i);
// Record the options to options.data. If the old or the new config
// is an object, use point options, otherwise use raw options
// (#4701, #4916).
seriesOptions.data[i] = (isObject(seriesOptions.data[i], true) ||
isObject(options, true)) ?
point.options :
pick(options, seriesOptions.data[i]);
// redraw
series.isDirty = series.isDirtyData = true;
if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320
chart.isDirtyBox = true;
}
if (seriesOptions.legendType === 'point') { // #1831, #1885
chart.isDirtyLegend = true;
}
if (redraw) {
chart.redraw(animation);
}
}
// Fire the event with a default handler of doing the update
if (runEvent === false) { // When called from setData
update();
}
else {
point.firePointEvent('update', { options: options }, update);
}
},
/**
* Remove a point and optionally redraw the series and if necessary the axes
*
* @sample highcharts/plotoptions/series-point-events-remove/
* Remove point and confirm
* @sample highcharts/members/point-remove/
* Remove pie slice
* @sample maps/members/point-remove/
* Remove selected points in Highmaps
*
* @function Highcharts.Point#remove
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart or wait for an explicit call. When
* doing more operations on the chart, for example running
* `point.remove()` in a loop, it is best practice to set `redraw`
* to false and call `chart.redraw()` after.
*
* @param {boolean|Highcharts.AnimationOptionsObject} [animation=false]
* Whether to apply animation, and optionally animation
* configuration.
*
* @return {void}
*/
remove: function (redraw, animation) {
this.series.removePoint(this.series.data.indexOf(this), redraw, animation);
}
});
// Extend the series prototype for dynamic methods
extend(Series.prototype, /** @lends Series.prototype */ {
/**
* Add a point to the series after render time. The point can be added at
* the end, or by giving it an X value, to the start or in the middle of the
* series.
*
* @sample highcharts/members/series-addpoint-append/
* Append point
* @sample highcharts/members/series-addpoint-append-and-shift/
* Append and shift
* @sample highcharts/members/series-addpoint-x-and-y/
* Both X and Y values given
* @sample highcharts/members/series-addpoint-pie/
* Append pie slice
* @sample stock/members/series-addpoint/
* Append 100 points in Highstock
* @sample stock/members/series-addpoint-shift/
* Append and shift in Highstock
* @sample maps/members/series-addpoint/
* Add a point in Highmaps
*
* @function Highcharts.Series#addPoint
*
* @param {Highcharts.PointOptionsType} options
* The point options. If options is a single number, a point with
* that y value is appended to the series. If it is an array, it will
* be interpreted as x and y values respectively. If it is an
* object, advanced options as outlined under `series.data` are
* applied.
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart after the point is added. When adding
* more than one point, it is highly recommended that the redraw
* option be set to false, and instead {@link Chart#redraw} is
* explicitly called after the adding of points is finished.
* Otherwise, the chart will redraw after adding each point.
*
* @param {boolean} [shift=false]
* If true, a point is shifted off the start of the series as one is
* appended to the end.
*
* @param {boolean|Highcharts.AnimationOptionsObject} [animation]
* Whether to apply animation, and optionally animation
* configuration.
*
* @param {boolean} [withEvent=true]
* Used internally, whether to fire the series `addPoint` event.
*
* @return {void}
*
* @fires Highcharts.Series#event:addPoint
*/
addPoint: function (options, redraw, shift, animation, withEvent) {
var series = this, seriesOptions = series.options, data = series.data, chart = series.chart, xAxis = series.xAxis, names = xAxis && xAxis.hasNames && xAxis.names, dataOptions = seriesOptions.data, point, xData = series.xData, isInTheMiddle, i, x;
// Optional redraw, defaults to true
redraw = pick(redraw, true);
// Get options and push the point to xData, yData and series.options. In
// series.generatePoints the Point instance will be created on demand
// and pushed to the series.data array.
point = { series: series };
series.pointClass.prototype.applyOptions.apply(point, [options]);
x = point.x;
// Get the insertion point
i = xData.length;
if (series.requireSorting && x < xData[i - 1]) {
isInTheMiddle = true;
while (i && xData[i - 1] > x) {
i--;
}
}
// Insert undefined item
series.updateParallelArrays(point, 'splice', i, 0, 0);
// Update it
series.updateParallelArrays(point, i);
if (names && point.name) {
names[x] = point.name;
}
dataOptions.splice(i, 0, options);
if (isInTheMiddle) {
series.data.splice(i, 0, null);
series.processData();
}
// Generate points to be added to the legend (#1329)
if (seriesOptions.legendType === 'point') {
series.generatePoints();
}
// Shift the first point off the parallel arrays
if (shift) {
if (data[0] && data[0].remove) {
data[0].remove(false);
}
else {
data.shift();
series.updateParallelArrays(point, 'shift');
dataOptions.shift();
}
}
// Fire event
if (withEvent !== false) {
fireEvent(series, 'addPoint', { point: point });
}
// redraw
series.isDirty = true;
series.isDirtyData = true;
if (redraw) {
chart.redraw(animation); // Animation is set anyway on redraw, #5665
}
},
/**
* Remove a point from the series. Unlike the
* {@link Highcharts.Point#remove} method, this can also be done on a point
* that is not instanciated because it is outside the view or subject to
* Highstock data grouping.
*
* @sample highcharts/members/series-removepoint/
* Remove cropped point
*
* @function Highcharts.Series#removePoint
*
* @param {number} i
* The index of the point in the {@link Highcharts.Series.data|data}
* array.
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart after the point is added. When
* removing more than one point, it is highly recommended that the
* `redraw` option be set to `false`, and instead {@link
* Highcharts.Chart#redraw} is explicitly called after the adding of
* points is finished.
*
* @param {boolean|Highcharts.AnimationOptionsObject} [animation]
* Whether and optionally how the series should be animated.
*
* @return {void}
*
* @fires Highcharts.Point#event:remove
*/
removePoint: function (i, redraw, animation) {
var series = this, data = series.data, point = data[i], points = series.points, chart = series.chart, remove = function () {
if (points && points.length === data.length) { // #4935
points.splice(i, 1);
}
data.splice(i, 1);
series.options.data.splice(i, 1);
series.updateParallelArrays(point || { series: series }, 'splice', i, 1);
if (point) {
point.destroy();
}
// redraw
series.isDirty = true;
series.isDirtyData = true;
if (redraw) {
chart.redraw();
}
};
setAnimation(animation, chart);
redraw = pick(redraw, true);
// Fire the event with a default handler of removing the point
if (point) {
point.firePointEvent('remove', null, remove);
}
else {
remove();
}
},
/**
* Remove a series and optionally redraw the chart.
*
* @sample highcharts/members/series-remove/
* Remove first series from a button
*
* @function Highcharts.Series#remove
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart or wait for an explicit call to
* {@link Highcharts.Chart#redraw}.
*
* @param {boolean|Highcharts.AnimationOptionsObject} [animation]
* Whether to apply animation, and optionally animation
* configuration.
*
* @param {boolean} [withEvent=true]
* Used internally, whether to fire the series `remove` event.
*
* @return {void}
*
* @fires Highcharts.Series#event:remove
*/
remove: function (redraw, animation, withEvent, keepEvents) {
var series = this, chart = series.chart;
/**
* @private
*/
function remove() {
// Destroy elements
series.destroy(keepEvents);
series.remove = null; // Prevent from doing again (#9097)
// Redraw
chart.isDirtyLegend = chart.isDirtyBox = true;
chart.linkSeries();
if (pick(redraw, true)) {
chart.redraw(animation);
}
}
// Fire the event with a default handler of removing the point
if (withEvent !== false) {
fireEvent(series, 'remove', null, remove);
}
else {
remove();
}
},
/**
* Update the series with a new set of options. For a clean and precise
* handling of new options, all methods and elements from the series are
* removed, and it is initialized from scratch. Therefore, this method is
* more performance expensive than some other utility methods like {@link
* Series#setData} or {@link Series#setVisible}.
*
* Note that `Series.update` may mutate the passed `data` options.
*
* @sample highcharts/members/series-update/
* Updating series options
* @sample maps/members/series-update/
* Update series options in Highmaps
*
* @function Highcharts.Series#update
*
* @param {Highcharts.SeriesOptionsType} options
* New options that will be merged with the series' existing options.
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart after the series is altered. If doing
* more operations on the chart, it is a good idea to set redraw to
* false and call {@link Chart#redraw} after.
*
* @return {void}
*
* @fires Highcharts.Series#event:update
* @fires Highcharts.Series#event:afterUpdate
*/
update: function (options, redraw) {
options = H.cleanRecursively(options, this.userOptions);
fireEvent(this, 'update', { options: options });
var series = this, chart = series.chart,
// must use user options when changing type because series.options
// is merged in with type specific plotOptions
oldOptions = series.userOptions, seriesOptions, initialType = series.initialType || series.type, newType = (options.type ||
oldOptions.type ||
chart.options.chart.type), keepPoints = !(
// Indicators, histograms etc recalculate the data. It should be
// possible to omit this.
this.hasDerivedData ||
// Changes to data grouping requires new points in new groups
options.dataGrouping ||
// New type requires new point classes
(newType && newType !== this.type) ||
// New options affecting how the data points are built
typeof options.pointStart !== 'undefined' ||
options.pointInterval ||
options.pointIntervalUnit ||
options.keys), initialSeriesProto = seriesTypes[initialType].prototype, n, groups = [
'group',
'markerGroup',
'dataLabelsGroup',
'transformGroup'
], preserve = [
'eventOptions',
'navigatorSeries',
'baseSeries'
],
// Animation must be enabled when calling update before the initial
// animation has first run. This happens when calling update
// directly after chart initialization, or when applying responsive
// rules (#6912).
animation = series.finishedAnimating && { animation: false }, kinds = {};
if (keepPoints) {
preserve.push('data', 'isDirtyData', 'points', 'processedXData', 'processedYData', 'xIncrement', '_hasPointMarkers', '_hasPointLabels',
// Map specific, consider moving it to series-specific preserve-
// properties (#10617)
'mapMap', 'mapData', 'minY', 'maxY', 'minX', 'maxX');
if (options.visible !== false) {
preserve.push('area', 'graph');
}
series.parallelArrays.forEach(function (key) {
preserve.push(key + 'Data');
});
if (options.data) {
// setData uses dataSorting options so we need to update them
// earlier
if (options.dataSorting) {
extend(series.options.dataSorting, options.dataSorting);
}
this.setData(options.data, false);
}
}
// Do the merge, with some forced options
options = merge(oldOptions, animation, {
// When oldOptions.index is null it should't be cleared.
// Otherwise navigator series will have wrong indexes (#10193).
index: typeof oldOptions.index === 'undefined' ?
series.index : oldOptions.index,
pointStart: pick(
// when updating from blank (#7933)
oldOptions.pointStart,
// when updating after addPoint
series.xData[0])
}, (!keepPoints && { data: series.options.data }), options);
// Merge does not merge arrays, but replaces them. Since points were
// updated, `series.options.data` has correct merged options, use it:
if (keepPoints && options.data) {
options.data = series.options.data;
}
// Make sure preserved properties are not destroyed (#3094)
preserve = groups.concat(preserve);
preserve.forEach(function (prop) {
preserve[prop] = series[prop];
delete series[prop];
});
// Destroy the series and delete all properties. Reinsert all
// methods and properties from the new type prototype (#2270,
// #3719).
series.remove(false, null, false, true);
for (n in initialSeriesProto) { // eslint-disable-line guard-for-in
series[n] = void 0;
}
if (seriesTypes[newType || initialType]) {
extend(series, seriesTypes[newType || initialType].prototype);
}
else {
error(17, true, chart, { missingModuleFor: (newType || initialType) });
}
// Re-register groups (#3094) and other preserved properties
preserve.forEach(function (prop) {
series[prop] = preserve[prop];
});
series.init(chart, options);
// Remove particular elements of the points. Check `series.options`
// because we need to consider the options being set on plotOptions as
// well.
if (keepPoints && this.points) {
seriesOptions = series.options;
// What kind of elements to destroy
if (seriesOptions.visible === false) {
kinds.graphic = 1;
kinds.dataLabel = 1;
}
else if (!series._hasPointLabels) {
var marker = seriesOptions.marker, dataLabels = seriesOptions.dataLabels;
if (marker && (marker.enabled === false ||
'symbol' in marker // #10870
)) {
kinds.graphic = 1;
}
if (dataLabels &&
dataLabels.enabled === false) {
kinds.dataLabel = 1;
}
}
this.points.forEach(function (point) {
if (point && point.series) {
point.resolveColor();
// Destroy elements in order to recreate based on updated
// series options.
if (Object.keys(kinds).length) {
point.destroyElements(kinds);
}
if (seriesOptions.showInLegend === false &&
point.legendItem) {
chart.legend.destroyItem(point);
}
}
}, this);
}
// Update the Z index of groups (#3380, #7397)
if (options.zIndex !== oldOptions.zIndex) {
groups.forEach(function (groupName) {
if (series[groupName]) {
series[groupName].attr({
zIndex: options.zIndex
});
}
});
}
series.initialType = initialType;
chart.linkSeries(); // Links are lost in series.remove (#3028)
fireEvent(this, 'afterUpdate');
if (pick(redraw, true)) {
chart.redraw(keepPoints ? void 0 : false);
}
},
/**
* Used from within series.update
*
* @private
* @function Highcharts.Series#setName
*
* @param {string} name
*
* @return {void}
*/
setName: function (name) {
this.name = this.options.name = this.userOptions.name = name;
this.chart.isDirtyLegend = true;
}
});
// Extend the Axis.prototype for dynamic methods
extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {
/**
* Update an axis object with a new set of options. The options are merged
* with the existing options, so only new or altered options need to be
* specified.
*
* @sample highcharts/members/axis-update/
* Axis update demo
*
* @function Highcharts.Axis#update
*
* @param {Highcharts.AxisOptions} options
* The new options that will be merged in with existing options on
* the axis.
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart after the axis is altered. If doing
* more operations on the chart, it is a good idea to set redraw to
* false and call {@link Chart#redraw} after.
*
* @return {void}
*/
update: function (options, redraw) {
var chart = this.chart, newEvents = ((options && options.events) || {});
options = merge(this.userOptions, options);
// Color Axis is not an array,
// This change is applied in the ColorAxis wrapper
if (chart.options[this.coll].indexOf) {
// Don't use this.options.index,
// StockChart has Axes in navigator too
chart.options[this.coll][chart.options[this.coll].indexOf(this.userOptions)] = options;
}
// Remove old events, if no new exist (#8161)
objectEach(chart.options[this.coll].events, function (fn, ev) {
if (typeof newEvents[ev] === 'undefined') {
newEvents[ev] = void 0;
}
});
this.destroy(true);
this.init(chart, extend(options, { events: newEvents }));
chart.isDirtyBox = true;
if (pick(redraw, true)) {
chart.redraw();
}
},
/**
* Remove the axis from the chart.
*
* @sample highcharts/members/chart-addaxis/
* Add and remove axes
*
* @function Highcharts.Axis#remove
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart following the remove.
*
* @return {void}
*/
remove: function (redraw) {
var chart = this.chart, key = this.coll, // xAxis or yAxis
axisSeries = this.series, i = axisSeries.length;
// Remove associated series (#2687)
while (i--) {
if (axisSeries[i]) {
axisSeries[i].remove(false);
}
}
// Remove the axis
erase(chart.axes, this);
erase(chart[key], this);
if (isArray(chart.options[key])) {
chart.options[key].splice(this.options.index, 1);
}
else { // color axis, #6488
delete chart.options[key];
}
chart[key].forEach(function (axis, i) {
// Re-index, #1706, #8075
axis.options.index = axis.userOptions.index = i;
});
this.destroy();
chart.isDirtyBox = true;
if (pick(redraw, true)) {
chart.redraw();
}
},
/**
* Update the axis title by options after render time.
*
* @sample highcharts/members/axis-settitle/
* Set a new Y axis title
*
* @function Highcharts.Axis#setTitle
*
* @param {Highcharts.AxisTitleOptions} titleOptions
* The additional title options.
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart after setting the title.
*
* @return {void}
*/
setTitle: function (titleOptions, redraw) {
this.update({ title: titleOptions }, redraw);
},
/**
* Set new axis categories and optionally redraw.
*
* @sample highcharts/members/axis-setcategories/
* Set categories by click on a button
*
* @function Highcharts.Axis#setCategories
*
* @param {Array<string>} categories
* The new categories.
*
* @param {boolean} [redraw=true]
* Whether to redraw the chart.
*
* @return {void}
*/
setCategories: function (categories, redraw) {
this.update({ categories: categories }, redraw);
}
});