diff --git a/js/parts/Chart.js b/js/parts/Chart.js
index 86be708f4f1..a29c50c32dc 100644
--- a/js/parts/Chart.js
+++ b/js/parts/Chart.js
@@ -90,10 +90,27 @@ Chart.prototype = {
// Handle regular options
var options,
- seriesOptions = userOptions.series; // skip merging data points to increase performance
+ type,
+ seriesOptions = userOptions.series, // skip merging data points to increase performance
+ userPlotOptions = userOptions.plotOptions || {};
userOptions.series = null;
options = merge(defaultOptions, userOptions); // do the merge
+
+ // Override (by copy of user options) or clear tooltip options
+ // in chart.options.plotOptions (#6218)
+ for (type in options.plotOptions) {
+ options.plotOptions[type].tooltip = (
+ userPlotOptions[type] &&
+ merge(userPlotOptions[type].tooltip) // override by copy
+ ) || undefined; // or clear
+ }
+ // User options have higher priority than default options (#6218).
+ // In case of exporting: path is changed
+ options.tooltip.userOptions = (userOptions.chart &&
+ userOptions.chart.forExport && userOptions.tooltip.userOptions) ||
+ userOptions.tooltip;
+
options.series = userOptions.series = seriesOptions; // set back the series data
this.userOptions = userOptions;
diff --git a/js/parts/Dynamics.js b/js/parts/Dynamics.js
index 1582ac16d56..48d8ad76e2c 100644
--- a/js/parts/Dynamics.js
+++ b/js/parts/Dynamics.js
@@ -191,7 +191,8 @@ extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {
* extended from plugins.
*/
propsRequireUpdateSeries: ['chart.inverted', 'chart.polar',
- 'chart.ignoreHiddenSeries', 'chart.type', 'colors', 'plotOptions'],
+ 'chart.ignoreHiddenSeries', 'chart.type', 'colors', 'plotOptions',
+ 'tooltip'],
/**
* Chart.update function that takes the whole options stucture.
@@ -248,6 +249,17 @@ extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {
}
/*= } =*/
}
+
+ // Moved up, because tooltip needs updated plotOptions (#6218)
+ /*= if (build.classic) { =*/
+ if (options.colors) {
+ this.options.colors = options.colors;
+ }
+ /*= } =*/
+
+ if (options.plotOptions) {
+ merge(true, this.options.plotOptions, options.plotOptions);
+ }
// Some option stuctures correspond one-to-one to chart objects that have
// update methods, for example
@@ -273,16 +285,6 @@ extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {
}
}
- /*= if (build.classic) { =*/
- if (options.colors) {
- this.options.colors = options.colors;
- }
- /*= } =*/
-
- if (options.plotOptions) {
- merge(true, this.options.plotOptions, options.plotOptions);
- }
-
// 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
diff --git a/js/parts/Series.js b/js/parts/Series.js
index 5df681fa1c6..b1774e7d74e 100644
--- a/js/parts/Series.js
+++ b/js/parts/Series.js
@@ -405,16 +405,22 @@ H.Series = H.seriesType('line', null, { // base series options
itemOptions
);
- // The tooltip options are merged between global and series specific options
+ // The tooltip options are merged between global and series specific
+ // options. Importance order asscendingly:
+ // globals: (1)tooltip, (2)plotOptions.series, (3)plotOptions[this.type]
+ // init userOptions with possible later updates: 4-6 like 1-3 and
+ // (7)this series options
this.tooltipOptions = merge(
- defaultOptions.tooltip,
- defaultOptions.plotOptions[this.type].tooltip,
- userOptions.tooltip,
- userPlotOptions.series && userPlotOptions.series.tooltip,
- userPlotOptions[this.type] && userPlotOptions[this.type].tooltip,
- itemOptions.tooltip
+ defaultOptions.tooltip, // 1
+ defaultOptions.plotOptions.series &&
+ defaultOptions.plotOptions.series.tooltip, // 2
+ defaultOptions.plotOptions[this.type].tooltip, // 3
+ chartOptions.tooltip.userOptions, // 4
+ plotOptions.series && plotOptions.series.tooltip, // 5
+ plotOptions[this.type].tooltip, // 6
+ itemOptions.tooltip // 7
);
-
+
// When shared tooltip, stickyTracking is true by default,
// unless user says otherwise.
this.stickyTracking = pick(
diff --git a/js/parts/Tooltip.js b/js/parts/Tooltip.js
index 753bc00bd2a..47f3c9c3590 100644
--- a/js/parts/Tooltip.js
+++ b/js/parts/Tooltip.js
@@ -181,6 +181,8 @@ H.Tooltip.prototype = {
update: function (options) {
this.destroy();
+ // Update user options (#6218)
+ merge(true, this.chart.options.tooltip.userOptions, options);
this.init(this.chart, merge(true, this.options, options));
},
diff --git a/samples/unit-tests/axis/category/demo.js b/samples/unit-tests/axis/category/demo.js
index d8c4213d9ca..6005592caf8 100644
--- a/samples/unit-tests/axis/category/demo.js
+++ b/samples/unit-tests/axis/category/demo.js
@@ -542,7 +542,9 @@ QUnit.test(
}],
plotOptions: {
- animation: false
+ series: {
+ animation: false
+ }
},
series: [{
diff --git a/samples/unit-tests/tooltip/options-order/demo.details b/samples/unit-tests/tooltip/options-order/demo.details
new file mode 100644
index 00000000000..a86ee3a2f30
--- /dev/null
+++ b/samples/unit-tests/tooltip/options-order/demo.details
@@ -0,0 +1,6 @@
+---
+ resources:
+ - https://code.jquery.com/qunit/qunit-2.0.1.js
+ - https://code.jquery.com/qunit/qunit-2.0.1.css
+ js_wrap: b
+...
\ No newline at end of file
diff --git a/samples/unit-tests/tooltip/options-order/demo.html b/samples/unit-tests/tooltip/options-order/demo.html
new file mode 100644
index 00000000000..8b772ab4e71
--- /dev/null
+++ b/samples/unit-tests/tooltip/options-order/demo.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/unit-tests/tooltip/options-order/demo.js b/samples/unit-tests/tooltip/options-order/demo.js
new file mode 100644
index 00000000000..e473623ac1b
--- /dev/null
+++ b/samples/unit-tests/tooltip/options-order/demo.js
@@ -0,0 +1,315 @@
+QUnit.test('Options importantance order static', function (assert) {
+ /* The importantance asscending order:
+ * 1) default / global -> tooltip
+ * 2) default / global -> plotOptions.series
+ * 3) default / global -> plotOptions.
+ * 4) user set -> tooltip
+ * 5) user set -> plotOptions.series
+ * 6) user set -> plotOptions.
+ * 7) user set -> series
+ */
+ Highcharts.setOptions({
+ tooltip: {
+ valueDecimals: '1', // 1)
+ padding: 1,
+ pointFormat: 'WRONG 1',
+ borderRadius: 1,
+ valueSuffix: 'WRONG 1',
+ footerFormat: 'WRONG 1',
+ valuePrefix: 'WRONG 1'
+ },
+ plotOptions: {
+ series: {
+ tooltip: {
+ padding: 42, // 2)
+ pointFormat: 'WRONG 2',
+ borderRadius: 2,
+ valueSuffix: 'WRONG 2',
+ footerFormat: 'WRONG 2',
+ valuePrefix: 'WRONG 2'
+ }
+ },
+ line: {
+ tooltip: {
+ pointFormat: 'point', // 3)
+ borderRadius: 3,
+ valueSuffix: 'WRONG 3',
+ footerFormat: 'WRONG 3',
+ valuePrefix: 'WRONG 3'
+ }
+ }
+ }
+ });
+
+ var chart = Highcharts.chart('container', {
+ tooltip: {
+ borderRadius: 20, // 4)
+ valueSuffix: 'WRONG 4',
+ footerFormat: 'WRONG 4',
+ valuePrefix: 'WRONG 4'
+ },
+ plotOptions: {
+ series: {
+ tooltip: {
+ valueSuffix: ' suffix', // 5)
+ footerFormat: 'WRONG 5',
+ valuePrefix: 'WRONG 5'
+ }
+ },
+ line: {
+ tooltip: {
+ footerFormat: 'foot', // 6)
+ valuePrefix: 'WRONG 6'
+ }
+ }
+ },
+ series: [{
+ data: [1.12345, 2, 3],
+ tooltip: {
+ valuePrefix: 'prefix ' // 7)
+ }
+ }]
+ }),
+ defaultOptions = Highcharts.getOptions(),
+ series = chart.series[0];
+
+ // 1) default / global -> tooltip
+ assert.strictEqual(
+ series.tooltipOptions.valueDecimals,
+ defaultOptions.tooltip.valueDecimals,
+ '1) defaultOptions.tooltip used'
+ );
+ assert.strictEqual(
+ series.tooltipOptions.valueDecimals,
+ '1',
+ '...and 1) option was merged correctly'
+ );
+
+ // 2) default / global -> plotOptions.series
+ assert.strictEqual(
+ series.tooltipOptions.padding,
+ defaultOptions.plotOptions.series.tooltip.padding,
+ '2) defaultOptions.plotOptions.series used'
+ );
+ assert.strictEqual(
+ series.tooltipOptions.padding,
+ 42,
+ '...and 2) option was merged correctly'
+ );
+
+ // 3) default / global -> plotOptions.
+ assert.strictEqual(
+ series.tooltipOptions.pointFormat,
+ defaultOptions.plotOptions.line.tooltip.pointFormat,
+ '3) defaultOptions.plotOptions. used'
+ );
+ assert.strictEqual(
+ series.tooltipOptions.pointFormat,
+ 'point',
+ '...and 3) option was merged correctly'
+ );
+
+ // 4) user set -> tooltip
+ assert.strictEqual(
+ series.tooltipOptions.borderRadius,
+ chart.options.tooltip.userOptions.borderRadius,
+ '4) chart.options.tooltip.userOptions used'
+ );
+ assert.strictEqual(
+ series.tooltipOptions.borderRadius,
+ 20,
+ '...and 4) option was merged correctly'
+ );
+
+ // 5) user set -> plotOptions.series
+ assert.strictEqual(
+ series.tooltipOptions.valueSuffix,
+ chart.options.plotOptions.series.tooltip.valueSuffix,
+ '5) chart.options.plotOptions.series used'
+ );
+ assert.strictEqual(
+ series.tooltipOptions.valueSuffix,
+ ' suffix',
+ '...and 5) option was merged correctly'
+ );
+
+ // 6) user set -> plotOptions.
+ assert.strictEqual(
+ series.tooltipOptions.footerFormat,
+ chart.options.plotOptions.line.tooltip.footerFormat,
+ '6) chart.options.plotOptions. used'
+ );
+ assert.strictEqual(
+ series.tooltipOptions.footerFormat,
+ 'foot',
+ '...and 6) option was merged correctly'
+ );
+
+ // 7) user set -> series
+ assert.strictEqual(
+ series.tooltipOptions.valuePrefix,
+ chart.series[0].userOptions.tooltip.valuePrefix,
+ '7) chart.series[n] used'
+ );
+ assert.strictEqual(
+ series.tooltipOptions.valuePrefix,
+ 'prefix ',
+ '...and 7) option was merged correctly'
+ );
+});
+
+QUnit.test('Options importantance order dynamic (#6218)', function (assert) {
+ /* The importantance asscending order:
+ * 1) default / global -> tooltip
+ * 2) default / global -> plotOptions.series
+ * 3) default / global -> plotOptions.
+ * 4) user set -> tooltip
+ * 5) user set -> plotOptions.series
+ * 6) user set -> plotOptions.
+ * 7) user set -> series
+ */
+ Highcharts.setOptions({
+ tooltip: {
+ headerFormat: '1' // 1)
+ },
+ plotOptions: {
+ series: {
+ tooltip: {
+ //headerFormat: '2' // 2)
+ }
+ },
+ line: {
+ tooltip: {
+ //headerFormat: '3' // 3)
+ }
+ }
+ }
+ });
+
+ var chart = Highcharts.chart('container', {
+ tooltip: {
+ //headerFormat: '4' // 4)
+ },
+ plotOptions: {
+ series: {
+ tooltip: {
+ //headerFormat: '5' // 5)
+ }
+ },
+ line: {
+ tooltip: {
+ //headerFormat: '6' // 6)
+ }
+ }
+ },
+ series: [{
+ data: [1.1234, 2, 3],
+ tooltip: {
+ //headerFormat: '7' // 7)
+ }
+ }]
+ });
+
+ // 1) default / global -> tooltip
+ assert.strictEqual(
+ chart.series[0].tooltipOptions.headerFormat,
+ '1',
+ '1) defaultOptions.tooltip used'
+ );
+
+ Highcharts.setOptions({
+ plotOptions: {
+ series: {
+ tooltip: {
+ headerFormat: '2'
+ }
+ }
+ }
+ });
+ chart.series[0].update({});
+
+ // 2) default / global -> plotOptions.series
+ assert.strictEqual(
+ chart.series[0].tooltipOptions.headerFormat,
+ '2',
+ '2) defaultOptions.plotOptions.series used'
+ );
+
+ Highcharts.setOptions({
+ plotOptions: {
+ line: {
+ tooltip: {
+ headerFormat: '3'
+ }
+ }
+ }
+ });
+ chart.series[0].update({});
+
+ // 3) default / global -> plotOptions.
+ assert.strictEqual(
+ chart.series[0].tooltipOptions.headerFormat,
+ '3',
+ '3) defaultOptions.plotOptions. used'
+ );
+
+ chart.update({
+ tooltip: {
+ headerFormat: '4'
+ }
+ });
+
+ // 4) user set -> tooltip
+ assert.strictEqual(
+ chart.series[0].tooltipOptions.headerFormat,
+ '4',
+ '4) chart.options.tooltip.userOptions used'
+ );
+
+ chart.update({
+ plotOptions: {
+ series: {
+ tooltip: {
+ headerFormat: '5'
+ }
+ }
+ }
+ });
+
+ // 5) user set -> plotOptions.series
+ assert.strictEqual(
+ chart.series[0].tooltipOptions.headerFormat,
+ '5',
+ '5) chart.options.plotOptions.series used'
+ );
+
+ chart.update({
+ plotOptions: {
+ line: {
+ tooltip: {
+ headerFormat: '6'
+ }
+ }
+ }
+ });
+
+ // 6) user set -> plotOptions.
+ assert.strictEqual(
+ chart.series[0].tooltipOptions.headerFormat,
+ '6',
+ '6) chart.options.plotOptions. used'
+ );
+
+ chart.series[0].update({
+ tooltip: {
+ headerFormat: '7'
+ }
+ });
+
+ // 7) user set -> series
+ assert.strictEqual(
+ chart.series[0].tooltipOptions.headerFormat,
+ '7',
+ '7) chart.series[n] used'
+ );
+});
diff --git a/samples/unit-tests/tooltip/split/demo.js b/samples/unit-tests/tooltip/split/demo.js
index 77f5b8c403e..e4fa3569f8c 100644
--- a/samples/unit-tests/tooltip/split/demo.js
+++ b/samples/unit-tests/tooltip/split/demo.js
@@ -1,97 +1,102 @@
QUnit.test('tooltip.destroy #5855', function (assert) {
- var chart = Highcharts.chart('container', {
- series: [{
- data: [1, 2, 3]
- }, {
- data: [3, 2, 1]
- }],
- tooltip: {
- split: true
- }
- }),
- series1 = chart.series[0],
- series2 = chart.series[1],
- p1 = series1.points[0],
- p2 = series2.points[0],
- tooltip = chart.tooltip;
- tooltip.refresh([p1, p2]);
- assert.strictEqual(
- typeof series1.tt,
- 'object',
- 'series[0].tt is exists'
- );
- assert.strictEqual(
- typeof series2.tt,
- 'object',
- 'series[1].tt is exists'
- );
- assert.strictEqual(
- typeof tooltip.tt,
- 'object',
- 'tooltip.tt is exists'
- );
+ var chart = Highcharts.chart('container', {
+ series: [{
+ data: [1, 2, 3]
+ }, {
+ data: [3, 2, 1]
+ }],
+ tooltip: {
+ split: true
+ }
+ }),
+ series1 = chart.series[0],
+ series2 = chart.series[1],
+ p1 = series1.points[0],
+ p2 = series2.points[0],
+ tooltip = chart.tooltip;
+ tooltip.refresh([p1, p2]);
+ assert.strictEqual(
+ typeof series1.tt,
+ 'object',
+ 'series[0].tt is exists'
+ );
+ assert.strictEqual(
+ typeof series2.tt,
+ 'object',
+ 'series[1].tt is exists'
+ );
+ assert.strictEqual(
+ typeof tooltip.tt,
+ 'object',
+ 'tooltip.tt is exists'
+ );
- tooltip.destroy();
+ tooltip.destroy();
- assert.strictEqual(
- series1.tt,
- undefined,
- 'series[0].tt is destroyed'
- );
- assert.strictEqual(
- series2.tt,
- undefined,
- 'series[1].tt is destroyed'
- );
- assert.strictEqual(
- tooltip.tt,
- undefined,
- 'tooltip.tt is destroyed'
- );
+ assert.strictEqual(
+ series1.tt,
+ undefined,
+ 'series[0].tt is destroyed'
+ );
+ assert.strictEqual(
+ series2.tt,
+ undefined,
+ 'series[1].tt is destroyed'
+ );
+ assert.strictEqual(
+ tooltip.tt,
+ undefined,
+ 'tooltip.tt is destroyed'
+ );
});
QUnit.test('Split tooltip and tooltip.style. #5838', function (assert) {
- var chart = Highcharts.chart('container', {
- series: [{
- data: [1, 2, 3]
- }, {
- data: [3, 2, 1]
+ var chart = Highcharts.chart('container', {
+ series: [{
+ data: [1, 2, 3]
+ }, {
+ data: [3, 2, 1]
- }],
- tooltip: {
- split: true
- }
- }),
- series1 = chart.series[0],
- series2 = chart.series[1],
- p1 = series1.points[0],
- p2 = series2.points[0],
- el,
- value;
+ }],
+ tooltip: {
+ split: true
+ }
+ }),
+ series1 = chart.series[0],
+ series2 = chart.series[1],
+ p1 = series1.points[0],
+ p2 = series2.points[0],
+ el,
+ value;
- chart.tooltip.refresh([p1, p2]);
- el = chart.tooltip.tt.text.element;
- value = window.getComputedStyle(el).getPropertyValue('color');
- assert.strictEqual(
- value,
- 'rgb(51, 51, 51)',
- 'tooltip default color.'
- );
+ chart.tooltip.refresh([p1, p2]);
+ el = chart.tooltip.tt.text.element;
+ value = window.getComputedStyle(el).getPropertyValue('color');
+ assert.strictEqual(
+ value,
+ 'rgb(51, 51, 51)',
+ 'tooltip default color.'
+ );
- chart.update({
- tooltip: {
- style: {
- color: '#FF0000'
- }
- }
- });
- chart.tooltip.refresh([p1, p2]);
- el = chart.tooltip.tt.text.element;
- value = window.getComputedStyle(el).getPropertyValue('color');
- assert.strictEqual(
- value,
- 'rgb(255, 0, 0)',
- 'tooltip color from style.'
- );
+ chart.update({
+ tooltip: {
+ style: {
+ color: '#FF0000'
+ }
+ }
+ });
+
+ chart.tooltip.refresh([
+ chart.series[0].points[0],
+ chart.series[1].points[0]
+ ]);
+
+ el = chart.tooltip.tt.text.element;
+ value = window.getComputedStyle(el).getPropertyValue('color');
+ assert.strictEqual(
+ value,
+ 'rgb(255, 0, 0)',
+ 'tooltip color from style.'
+ );
});