@eclipse-scout/chart 11.0.41 → 22.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +2 -2
  2. package/dist/eclipse-scout-chart-theme-dark.css +3806 -1606
  3. package/dist/eclipse-scout-chart-theme-dark.css.map +1 -1
  4. package/dist/eclipse-scout-chart-theme.css +3689 -1489
  5. package/dist/eclipse-scout-chart-theme.css.map +1 -1
  6. package/dist/eclipse-scout-chart.js +23321 -6684
  7. package/dist/eclipse-scout-chart.js.map +1 -1
  8. package/dist/file-list +0 -6
  9. package/package.json +21 -21
  10. package/src/chart/AbstractChartRenderer.js +7 -1
  11. package/src/chart/AbstractSvgChartRenderer.js +3 -3
  12. package/src/chart/Chart.js +42 -9
  13. package/src/chart/Chart.less +121 -78
  14. package/src/chart/ChartJsRenderer.js +1097 -582
  15. package/src/chart/FulfillmentChartRenderer.js +7 -5
  16. package/src/chart/SalesfunnelChartRenderer.js +16 -15
  17. package/src/chart/SpeedoChartRenderer.js +11 -9
  18. package/src/chart/VennAsync3Calculator.js +2 -9
  19. package/src/chart/VennChartRenderer.js +6 -17
  20. package/src/style/colors-dark.less +49 -11
  21. package/src/style/colors.less +74 -56
  22. package/src/table/controls/ChartTableControl.js +103 -156
  23. package/src/table/controls/ChartTableControl.less +177 -112
  24. package/src/table/controls/ChartTableControlLayout.js +4 -1
  25. package/dist/eclipse-scout-chart-4f219da36ee28808d3bf.min.js +0 -2
  26. package/dist/eclipse-scout-chart-4f219da36ee28808d3bf.min.js.map +0 -1
  27. package/dist/eclipse-scout-chart-theme-dark-e356c56e246c4f8f5ff3.min.css +0 -1
  28. package/dist/eclipse-scout-chart-theme-e85ee1f9c7c3e6d5af5d.min.css +0 -1
  29. package/dist/texts.json +0 -64
  30. package/src/chart/ChartJsTooltipDelay.js +0 -127
@@ -9,52 +9,111 @@
9
9
  * BSI Business Systems Integration AG - initial API and implementation
10
10
  */
11
11
  import {AbstractChartRenderer, Chart} from '../index';
12
- import ChartJs from 'chart.js';
13
- import {arrays, colorSchemes, Event, numbers, objects, strings, styles} from '@eclipse-scout/core';
14
- // noinspection ES6UnusedImports
15
- import chartjs_plugin_datalabels from 'chartjs-plugin-datalabels';
16
- // noinspection ES6UnusedImports
17
- import ChartJsTooltipDelay from './ChartJsTooltipDelay';
12
+ import ChartJs from 'chart.js/auto';
13
+ import {arrays, colorSchemes, Event, graphics, numbers, objects, scout, strings, styles, tooltips} from '@eclipse-scout/core';
14
+ import ChartDataLabels from 'chartjs-plugin-datalabels';
15
+ import $ from 'jquery';
16
+
17
+ ChartJs.register(ChartDataLabels);
18
+
19
+ /**
20
+ * @typedef CoreChartOptions
21
+ * @property {number} [font.size]
22
+ */
23
+
24
+ /**
25
+ * @typedef TickOptions
26
+ * @property {number} maxRotation
27
+ */
28
+
29
+ /**
30
+ * @typedef Scale
31
+ * @property {number} labelRotation
32
+ * @property {number} height
33
+ * @property {object} _labelSizes
34
+ * @property {object} _labelSizes.widest
35
+ */
36
+
37
+ /**
38
+ * @typedef TooltipModel
39
+ * @property {array} _tooltipItems
40
+ */
41
+
42
+ /**
43
+ * @typedef ArcElement
44
+ * @property {number} innerRadius
45
+ * @property {number} outerRadius
46
+ * @property {number} startAngle
47
+ * @property {number} endAngle
48
+ */
49
+
50
+ /**
51
+ * @typedef PointElement
52
+ * @property {number} angle
53
+ */
54
+
55
+ /**
56
+ * @typedef BarElement
57
+ * @property {number} height
58
+ * @property {number} width
59
+ */
18
60
 
19
61
  /**
20
- * @typedef ChartJs
21
- * @property {object} [defaults]
22
- * @property {object} [defaults.global]
23
- * @property {object} [defaults.global.legend]
24
- * @property {object} [defaults.global.legend.labels]
25
- * @property {object} [defaults.global.elements]
26
- * @property {object} [defaults.global.elements.line]
27
- * @property {object} [defaults.global.elements.point]
28
- * @property {object} [defaults.global.elements.arc]
29
- * @property {object} [defaults.global.elements.rectangle]
30
- * @property {object} [defaults.global.tooltips]
62
+ * @typedef TooltipOptions
63
+ * @property {string} titleFont.family
31
64
  */
32
- ChartJs.defaults.global.maintainAspectRatio = false;
33
- ChartJs.defaults.global.legend.labels.usePointStyle = true;
34
- ChartJs.defaults.global.legend.labels.boxWidth = 7;
35
- ChartJs.defaults.global.elements.line.tension = 0;
36
- ChartJs.defaults.global.elements.line.fill = false;
37
- ChartJs.defaults.global.elements.line.borderWidth = 2;
38
- ChartJs.defaults.global.elements.point.radius = 0;
39
- ChartJs.defaults.global.elements.point.hitRadius = 10;
40
- ChartJs.defaults.global.elements.point.hoverRadius = 5;
41
- ChartJs.defaults.global.elements.point.hoverBorderWidth = 2;
42
- ChartJs.defaults.global.elements.arc.borderWidth = 1;
43
- ChartJs.defaults.global.elements.rectangle.borderWidth = 1;
44
- ChartJs.defaults.global.elements.rectangle.borderSkipped = '';
45
- ChartJs.defaults.horizontalBar.elements.rectangle.borderSkipped = '';
46
- ChartJs.defaults.global.tooltips.borderWidth = 1;
47
- ChartJs.defaults.global.tooltips.cornerRadius = 4;
48
- ChartJs.defaults.global.tooltips.xPadding = 8;
49
- ChartJs.defaults.global.tooltips.yPadding = 8;
50
- ChartJs.defaults.global.tooltips.titleSpacing = 4;
51
- ChartJs.defaults.global.tooltips.titleMarginBottom = 8;
52
- ChartJs.defaults.global.tooltips.bodySpacing = 4;
65
+
66
+ $.extend(true, ChartJs.defaults, {
67
+ maintainAspectRatio: false,
68
+ elements: {
69
+ line: {
70
+ borderWidth: 2
71
+ },
72
+ point: {
73
+ radius: 0,
74
+ hitRadius: 10,
75
+ hoverRadius: 7,
76
+ hoverBorderWidth: 2
77
+ },
78
+ arc: {
79
+ borderWidth: 1
80
+ },
81
+ bar: {
82
+ borderWidth: 1,
83
+ borderSkipped: ''
84
+ }
85
+ },
86
+ plugins: {
87
+ legend: {
88
+ labels: {
89
+ usePointStyle: true,
90
+ boxWidth: 7
91
+ }
92
+ }
93
+ }
94
+ });
95
+ $.extend(true, ChartJs.overrides, {
96
+ line: {
97
+ elements: {
98
+ point: {
99
+ borderWidth: 2
100
+ }
101
+ }
102
+ }
103
+ });
53
104
 
54
105
  let chartJsGlobalsInitialized = false;
106
+ const PHI = (1 + Math.sqrt(5)) / 2; // golden ratio
55
107
 
56
108
  /**
57
109
  * @typedef Dataset
110
+ * @property {string} [datasetId]
111
+ * @property {array} [data]
112
+ *
113
+ * @property {array|string} [backgroundColor]
114
+ * @property {array|string} [backgroundColorBackup]
115
+ * @property {array|string} [hoverBackgroundColor]
116
+ *
58
117
  * @property {array|string} [pointBackgroundColor]
59
118
  * @property {array|string} [pointHoverBackgroundColor]
60
119
  * @property {array|number} [pointRadius]
@@ -83,17 +142,11 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
83
142
  super(chart);
84
143
  this.chartJs = null;
85
144
  this.onlyIntegers = true;
86
- this.minSpaceBetweenYTicks = 35;
87
- this.minSpaceBetweenXTicks = 150;
88
145
  this.maxXAxesTicksHeigth = 75;
89
146
  this.numSupportedColors = 6;
90
147
  this.colorSchemeCssClass = '';
91
148
  this.minRadialChartDatalabelSpace = 25;
92
149
 
93
- this._tooltipTitle = this._formatTooltipTitle.bind(this);
94
- this._tooltipLabel = this._formatTooltipLabel.bind(this);
95
- this._tooltipLabelColor = this._computeTooltipLabelColor.bind(this);
96
-
97
150
  this._labelFormatter = this._formatLabel.bind(this);
98
151
  this._xLabelFormatter = this._formatXLabel.bind(this);
99
152
  this._yLabelFormatter = this._formatYLabel.bind(this);
@@ -123,6 +176,14 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
123
176
  this._legendPointerLeaveHandler = this._onLegendLeavePointer.bind(this);
124
177
 
125
178
  this._resizeHandler = this._onResize.bind(this);
179
+
180
+ this._tooltipTitleGenerator = this._generateTooltipTitle.bind(this);
181
+ this._tooltipLabelGenerator = this._generateTooltipLabel.bind(this);
182
+ this._tooltipLabelValueGenerator = this._generateTooltipLabelValue.bind(this);
183
+ this._tooltipLabelColorGenerator = this._generateTooltipLabelColor.bind(this);
184
+ this._tooltipRenderer = this._renderTooltip.bind(this);
185
+ this._tooltip = null;
186
+ this._tooltipTimeoutId = null;
126
187
  }
127
188
 
128
189
  _validateChartData() {
@@ -168,7 +229,7 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
168
229
  chartConfigDataValid = false;
169
230
  }
170
231
 
171
- if (chartConfigDataValid && scout.isOneOf(this.chart.config.type, Chart.Type.POLAR_AREA, Chart.Type.RADAR)) {
232
+ if (chartConfigDataValid && scout.isOneOf(config.type, Chart.Type.POLAR_AREA, Chart.Type.RADAR)) {
172
233
  // check lengths
173
234
  let i, length = 0;
174
235
  for (i = 0; i < config.data.datasets.length; i++) {
@@ -196,16 +257,15 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
196
257
  }
197
258
  this.firstOpaqueBackgroundColor = styles.getFirstOpaqueBackgroundColor(this.$canvas);
198
259
  if (!chartJsGlobalsInitialized) {
199
- ChartJs.defaults.global.defaultFontFamily = this.$canvas.css('font-family');
260
+ ChartJs.defaults.font.family = this.$canvas.css('font-family');
200
261
  chartJsGlobalsInitialized = true;
201
262
  }
202
263
  /**
203
264
  * @property {number} options.bubble.sizeOfLargestBubble
204
265
  * @property {object} options.numberFormatter
205
- * @property {number} options.tooltips.titleFontFamily
206
- * @property {object} options.scales.scaleLabelByTypeMap
207
- * @property {object} options.scales.xLabelMap
208
- * @property {object} options.scales.yLabelMap
266
+ * @property {object} options.scaleLabelByTypeMap
267
+ * @property {object} options.xLabelMap
268
+ * @property {object} options.yLabelMap
209
269
  */
210
270
  let config = $.extend(true, {}, this.chart.config);
211
271
  this._adjustConfig(config);
@@ -240,65 +300,69 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
240
300
  let config = $.extend(true, {}, this.chart.config);
241
301
  this._adjustConfig(config);
242
302
 
243
- let hiddenDataIndices = [],
244
- applyHiddenDatasetIndices = [];
245
-
246
303
  let targetData = this.chartJs.config.data,
247
304
  sourceData = config.data;
248
305
 
249
- if (targetData && sourceData) {
250
- // Transfer property from source object to target object:
251
- // 1. If the property is not set on the target object, copy it from source.
252
- // 2. If the property is not set on the source object, set it to undefined if setToUndefined = true. Otherwise empty the array if it is an array property or set it to undefined.
253
- // 3. If the property is not an array on the source or the target object, copy the property from the source to the target object.
254
- // 4. If the property is an array on both objects, do not update the array, but transfer the elements (update elements directly, use pop(), push() or splice() if one array is longer than the other).
255
- // This needs to be done to have a smooth animation from the old to the new state and not a complete rebuild of the chart.
256
- let transferProperty = (source, target, property, setToUndefined) => {
257
- if (!source || !target || !property) {
258
- return;
259
- }
260
- // 1. Property not set on target
261
- if (!target[property]) {
262
- target[property] = source[property];
263
- return;
306
+ // Transfer property from source object to target object:
307
+ // 1. If the property is not set on the target object, copy it from source.
308
+ // 2. If the property is not set on the source object, set it to undefined if setToUndefined = true. Otherwise empty the array if it is an array property or set it to undefined.
309
+ // 3. If the property is not an array on the source or the target object, copy the property from the source to the target object.
310
+ // 4. If the property is an array on both objects, do not update the array, but transfer the elements (update elements directly, use pop(), push() or splice() if one array is longer than the other).
311
+ let transferProperty = (source, target, property, setToUndefined) => {
312
+ if (!source || !target || !property) {
313
+ return;
314
+ }
315
+ // 1. Property not set on target
316
+ if (!target[property]) {
317
+ let src = source[property];
318
+ if (src || setToUndefined) {
319
+ target[property] = src;
264
320
  }
265
- // 2. Property not set on source
266
- if (!source[property]) {
267
- if (setToUndefined) {
268
- // Set to undefined if setToUndefined = true
269
- target[property] = undefined;
270
- return;
271
- }
272
- // Empty array
273
- if (Array.isArray(target[property])) {
274
- target[property].splice(0);
275
- return;
276
- }
277
- // Otherwise set to undefined
321
+ return;
322
+ }
323
+ // 2. Property not set on source
324
+ if (!source[property]) {
325
+ if (setToUndefined) {
326
+ // Set to undefined if setToUndefined = true
278
327
  target[property] = undefined;
279
328
  return;
280
329
  }
281
- // 3. Property is not an array on the source or the target object
282
- if (!Array.isArray(source[property]) || !Array.isArray(target[property])) {
283
- target[property] = source[property];
330
+ // Empty array
331
+ if (Array.isArray(target[property])) {
332
+ target[property].splice(0, target[property].length);
284
333
  return;
285
334
  }
286
- // 4. Property is an array on the source and the target object
287
- for (let i = 0; i < Math.min(source[property].length, target[property].length); i++) {
288
- // Update elements directly
289
- target[property][i] = source[property][i];
290
- }
291
- let targetLength = target[property].length,
292
- sourceLength = source[property].length;
293
- if (targetLength > sourceLength) {
294
- // Target array is longer than source array
295
- target[property].splice(sourceLength);
296
- } else if (targetLength < sourceLength) {
297
- // Source array is longer than target array
298
- target[property].push(...source[property].splice(targetLength));
299
- }
300
- };
335
+ // Otherwise set to undefined
336
+ target[property] = undefined;
337
+ return;
338
+ }
339
+ // 3. Property is not an array on the source or the target object
340
+ if (!Array.isArray(source[property]) || !Array.isArray(target[property])) {
341
+ target[property] = source[property];
342
+ return;
343
+ }
344
+ // 4. Property is an array on the source and the target object
345
+ for (let i = 0; i < Math.min(source[property].length, target[property].length); i++) {
346
+ // Update elements directly
347
+ target[property][i] = source[property][i];
348
+ }
349
+ let targetLength = target[property].length,
350
+ sourceLength = source[property].length;
351
+ if (targetLength > sourceLength) {
352
+ // Target array is longer than source array
353
+ target[property].splice(sourceLength, targetLength - sourceLength);
354
+ } else if (targetLength < sourceLength) {
355
+ // Source array is longer than target array
356
+ target[property].push(...source[property].splice(targetLength));
357
+ }
358
+ };
359
+
360
+ let findDataset = (datasets, datasetId) => arrays.find(datasets, dataset => dataset.datasetId === datasetId);
361
+ let findDatasetIndex = (datasets, datasetId) => arrays.findIndex(datasets, dataset => dataset.datasetId === datasetId);
301
362
 
363
+ if (targetData && sourceData) {
364
+ // Transfer properties from source to target, instead of overwriting the whole data object.
365
+ // This needs to be done to have a smooth animation from the old to the new state and not a complete rebuild of the chart.
302
366
  transferProperty(sourceData, targetData, 'labels');
303
367
 
304
368
  if (!targetData.datasets) {
@@ -308,47 +372,89 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
308
372
  sourceData.datasets = [];
309
373
  }
310
374
 
311
- // If the chart is a pie-, doughnut- or polar-area-chart, not complete datasets are hidden but elements of each dataset.
312
- // Store these hidden data indices to apply them to newly added datasets.
313
- if (scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA) && targetData.datasets.length) {
314
- let meta = this.chartJs.getDatasetMeta(0);
315
- if (meta && meta.data && Array.isArray(meta.data)) {
316
- meta.data.forEach((dataMeta, idx) => {
317
- if (dataMeta.hidden) {
318
- hiddenDataIndices.push(idx);
319
- }
320
- });
321
- }
375
+ // if all datasets have no id set, add artificial dataset ids
376
+ if (sourceData.datasets.every(dataset => objects.isNullOrUndefined(dataset.datasetId))) {
377
+ sourceData.datasets.forEach((dataset, idx) => {
378
+ dataset.datasetId = '' + idx;
379
+ });
380
+ targetData.datasets.forEach((dataset, idx) => {
381
+ dataset.datasetId = '' + idx;
382
+ });
322
383
  }
323
384
 
324
- for (let i = 0; i < Math.min(sourceData.datasets.length, targetData.datasets.length); i++) {
325
- let targetDataset = targetData.datasets[i],
326
- sourceDataset = sourceData.datasets[i];
385
+ // update existing datasets
386
+ // Important: Update existing datasets first, before removing obsolete datasets
387
+ // (the dataset object has listeners from Chart.js, which do not work well on a partially updated chart (updated datasets, but not yet updated chart)
388
+ targetData.datasets.forEach(targetDataset => {
389
+ let sourceDataset = findDataset(sourceData.datasets, targetDataset.datasetId);
390
+
391
+ if (sourceDataset) {
392
+ targetDataset.label = sourceDataset.label;
393
+ targetDataset.type = sourceDataset.type;
394
+
395
+ transferProperty(sourceDataset, targetDataset, 'data');
396
+
397
+ transferProperty(sourceDataset, targetDataset, 'backgroundColor', true);
398
+ transferProperty(sourceDataset, targetDataset, 'borderColor', true);
399
+ transferProperty(sourceDataset, targetDataset, 'hoverBackgroundColor', true);
400
+ transferProperty(sourceDataset, targetDataset, 'hoverBorderColor', true);
401
+ transferProperty(sourceDataset, targetDataset, 'legendColor', true);
402
+ transferProperty(sourceDataset, targetDataset, 'pointBackgroundColor', true);
403
+ transferProperty(sourceDataset, targetDataset, 'pointHoverBackgroundColor', true);
404
+ transferProperty(sourceDataset, targetDataset, 'pointBorderColor', true);
405
+ transferProperty(sourceDataset, targetDataset, 'pointHoverBorderColor', true);
406
+
407
+ transferProperty(sourceDataset, targetDataset, 'uncheckedBackgroundColor', true);
408
+ transferProperty(sourceDataset, targetDataset, 'uncheckedHoverBackgroundColor', true);
409
+ transferProperty(sourceDataset, targetDataset, 'checkedBackgroundColor', true);
410
+ transferProperty(sourceDataset, targetDataset, 'checkedHoverBackgroundColor', true);
411
+ transferProperty(sourceDataset, targetDataset, 'uncheckedPointBackgroundColor', true);
412
+ transferProperty(sourceDataset, targetDataset, 'uncheckedPointHoverBackgroundColor', true);
413
+ transferProperty(sourceDataset, targetDataset, 'checkedPointBackgroundColor', true);
414
+ transferProperty(sourceDataset, targetDataset, 'checkedPointHoverBackgroundColor', true);
415
+
416
+ transferProperty(sourceDataset, targetDataset, 'lineTension', true);
417
+
418
+ transferProperty(sourceDataset, targetDataset, 'pointRadius', true);
419
+ transferProperty(sourceDataset, targetDataset, 'uncheckedPointRadius', true);
420
+ transferProperty(sourceDataset, targetDataset, 'checkedPointRadius', true);
421
+
422
+ this._adjustDatasetBorderWidths(targetDataset);
423
+ }
424
+ });
327
425
 
328
- targetDataset.label = sourceDataset.label;
329
- targetDataset.type = sourceDataset.type;
426
+ // remove deleted datasets, loop backwards to not compromise the loop when modifying the array
427
+ // datasets without an id get deleted anyway (replaced in every update, because a correct identification is not possible)
428
+ for (let i = targetData.datasets.length - 1; i >= 0; i--) {
429
+ let datasetId = targetData.datasets[i].datasetId;
430
+ let deleted = objects.isNullOrUndefined(datasetId) || findDatasetIndex(sourceData.datasets, datasetId) === -1;
431
+ if (deleted) {
432
+ targetData.datasets.splice(i, 1);
433
+ }
434
+ }
330
435
 
331
- transferProperty(sourceDataset, targetDataset, 'data');
436
+ // sort existing, updated datasets
437
+ targetData.datasets.sort((a, b) => {
438
+ return findDatasetIndex(sourceData.datasets, a.datasetId) - findDatasetIndex(sourceData.datasets, b.datasetId);
439
+ });
332
440
 
333
- transferProperty(sourceDataset, targetDataset, 'backgroundColor', true);
334
- transferProperty(sourceDataset, targetDataset, 'borderColor', true);
335
- transferProperty(sourceDataset, targetDataset, 'hoverBackgroundColor', true);
336
- transferProperty(sourceDataset, targetDataset, 'hoverBorderColor', true);
337
- transferProperty(sourceDataset, targetDataset, 'legendColor', true);
338
- transferProperty(sourceDataset, targetDataset, 'pointHoverBackgroundColor', true);
339
- }
340
- let targetLength = targetData.datasets.length,
341
- sourceLength = sourceData.datasets.length;
342
- if (targetLength > sourceLength) {
343
- targetData.datasets.splice(sourceLength);
344
- } else if (targetLength < sourceLength) {
345
- targetData.datasets.push(...sourceData.datasets.splice(targetLength));
346
- applyHiddenDatasetIndices = arrays.init(sourceLength - targetLength).map((elem, idx) => targetLength + idx);
347
- }
441
+ // add all new datasets
442
+ sourceData.datasets.forEach((sourceDataset, idx) => {
443
+ let targetDataset = targetData.datasets[idx];
444
+ // exclude datasets without an id here, to ensure that multiple datasets without an id do not overwrite each other
445
+ if (targetDataset && targetDataset.datasetId && sourceDataset.datasetId === targetDataset.datasetId) {
446
+ return;
447
+ }
448
+ targetData.datasets.splice(idx, 0, sourceDataset);
449
+ });
348
450
  } else {
349
451
  this.chartJs.config.data = sourceData;
350
452
  }
351
453
 
454
+ // update label maps for scales (the label maps, despite being part of the config, can be updated, without redrawing the whole chart)
455
+ transferProperty(config.options, this.chartJs.config.options, 'xLabelMap', true);
456
+ transferProperty(config.options, this.chartJs.config.options, 'yLabelMap', true);
457
+
352
458
  $.extend(true, this.chartJs.config, {
353
459
  options: {
354
460
  animation: {
@@ -356,21 +462,13 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
356
462
  }
357
463
  }
358
464
  });
359
- this.chartJs.update();
465
+ let scales = this.chartJs.config.options.scales || {},
466
+ axes = [scales.x || {}, scales.y || {}, scales.yDiffType || {}, scales.r || {}];
467
+ axes.forEach(axis => {
468
+ (axis.ticks || {}).stepSize = undefined;
469
+ });
360
470
 
361
- // Apply hidden data indices (only set for pie-, doughnut- or polar-area-chart)
362
- if (hiddenDataIndices.length && applyHiddenDatasetIndices.length) {
363
- applyHiddenDatasetIndices.forEach(datasetIndex => {
364
- let meta = this.chartJs.getDatasetMeta(datasetIndex);
365
- if (meta && meta.data && Array.isArray(meta.data)) {
366
- hiddenDataIndices
367
- .filter(dataIndex => meta.data.length > dataIndex)
368
- .forEach(dataIndex => {
369
- meta.data[dataIndex].hidden = true;
370
- });
371
- }
372
- });
373
- }
471
+ this.chartJs.update();
374
472
 
375
473
  this._adjustSize(this.chartJs.config, this.chartJs.chartArea);
376
474
  this.chartJs.update();
@@ -380,10 +478,12 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
380
478
  return true;
381
479
  }
382
480
 
383
- _updateChart(animated) {
384
- let config = this.chartJs.config;
385
- this._adjustColors(config);
386
- this._renderChart(config, animated);
481
+ refresh() {
482
+ if (this.chartJs) {
483
+ this.chartJs.update();
484
+ } else {
485
+ super.refresh();
486
+ }
387
487
  }
388
488
 
389
489
  _renderCheckedItems() {
@@ -431,11 +531,19 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
431
531
  transferArrayValues(dataset.pointBackgroundColor, dataset.uncheckedPointBackgroundColor, uncheckedIndices) +
432
532
  transferArrayValues(dataset.pointHoverBackgroundColor, dataset.uncheckedPointHoverBackgroundColor, uncheckedIndices) +
433
533
  transferArrayValues(dataset.pointRadius, dataset.uncheckedPointRadius, uncheckedIndices);
534
+
535
+ this._adjustDatasetBorderWidths(dataset);
434
536
  });
435
537
 
436
538
  return 0 < changed;
437
539
  }
438
540
 
541
+ stopAnimations() {
542
+ if (this.chartJs) {
543
+ this.chartJs.stop();
544
+ }
545
+ }
546
+
439
547
  _adjustConfig(config) {
440
548
  if (!config || !config.type) {
441
549
  return;
@@ -457,10 +565,15 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
457
565
  if (config.type === Chart.Type.COMBO_BAR_LINE) {
458
566
  config.type = Chart.Type.BAR;
459
567
 
460
- let scaleLabelByTypeMap = ((config.options || {}).scales || {}).scaleLabelByTypeMap;
568
+ let scaleLabelByTypeMap = (config.options || {}).scaleLabelByTypeMap;
461
569
  if (scaleLabelByTypeMap) {
462
570
  scaleLabelByTypeMap[Chart.Type.BAR] = scaleLabelByTypeMap[Chart.Type.COMBO_BAR_LINE];
463
571
  }
572
+ } else if (this._isHorizontalBar(config)) {
573
+ config.type = Chart.Type.BAR;
574
+ config.options = $.extend(true, {}, config.options, {
575
+ indexAxis: 'y'
576
+ });
464
577
  }
465
578
  }
466
579
 
@@ -470,17 +583,15 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
470
583
 
471
584
  let setLabelMap = (identifier, labelMap) => {
472
585
  if (!$.isEmptyObject(labelMap)) {
473
- config.options = $.extend(true, {}, {
474
- scales: {}
475
- }, config.options);
476
- config.options.scales[identifier] = labelMap;
586
+ config.options[identifier] = labelMap;
477
587
  }
478
588
  };
479
589
 
480
590
  (chartData.axes[0] || []).forEach(elem => labels.push(elem.label));
481
591
 
482
- setLabelMap(config.type === Chart.Type.BAR_HORIZONTAL ? 'yLabelMap' : 'xLabelMap', this._computeLabelMap(chartData.axes[0]));
483
- setLabelMap(config.type === Chart.Type.BAR_HORIZONTAL ? 'xLabelMap' : 'yLabelMap', this._computeLabelMap(chartData.axes[1]));
592
+ let isHorizontalBar = this._isHorizontalBar(config);
593
+ setLabelMap(isHorizontalBar ? 'yLabelMap' : 'xLabelMap', this._computeLabelMap(chartData.axes[0]));
594
+ setLabelMap(isHorizontalBar ? 'xLabelMap' : 'yLabelMap', this._computeLabelMap(chartData.axes[1]));
484
595
 
485
596
  chartData.chartValueGroups.forEach(elem => datasets.push({
486
597
  type: elem.type,
@@ -498,6 +609,11 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
498
609
  };
499
610
  }
500
611
 
612
+ _isHorizontalBar(config) {
613
+ return config && (config.type === Chart.Type.BAR_HORIZONTAL
614
+ || (config.type === Chart.Type.BAR && config.options && config.options.indexAxis === 'y'));
615
+ }
616
+
501
617
  _computeLabelMap(axis) {
502
618
  let labelMap = {};
503
619
  (axis || []).forEach((elem, idx) => {
@@ -518,17 +634,83 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
518
634
  }
519
635
 
520
636
  _adjustBarBorderWidth(config) {
521
- if (!config || !config.data || !config.type || !scout.isOneOf(config.type, Chart.Type.BAR, Chart.Type.BAR_HORIZONTAL)) {
637
+ if (!config || !config.data || !config.type || !scout.isOneOf(config.type, Chart.Type.BAR)) {
522
638
  return;
523
639
  }
524
640
 
525
641
  config.data.datasets.forEach(dataset => {
526
642
  if ((dataset.type || Chart.Type.BAR) === Chart.Type.BAR) {
643
+ dataset.borderWidth = dataset.borderWidth || 1;
527
644
  dataset.hoverBorderWidth = dataset.hoverBorderWidth || 2;
645
+ this._adjustDatasetBorderWidths(dataset);
528
646
  }
529
647
  });
530
648
  }
531
649
 
650
+ _adjustDatasetBorderWidths(dataset) {
651
+ this._adjustDatasetBorderWidth(dataset);
652
+ this._adjustDatasetBorderWidth(dataset, true);
653
+ }
654
+
655
+ /**
656
+ * Sets the borderWidth to 0 if the backgroundColor and the borderColor are identical and backups the original value.
657
+ * This method is idempotent as it restores the original state first and then applies its logic.
658
+ *
659
+ * @param {Dataset} dataset
660
+ * @param {boolean?} hover whether hoverBorderWidth, hoverBackgroundColor and hoverBorderColor should be considered instead of borderWidth, backgroundColor and borderColor
661
+ */
662
+ _adjustDatasetBorderWidth(dataset, hover) {
663
+ if (!dataset) {
664
+ return;
665
+ }
666
+ let borderWidthIdentifier = hover ? 'hoverBorderWidth' : 'borderWidth',
667
+ borderWidthBackupIdentifier = hover ? 'hoverBorderWidthBackup' : 'borderWidthBackup',
668
+ backgroundColorIdentifier = hover ? 'hoverBackgroundColor' : 'backgroundColor',
669
+ borderColorIdentifier = hover ? 'hoverBorderColor' : 'borderColor';
670
+ // restore original state if there is an backup
671
+ if (dataset[borderWidthBackupIdentifier]) {
672
+ dataset[borderWidthIdentifier] = dataset[borderWidthBackupIdentifier];
673
+ delete dataset[borderWidthBackupIdentifier];
674
+ }
675
+ // do nothing if there is no borderWidth set on the dataset or the borderWidth is a function
676
+ if (!dataset[borderWidthIdentifier] || objects.isFunction(dataset[borderWidthIdentifier])) {
677
+ return;
678
+ }
679
+ let isBorderWidthArray = Array.isArray(dataset[borderWidthIdentifier]),
680
+ isBackgroundColorArray = Array.isArray(dataset[backgroundColorIdentifier]),
681
+ isBorderColorArray = Array.isArray(dataset[borderColorIdentifier]),
682
+ isArray = isBorderWidthArray || isBackgroundColorArray || isBorderColorArray;
683
+ // if none of the properties is an array, simply backup the borderWidth and set it to 0
684
+ if (!isArray && dataset[backgroundColorIdentifier] === dataset[borderColorIdentifier]) {
685
+ dataset[borderWidthBackupIdentifier] = dataset[borderWidthIdentifier];
686
+ dataset[borderWidthIdentifier] = 0;
687
+ return;
688
+ }
689
+ // at least one of the properties is an array, therefore the borderWidth needs to be an array from now on
690
+ let dataLength = (dataset.data || []).length;
691
+ if (!isBorderWidthArray) {
692
+ dataset[borderWidthIdentifier] = arrays.init(dataLength, dataset[borderWidthIdentifier]);
693
+ } else if (dataset[borderWidthIdentifier].length < dataLength) {
694
+ dataset[borderWidthIdentifier].push(...arrays.init(dataLength - dataset[borderWidthIdentifier].length, dataset[borderWidthIdentifier][0]));
695
+ }
696
+ let borderWidth = dataset[borderWidthIdentifier],
697
+ length = borderWidth.length,
698
+ borderWidthBackup = arrays.init(length);
699
+ for (let i = 0; i < length; i++) {
700
+ // it makes no difference if the backgroundColor/borderColor is not an array as a not-array-value is applied to every element by chart.js
701
+ let backgroundColor = isBackgroundColorArray ? dataset[backgroundColorIdentifier][i] : dataset[backgroundColorIdentifier],
702
+ borderColor = isBorderColorArray ? dataset[borderColorIdentifier][i] : dataset[borderColorIdentifier];
703
+ borderWidthBackup[i] = borderWidth[i];
704
+ if (backgroundColor === borderColor) {
705
+ borderWidth[i] = 0;
706
+ }
707
+ }
708
+ // only set the backup if at least one of the borderWidths changed
709
+ if (!arrays.equals(borderWidth, borderWidthBackup)) {
710
+ dataset[borderWidthBackupIdentifier] = borderWidthBackup;
711
+ }
712
+ }
713
+
532
714
  _adjustMaxSegments(config) {
533
715
  if (!config || !config.data || !config.type || !scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA, Chart.Type.RADAR)) {
534
716
  return;
@@ -565,6 +747,11 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
565
747
  return config.options.maxSegments - 1 <= index;
566
748
  }
567
749
 
750
+ /**
751
+ * Fill temporary variable z for every bubble, if not yet set, and set bubble radius temporary to 1.
752
+ * This allows the chart to render itself with correct dimensions and with no interfering from bubbles (very large bubbles make the chart grid itself smaller).
753
+ * Later on in {@link _adjustBubbleSizes}, the bubble sizes will be calculated relative to the chart dimensions and the configured min/max sizes.
754
+ */
568
755
  _adjustBubbleRadii(config) {
569
756
  if (!config || !config.data || !config.type || config.type !== Chart.Type.BUBBLE) {
570
757
  return;
@@ -573,9 +760,8 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
573
760
  config.data.datasets.forEach(dataset => dataset.data.forEach(data => {
574
761
  if (!isNaN(data.r)) {
575
762
  data.z = Math.pow(data.r, 2);
576
- } else if (!isNaN(data.z)) {
577
- data.r = Math.sqrt(data.z);
578
763
  }
764
+ data.r = 1;
579
765
  }));
580
766
  }
581
767
 
@@ -599,138 +785,308 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
599
785
  }
600
786
 
601
787
  config.options = $.extend(true, {}, {
602
- hover: {
603
- mode: 'nearest'
604
- },
605
- tooltips: {
606
- mode: 'nearest',
607
- callbacks: {
608
- title: this._tooltipTitle,
609
- label: this._tooltipLabel,
610
- labelColor: this._tooltipLabelColor
788
+ plugins: {
789
+ tooltip: {
790
+ callbacks: {
791
+ title: this._tooltipTitleGenerator,
792
+ label: this._tooltipLabelGenerator,
793
+ labelValue: this._tooltipLabelValueGenerator,
794
+ labelColor: this._tooltipLabelColorGenerator
795
+ }
611
796
  }
612
797
  }
613
798
  }, config.options);
799
+
800
+ let tooltip = config.options.plugins.tooltip;
801
+
802
+ if (!tooltip.enabled) {
803
+ return;
804
+ }
805
+
806
+ tooltip.enabled = false;
807
+ tooltip.external = this._tooltipRenderer;
614
808
  }
615
809
 
616
- _formatTooltipTitle(tooltipItems, data) {
617
- let config = this.chartJs.config,
618
- ctx = this.chartJs.ctx,
619
- tooltipItem = tooltipItems[0],
620
- title,
621
- defaultGlobal = ChartJs.defaults.global,
622
- defaultTypeTooltips = {},
623
- defaultGlobalTooltips = defaultGlobal.tooltips;
624
- if (ChartJs.defaults[config.type]) {
625
- defaultTypeTooltips = $.extend(true, {}, defaultTypeTooltips, ChartJs.defaults[config.type].tooltips);
626
- }
627
- if (scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA, Chart.Type.LINE, Chart.Type.BAR, Chart.Type.BAR_HORIZONTAL, Chart.Type.RADAR)) {
628
- let label = data.labels[tooltipItem.index];
629
- title = config.options.reformatLabels ? this._formatLabel(label) : label;
630
- } else if (config.type === Chart.Type.BUBBLE) {
631
- let xAxis = config.options.scales.xAxes[0],
632
- yAxis = config.options.scales.yAxes[0],
633
- xAxisLabel = xAxis.scaleLabel.labelString,
634
- yAxisLabel = yAxis.scaleLabel.labelString;
635
- xAxisLabel = xAxisLabel ? (xAxisLabel + ':') : ChartJsRenderer.ARROW_LEFT_RIGHT;
636
- yAxisLabel = yAxisLabel ? (yAxisLabel + ':') : ' ' + ChartJsRenderer.ARROW_UP_DOWN + ' ';
810
+ _generateTooltipTitle(tooltipItems) {
811
+ if (!tooltipItems || !tooltipItems.length) {
812
+ return '';
813
+ }
814
+ let tooltipItem = tooltipItems[0],
815
+ chart = tooltipItem.chart,
816
+ config = chart.config,
817
+ dataset = tooltipItem.dataset,
637
818
  title = [];
638
- let xTickLabel = xAxis.ticks.callback(tooltipItem.xLabel);
819
+ if (config.type === Chart.Type.BUBBLE) {
820
+ let xAxis = config.options.scales.x,
821
+ yAxis = config.options.scales.y,
822
+ xAxisLabel = xAxis.title.text,
823
+ yAxisLabel = yAxis.title.text;
824
+ xAxisLabel = xAxisLabel ? strings.encode(xAxisLabel) : ChartJsRenderer.ARROW_LEFT_RIGHT;
825
+ yAxisLabel = yAxisLabel ? strings.encode(yAxisLabel) : '&nbsp;' + ChartJsRenderer.ARROW_UP_DOWN + '&nbsp;';
826
+ let xTickLabel = xAxis.ticks.callback(dataset.data[tooltipItem.dataIndex].x);
639
827
  if (xTickLabel) {
640
- title.push(xAxisLabel + ' ' + xTickLabel);
828
+ title.push(this._createTooltipAttribute(xAxisLabel, strings.encode(xTickLabel), true));
641
829
  }
642
- let yTickLabel = yAxis.ticks.callback(tooltipItem.yLabel);
830
+ let yTickLabel = yAxis.ticks.callback(dataset.data[tooltipItem.dataIndex].y);
643
831
  if (yTickLabel) {
644
- title.push(yAxisLabel + ' ' + yTickLabel);
832
+ title.push(this._createTooltipAttribute(yAxisLabel, strings.encode(yTickLabel), true));
645
833
  }
646
834
  } else {
647
- let defaultTypeTooltipTitle;
648
- if (defaultTypeTooltips.callbacks) {
649
- defaultTypeTooltipTitle = defaultTypeTooltips.callbacks.title;
650
- }
651
- let defaultTooltipTitle = defaultTypeTooltipTitle || defaultGlobalTooltips.callbacks.title;
652
- title = defaultTooltipTitle.call(this.chartJs, tooltipItems, data);
653
- }
654
- let horizontalSpace = this.$canvas.cssWidth() - (2 * config.options.tooltips.xPadding),
655
- measureText = ctx.measureText.bind(ctx),
656
- oldFont = ctx.font,
657
- titleFontStyle = config.options.tooltips.titleFontStyle || defaultTypeTooltips.titleFontStyle || defaultGlobalTooltips.titleFontStyle || defaultGlobal.defaultFontStyle,
658
- titleFontSize = config.options.tooltips.titleFontSize || defaultTypeTooltips.titleFontSize || defaultGlobalTooltips.titleFontSize || defaultGlobal.defaultFontSize,
659
- titleFontFamily = config.options.tooltips.titleFontFamily || defaultTypeTooltips.titleFontFamily || defaultGlobalTooltips.titleFontFamily || defaultGlobal.defaultFontFamily,
660
- result = [];
661
- ctx.font = titleFontStyle + ' ' + titleFontSize + 'px ' + titleFontFamily;
662
- arrays.ensure(title).forEach(titleLine => result.push(strings.truncateText(titleLine, horizontalSpace, measureText)));
663
- ctx.font = oldFont;
664
- return result;
665
- }
666
-
667
- _formatTooltipLabel(tooltipItem, data) {
668
- let config = this.chartJs.config,
669
- ctx = this.chartJs.ctx,
670
- datasets = data ? data.datasets : null,
671
- dataset = datasets ? datasets[tooltipItem.datasetIndex] : null,
672
- label, value,
673
- defaultGlobal = ChartJs.defaults.global,
674
- defaultTypeTooltips = {},
675
- defaultGlobalTooltips = defaultGlobal.tooltips;
676
- if (ChartJs.defaults[config.type]) {
677
- defaultTypeTooltips = $.extend(true, {}, defaultTypeTooltips, ChartJs.defaults[config.type].tooltips);
678
- }
679
- if (scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA, Chart.Type.LINE, Chart.Type.BAR, Chart.Type.BAR_HORIZONTAL, Chart.Type.RADAR)) {
680
- label = dataset.label;
681
- value = this._formatLabel(dataset.data[tooltipItem.index]);
682
- } else if (config.type === Chart.Type.BUBBLE) {
683
- label = dataset.label;
684
- value = this._formatLabel(dataset.data[tooltipItem.index].z);
685
- } else {
686
- let defaultTypeTooltipLabel;
687
- if (defaultTypeTooltips.callbacks) {
688
- defaultTypeTooltipLabel = defaultTypeTooltips.callbacks.label;
689
- }
690
- let defaultTooltipLabel = defaultTypeTooltipLabel || defaultGlobalTooltips.callbacks.label;
691
- label = defaultTooltipLabel.call(this.chartJs, tooltipItem, data);
692
- }
693
- label = ' ' + label;
694
- value = value ? ' ' + value : '';
695
- let colorRectSize = config.options.tooltips.displayColors ? config.options.tooltips.bodyFontSize || defaultTypeTooltips.bodyFontSize || defaultGlobalTooltips.bodyFontSize || defaultGlobal.defaultFontSize : 0,
696
- horizontalSpace = this.$canvas.cssWidth() - (2 * config.options.tooltips.xPadding) - colorRectSize,
697
- measureText = ctx.measureText.bind(ctx),
698
- result = label + (value ? ':' + value : '');
699
- if (measureText(result).width > horizontalSpace) {
700
- if (measureText(value).width > horizontalSpace / 2) {
701
- return strings.truncateText(value, horizontalSpace, measureText);
702
- }
703
- return strings.truncateText(label, horizontalSpace - measureText(value ? ':' + value : '').width, measureText) + (value ? ':' + value : '');
835
+ let label = chart.data.labels[tooltipItem.dataIndex];
836
+ title.push(this._createTooltipAttribute(config.options.reformatLabels ? this._formatLabel(label) : label, '', true));
704
837
  }
705
- return result;
838
+ return title;
706
839
  }
707
840
 
708
- _computeTooltipLabelColor(tooltipItem, chart) {
709
- let config = chart.config,
710
- tooltips = config.options ? config.options.tooltips : null,
711
- tooltipBackgroundColor = tooltips ? tooltips.backgroundColor : null,
712
- datasets = chart.data ? chart.data.datasets : null,
713
- dataset = datasets ? datasets[tooltipItem.datasetIndex] : null,
714
- backgroundColor;
841
+ _generateTooltipLabel(tooltipItem) {
842
+ return strings.encode(tooltipItem.dataset.label);
843
+ }
844
+
845
+ _generateTooltipLabelValue(tooltipItem) {
846
+ let config = tooltipItem.chart.config,
847
+ dataset = tooltipItem.dataset;
848
+ if (config.type === Chart.Type.BUBBLE) {
849
+ return strings.encode(this._formatLabel(dataset.data[tooltipItem.dataIndex].z));
850
+ }
851
+ return strings.encode(this._formatLabel(dataset.data[tooltipItem.dataIndex]));
852
+ }
853
+
854
+ _generateTooltipLabelColor(tooltipItem) {
855
+ let config = tooltipItem.chart.config,
856
+ dataset = tooltipItem.dataset,
857
+ legendColor, backgroundColor, borderColor, index;
715
858
  if (scout.isOneOf((dataset.type || config.type), Chart.Type.LINE, Chart.Type.BAR, Chart.Type.BAR_HORIZONTAL, Chart.Type.RADAR, Chart.Type.BUBBLE)) {
716
- backgroundColor = dataset.legendColor || dataset.borderColor;
859
+ borderColor = dataset.borderColor;
860
+ legendColor = dataset.legendColor;
861
+ index = tooltipItem.datasetIndex;
717
862
  }
718
863
  if (scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
719
- let legendColor = Array.isArray(dataset.legendColor) ? dataset.legendColor[tooltipItem.index] : dataset.legendColor,
720
- datasetBackgroundColor = Array.isArray(dataset.backgroundColor) ? dataset.backgroundColor[tooltipItem.index] : dataset.backgroundColor;
721
- backgroundColor = legendColor || this._adjustColorOpacity(datasetBackgroundColor, 1);
864
+ legendColor = Array.isArray(dataset.legendColor) ? dataset.legendColor[tooltipItem.dataIndex] : dataset.legendColor;
865
+ backgroundColor = Array.isArray(dataset.backgroundColor) ? dataset.backgroundColor[tooltipItem.dataIndex] : dataset.backgroundColor;
866
+ backgroundColor = this._adjustColorOpacity(backgroundColor, 1);
867
+ index = tooltipItem.dataIndex;
868
+ }
869
+ if (objects.isFunction(legendColor)) {
870
+ legendColor = legendColor.call(tooltipItem.chart, index);
722
871
  }
723
- if (!backgroundColor || typeof backgroundColor === 'function') {
872
+ let tooltipLabelColor = legendColor || backgroundColor || borderColor;
873
+ if (!tooltipLabelColor || objects.isFunction(tooltipLabelColor)) {
724
874
  let defaultTypeTooltipLabelColor;
725
- if (ChartJs.defaults[config.type] && ChartJs.defaults[config.type].callbacks) {
726
- defaultTypeTooltipLabelColor = ChartJs.defaults[config.type].callbacks.labelColor;
875
+ // noinspection DuplicatedCode
876
+ if (ChartJs.overrides[config.type] && ChartJs.overrides[config.type].plugins && ChartJs.overrides[config.type].plugins.tooltip && ChartJs.overrides[config.type].plugins.tooltip.callbacks) {
877
+ defaultTypeTooltipLabelColor = ChartJs.overrides[config.type].plugins.tooltip.callbacks.labelColor;
727
878
  }
728
- let defaultTooltipLabelColor = defaultTypeTooltipLabelColor || ChartJs.defaults.global.tooltips.callbacks.labelColor;
729
- backgroundColor = defaultTooltipLabelColor.call(chart, tooltipItem, chart).backgroundColor;
879
+ let defaultTooltipLabelColor = defaultTypeTooltipLabelColor || ChartJs.defaults.plugins.tooltip.callbacks.labelColor;
880
+ tooltipLabelColor = defaultTooltipLabelColor.call(tooltipItem.chart, tooltipItem).backgroundColor;
730
881
  }
731
882
  return {
732
- borderColor: tooltipBackgroundColor,
733
- backgroundColor: backgroundColor
883
+ backgroundColor: tooltipLabelColor
884
+ };
885
+ }
886
+
887
+ _createTooltipAttribute(label, value, isTitle, color) {
888
+ let cssClass = isTitle ? 'attribute title' : 'attribute';
889
+ return '<div class="' + cssClass + '">' +
890
+ (color ? '<div class="color" style="background-color:' + color + '"></div>' : '') +
891
+ (label ? '<label>' + label + '</label>' : '') +
892
+ (value ? '<div class="value">' + value + '</div>' : '') +
893
+ '</div>';
894
+ }
895
+
896
+ _renderTooltip(context) {
897
+ let isHideTooltip = context.tooltip.opacity === 0 || context.tooltip._tooltipItems.length < 1;
898
+ if (isHideTooltip) {
899
+ if (this._tooltipTimeoutId) {
900
+ clearTimeout(this._tooltipTimeoutId);
901
+ this._tooltipTimeoutId = undefined;
902
+ }
903
+ if (this._tooltip) {
904
+ this._tooltip.destroy();
905
+ this._tooltip = null;
906
+ }
907
+ return;
908
+ }
909
+
910
+ let isTooltipShowing = !!this._tooltip;
911
+ if (isTooltipShowing) {
912
+ this._renderTooltipLater(context);
913
+ } else {
914
+ this._tooltipTimeoutId = setTimeout(() => this._renderTooltipLater(context), tooltips.DEFAULT_TOOLTIP_DELAY);
915
+ }
916
+ }
917
+
918
+ _renderTooltipLater(context) {
919
+ if (!this.rendered || this.removing) {
920
+ return;
921
+ }
922
+ let tooltip = context.tooltip,
923
+ tooltipItems = tooltip._tooltipItems;
924
+ if (tooltipItems.length < 1) {
925
+ return;
926
+ }
927
+ if (this._tooltip) {
928
+ this._tooltip.destroy();
929
+ this._tooltip = null;
930
+ }
931
+
932
+ let tooltipOptions = tooltip.options || {},
933
+ tooltipCallbacks = tooltipOptions.callbacks || {},
934
+ tooltipTitle = tooltipCallbacks.title,
935
+ tooltipLabel = tooltipCallbacks.label,
936
+ tooltipLabelValue = tooltipCallbacks.labelValue,
937
+ tooltipColor = tooltipCallbacks.labelColor,
938
+ tooltipText = '';
939
+
940
+ if (objects.isFunction(tooltipTitle)) {
941
+ tooltipText += arrays.ensure(tooltipTitle(tooltipItems)).join('');
942
+ }
943
+ tooltipItems.forEach(tooltipItem => {
944
+ let label, labelValue, labelColor;
945
+ if (objects.isFunction(tooltipLabel)) {
946
+ label = tooltipLabel(tooltipItem);
947
+ label = objects.isString(label) ? label : '';
948
+ }
949
+ if (objects.isFunction(tooltipLabelValue)) {
950
+ labelValue = tooltipLabelValue(tooltipItem);
951
+ labelValue = objects.isString(labelValue) ? labelValue : '';
952
+ }
953
+ if (objects.isFunction(tooltipColor)) {
954
+ labelColor = tooltipColor(tooltipItem);
955
+ labelColor = objects.isPlainObject(labelColor) ? (labelColor.backgroundColor || '') : '';
956
+ }
957
+ tooltipText += this._createTooltipAttribute(label, labelValue, false, labelColor);
958
+ });
959
+
960
+ let positionAndOffset = this._computeTooltipPositionAndOffset(tooltipItems[0]),
961
+ origin = graphics.offsetBounds(this.$canvas);
962
+ origin.x += tooltip.caretX + positionAndOffset.offsetX;
963
+ origin.y += tooltip.caretY + positionAndOffset.offsetY;
964
+ // Tooltip adds origin.height to origin.y in 'bottom' mode, but origin.y is already the correct value for 'top' and 'bottom' mode
965
+ origin.height = 0;
966
+
967
+ this._tooltip = scout.create({
968
+ objectType: 'Tooltip',
969
+ parent: this.chart,
970
+ $anchor: this.$canvas,
971
+ text: tooltipText,
972
+ htmlEnabled: true,
973
+ cssClass: strings.join(' ', 'chart-tooltip', tooltipOptions.cssClass),
974
+ tooltipPosition: positionAndOffset.tooltipPosition,
975
+ tooltipDirection: positionAndOffset.tooltipDirection,
976
+ origin: origin
977
+ });
978
+ this._tooltip.render();
979
+
980
+ this._tooltip.$container
981
+ .css('pointer-events', 'none');
982
+
983
+ if ((tooltipOptions.titleFont || {}).family) {
984
+ this._tooltip.$container
985
+ .css('--chart-tooltip-font-family', tooltipOptions.titleFont.family);
986
+ }
987
+
988
+ let maxLabelPrefSize = 0;
989
+ this._tooltip.$container.find('label').each((idx, elem) => maxLabelPrefSize = Math.max(maxLabelPrefSize, graphics.prefSize($(elem)).width));
990
+ if (maxLabelPrefSize > 0) {
991
+ this._tooltip.$container
992
+ .css('--chart-tooltip-label-width', Math.min(maxLabelPrefSize, 120) + 'px');
993
+ }
994
+ }
995
+
996
+ _computeTooltipPositionAndOffset(tooltipItem) {
997
+ let tooltipPosition = 'top',
998
+ tooltipDirection = 'right',
999
+ offsetX = 0,
1000
+ offsetY = 0;
1001
+
1002
+ let chart = tooltipItem.chart,
1003
+ datasetIndex = tooltipItem.datasetIndex,
1004
+ dataIndex = tooltipItem.dataIndex,
1005
+ config = chart.config,
1006
+ datasets = config.data.datasets,
1007
+ dataset = datasets[datasetIndex],
1008
+ value = dataset.data[dataIndex];
1009
+
1010
+ if (this._isHorizontalBar(config)) {
1011
+ tooltipDirection = value < 0 ? 'left' : 'right';
1012
+ } else if ((dataset.type || config.type) === Chart.Type.BAR) {
1013
+ tooltipPosition = value < 0 ? 'bottom' : 'top';
1014
+ } else if (scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
1015
+ // noinspection JSValidateTypes
1016
+ /**
1017
+ * @type ArcElement
1018
+ */
1019
+ let element = chart.getDatasetMeta(datasetIndex).data[dataIndex];
1020
+ let startAngle = element.startAngle,
1021
+ endAngle = element.endAngle,
1022
+ angle = (startAngle + endAngle) / 2;
1023
+ tooltipPosition = (0 <= angle && angle < Math.PI) ? 'bottom' : 'top';
1024
+ tooltipDirection = (-Math.PI / 2 <= angle && angle < Math.PI / 2) ? 'right' : 'left';
1025
+ } else if (config.type === Chart.Type.RADAR) {
1026
+ // noinspection JSValidateTypes
1027
+ /**
1028
+ * @type PointElement
1029
+ */
1030
+ let element = chart.getDatasetMeta(datasetIndex).data[dataIndex];
1031
+ let angle = element.angle;
1032
+ tooltipPosition = (0 <= angle && angle < Math.PI) ? 'bottom' : 'top';
1033
+ tooltipDirection = (-Math.PI / 2 <= angle && angle < Math.PI / 2) ? 'right' : 'left';
1034
+ } else if (config.type === Chart.Type.BUBBLE) {
1035
+ let element = chart.getDatasetMeta(datasetIndex).data[dataIndex];
1036
+ let chartArea = chart.chartArea,
1037
+ mid = chartArea.left + (chartArea.width / 2);
1038
+ tooltipDirection = element.x < mid ? 'left' : 'right';
1039
+ }
1040
+
1041
+ if (this._isHorizontalBar(config)) {
1042
+ // noinspection JSValidateTypes
1043
+ /**
1044
+ * @type BarElement
1045
+ */
1046
+ let element = chart.getDatasetMeta(datasetIndex).data[dataIndex];
1047
+ let height = element.height,
1048
+ width = element.width,
1049
+ // golden ratio: (a + b) / a = a / b = PHI
1050
+ // and a + b = width
1051
+ // -> b = width / (PHI + 1)
1052
+ b = width / (PHI + 1);
1053
+
1054
+ offsetY = -height / 2;
1055
+ offsetX = tooltipDirection === 'left' ? b : -b;
1056
+ } else if (scout.isOneOf(config.type, Chart.Type.LINE, Chart.Type.BUBBLE, Chart.Type.RADAR) || dataset.type === Chart.Type.LINE) {
1057
+ // noinspection JSValidateTypes
1058
+ /**
1059
+ * @type PointElement
1060
+ */
1061
+ let element = chart.getDatasetMeta(datasetIndex).data[dataIndex];
1062
+ let options = element.options,
1063
+ offset = options.hoverRadius + options.hoverBorderWidth;
1064
+ if (config.type === Chart.Type.BUBBLE) {
1065
+ offset += value.r;
1066
+ }
1067
+
1068
+ offsetY = tooltipPosition === 'top' ? -offset : offset;
1069
+ } else if (scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
1070
+ // noinspection JSValidateTypes
1071
+ /**
1072
+ * @type ArcElement
1073
+ */
1074
+ let element = chart.getDatasetMeta(datasetIndex).data[dataIndex];
1075
+ let startAngle = element.startAngle,
1076
+ endAngle = element.endAngle,
1077
+ angle = (startAngle + endAngle) / 2,
1078
+ innerRadius = element.innerRadius,
1079
+ outerRadius = element.outerRadius,
1080
+ offset = (outerRadius - innerRadius) / 2;
1081
+ offsetX = offset * Math.cos(angle);
1082
+ offsetY = offset * Math.sin(angle);
1083
+ }
1084
+
1085
+ return {
1086
+ tooltipPosition: tooltipPosition,
1087
+ tooltipDirection: tooltipDirection,
1088
+ offsetX: offsetX,
1089
+ offsetY: offsetY
734
1090
  };
735
1091
  }
736
1092
 
@@ -741,139 +1097,140 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
741
1097
 
742
1098
  config.options = $.extend(true, {}, config.options);
743
1099
 
744
- this._adjustScale(config);
745
- this._adjustScales(config);
1100
+ this._adjustScalesR(config);
1101
+ this._adjustScalesXY(config);
746
1102
  }
747
1103
 
748
- _adjustScale(config) {
1104
+ _adjustScalesR(config) {
749
1105
  if (!config || !config.type || !config.options) {
750
1106
  return;
751
1107
  }
752
1108
 
753
1109
  if (scout.isOneOf(config.type, Chart.Type.POLAR_AREA, Chart.Type.RADAR)) {
754
1110
  config.options = $.extend(true, {}, {
755
- scale: {}
1111
+ scales: {
1112
+ r: {}
1113
+ }
756
1114
  }, config.options);
757
1115
  }
758
1116
 
759
- let options = config.options;
760
- if (options.scale) {
761
- options.scale = $.extend(true, {}, {
1117
+ let options = config.options,
1118
+ scales = options ? options.scales : {};
1119
+ if (scales && scales.r) {
1120
+ scales.r = $.extend(true, {}, {
1121
+ minSpaceBetweenTicks: 35,
1122
+ beginAtZero: true,
762
1123
  angleLines: {
763
1124
  display: false
764
1125
  },
765
- gridLines: {
766
- borderDash: [2, 4]
767
- },
768
1126
  ticks: {
769
- beginAtZero: true,
770
1127
  callback: this._labelFormatter
771
1128
  },
772
1129
  pointLabels: {
773
- fontSize: ChartJs.defaults.global.defaultFontSize
1130
+ font: {
1131
+ size: ChartJs.defaults.font.size
1132
+ }
774
1133
  }
775
- }, options.scale);
1134
+ }, scales.r);
776
1135
  }
777
1136
  }
778
1137
 
779
- _adjustScales(config) {
1138
+ _adjustScalesXY(config) {
780
1139
  if (!config || !config.type || !config.options) {
781
1140
  return;
782
1141
  }
783
1142
 
784
- if (scout.isOneOf(config.type, Chart.Type.BAR, Chart.Type.BAR_HORIZONTAL, Chart.Type.LINE, Chart.Type.BUBBLE)) {
1143
+ if (scout.isOneOf(config.type, Chart.Type.BAR, Chart.Type.LINE, Chart.Type.BUBBLE)) {
785
1144
  config.options = $.extend(true, {}, {
786
1145
  scales: {
787
- xAxes: [{}],
788
- yAxes: [{}]
1146
+ x: {
1147
+ minSpaceBetweenTicks: 150
1148
+ },
1149
+ y: {
1150
+ minSpaceBetweenTicks: 35
1151
+ }
789
1152
  }
790
1153
  }, config.options);
791
1154
  }
792
1155
 
793
- this._adjustXAxes(config);
794
- this._adjustYAxes(config);
1156
+ this._adjustXAxis(config);
1157
+ this._adjustYAxis(config);
795
1158
  }
796
1159
 
797
- _adjustXAxes(config) {
798
- if (!config || !config.type || !config.options || !config.options.scales || !config.options.scales.xAxes) {
1160
+ _adjustXAxis(config) {
1161
+ if (!config || !config.type || !config.options || !config.options.scales || !config.options.scales.x) {
799
1162
  return;
800
1163
  }
801
1164
 
802
1165
  let type = config.type,
803
- xAxes = config.options.scales.xAxes;
804
-
805
- for (let i = 0; i < xAxes.length; i++) {
806
- if (scout.isOneOf(type, Chart.Type.BAR_HORIZONTAL, Chart.Type.BUBBLE)) {
807
- xAxes[i] = $.extend(true, {}, {
808
- offset: type === Chart.Type.BUBBLE,
809
- gridLines: {
810
- drawBorder: false,
811
- drawTicks: false,
812
- zeroLineBorderDash: [2, 4],
813
- borderDash: [2, 4]
814
- },
815
- ticks: {
816
- padding: 5,
817
- beginAtZero: type === Chart.Type.BAR_HORIZONTAL
818
- }
819
- }, xAxes[i]);
820
- } else {
821
- xAxes[i] = $.extend(true, {}, {
822
- offset: true,
823
- gridLines: {
824
- display: false
825
- }
826
- }, xAxes[i]);
827
- }
828
- if (scout.isOneOf(type, Chart.Type.BAR_HORIZONTAL, Chart.Type.BUBBLE) || config.options.reformatLabels) {
829
- xAxes[i] = $.extend(true, {}, {
830
- ticks: {
831
- callback: this._xLabelFormatter
832
- }
833
- }, xAxes[i]);
834
- }
835
- xAxes[i].afterCalculateTickRotation = this._xAxisFitter;
1166
+ scales = config.options.scales;
1167
+
1168
+ if (this._isHorizontalBar(config) || type === Chart.Type.BUBBLE) {
1169
+ scales.x = $.extend(true, {}, {
1170
+ beginAtZero: this._isHorizontalBar(config),
1171
+ offset: type === Chart.Type.BUBBLE,
1172
+ grid: {
1173
+ drawBorder: false,
1174
+ drawTicks: false
1175
+ },
1176
+ ticks: {
1177
+ padding: 5
1178
+ }
1179
+ }, scales.x);
1180
+ } else {
1181
+ scales.x = $.extend(true, {}, {
1182
+ offset: true,
1183
+ grid: {
1184
+ display: false,
1185
+ drawBorder: false
1186
+ }
1187
+ }, scales.x);
836
1188
  }
1189
+ if (this._isHorizontalBar(config) || type === Chart.Type.BUBBLE || config.options.reformatLabels) {
1190
+ scales.x = $.extend(true, {}, {
1191
+ ticks: {
1192
+ callback: this._xLabelFormatter
1193
+ }
1194
+ }, scales.x);
1195
+ }
1196
+ scales.x.afterCalculateLabelRotation = this._xAxisFitter;
837
1197
  }
838
1198
 
839
- _adjustYAxes(config) {
840
- if (!config || !config.type || !config.options || !config.options.scales || !config.options.scales.yAxes) {
1199
+ _adjustYAxis(config) {
1200
+ if (!config || !config.type || !config.options || !config.options.scales || !config.options.scales.y) {
841
1201
  return;
842
1202
  }
843
1203
 
844
1204
  let type = config.type,
845
- yAxes = config.options.scales.yAxes;
1205
+ scales = config.options.scales;
846
1206
 
847
- for (let i = 0; i < yAxes.length; i++) {
848
- if (type === Chart.Type.BAR_HORIZONTAL) {
849
- yAxes[i] = $.extend(true, {}, {
850
- gridLines: {
851
- display: false
852
- }
853
- }, yAxes[i]);
854
- } else {
855
- yAxes[i] = $.extend(true, {}, {
856
- gridLines: {
857
- drawBorder: false,
858
- drawTicks: false,
859
- zeroLineBorderDash: [2, 4],
860
- borderDash: [2, 4]
861
- },
862
- ticks: {
863
- padding: 5,
864
- beginAtZero: type !== Chart.Type.BUBBLE
865
- }
866
- }, yAxes[i]);
867
- }
868
- if (type !== Chart.Type.BAR_HORIZONTAL || config.options.reformatLabels) {
869
- yAxes[i] = $.extend(true, {}, {
870
- ticks: {
871
- callback: this._yLabelFormatter
872
- }
873
- }, yAxes[i]);
874
- }
875
- yAxes[i].afterFit = this._yAxisFitter;
1207
+ if (this._isHorizontalBar(config)) {
1208
+ scales.y = $.extend(true, {}, {
1209
+ grid: {
1210
+ display: false,
1211
+ drawBorder: false
1212
+ }
1213
+ }, scales.y);
1214
+ } else {
1215
+ scales.y = $.extend(true, {}, {
1216
+ beginAtZero: type !== Chart.Type.BUBBLE,
1217
+ grid: {
1218
+ drawBorder: false,
1219
+ drawTicks: false
1220
+ },
1221
+ ticks: {
1222
+ padding: 5
1223
+ }
1224
+ }, scales.y);
876
1225
  }
1226
+ if (!this._isHorizontalBar(config) || config.options.reformatLabels) {
1227
+ scales.y = $.extend(true, {}, {
1228
+ ticks: {
1229
+ callback: this._yLabelFormatter
1230
+ }
1231
+ }, scales.y);
1232
+ }
1233
+ scales.y.afterFit = this._yAxisFitter;
877
1234
  }
878
1235
 
879
1236
  _adjustPlugins(config) {
@@ -891,10 +1248,61 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
891
1248
  formatter: this._radialChartDatalabelsFormatter
892
1249
  }, plugins.datalabels);
893
1250
  plugins.datalabels.display = this._radialChartDatalabelsDisplayHandler;
894
- } else if (scout.isOneOf(config.type, Chart.Type.BAR, Chart.Type.BAR_HORIZONTAL, Chart.Type.LINE, Chart.Type.POLAR_AREA, Chart.Type.RADAR, Chart.Type.BUBBLE)) {
1251
+ // since the this._radialChartDatalabelsDisplayHandler depends on values that are animated, we need to update the labels during the animation
1252
+ this._updatingDatalabels = false;
1253
+ let animation = config.options.animation || {},
1254
+ onProgress = animation.onProgress,
1255
+ onComplete = animation.onComplete,
1256
+ updateDatalabels = animation => {
1257
+ if (this._updatingDatalabels) {
1258
+ return;
1259
+ }
1260
+ this._updatingDatalabels = true;
1261
+ // invert the _actives of the datalabel plugin and call its afterEvent-hook
1262
+ // this hook will update its _actives to chart.getActiveElements() and recalculate the labels for all elements that changed
1263
+ // setting _actives to the complement of chart.getActiveElements() guarantees that all labels are updated
1264
+ let chart = animation.chart,
1265
+ metas = chart.getSortedVisibleDatasetMetas(),
1266
+ activeElements = [...chart.getActiveElements()],
1267
+ inactiveElements = [];
1268
+
1269
+ metas.forEach((meta, datasetIndex) => {
1270
+ meta.data.forEach((element, index) => {
1271
+ let activeIndex = arrays.findIndex(activeElements, activeElement => activeElement.datasetIndex === datasetIndex && activeElement.index === index);
1272
+ if (activeIndex > 0) {
1273
+ activeElements.splice(activeIndex, 1);
1274
+ } else {
1275
+ inactiveElements.push({
1276
+ datasetIndex: datasetIndex,
1277
+ index: index,
1278
+ element: element
1279
+ });
1280
+ }
1281
+ });
1282
+ });
1283
+
1284
+ // the datalabels plugin stores its data on the chart in $datalabels (see EXPANDO_KEY in chartjs-plugin-datalabels)
1285
+ chart['$' + ChartDataLabels.id]._actives = inactiveElements;
1286
+ // noinspection JSCheckFunctionSignatures
1287
+ ChartDataLabels.afterEvent(chart);
1288
+ this._updatingDatalabels = false;
1289
+ },
1290
+ updateDatalabelsAndDefaultCallback = (animation, defaultCallback) => {
1291
+ updateDatalabels(animation);
1292
+ if (defaultCallback) {
1293
+ defaultCallback(animation);
1294
+ }
1295
+ };
1296
+
1297
+ config.options.animation = $.extend(true, {}, config.options.animation, {
1298
+ onProgress: animation => updateDatalabelsAndDefaultCallback(animation, onProgress),
1299
+ onComplete: animation => updateDatalabelsAndDefaultCallback(animation, onComplete)
1300
+ });
1301
+
1302
+ } else if (scout.isOneOf(config.type, Chart.Type.BAR, Chart.Type.LINE, Chart.Type.POLAR_AREA, Chart.Type.RADAR, Chart.Type.BUBBLE)) {
895
1303
  plugins.datalabels = $.extend(true, {}, {
896
1304
  backgroundColor: this._datalabelBackgroundColorHandler,
897
- borderRadius: 4
1305
+ borderRadius: 3
898
1306
  }, plugins.datalabels);
899
1307
  plugins.datalabels.display = 'auto';
900
1308
  }
@@ -951,8 +1359,8 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
951
1359
  }
952
1360
 
953
1361
  _getLabelMap(identifier) {
954
- if (this.chartJs && this.chartJs.config && this.chartJs.config.options && this.chartJs.config.options.scales) {
955
- return this.chartJs.config.options.scales[identifier];
1362
+ if (this.chartJs && this.chartJs.config && this.chartJs.config.options) {
1363
+ return this.chartJs.config.options[identifier];
956
1364
  }
957
1365
  }
958
1366
 
@@ -996,13 +1404,17 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
996
1404
  return this.session.locale.decimalFormat.format(Math.sign(label) * abs) + abbreviation;
997
1405
  }
998
1406
 
1407
+ /**
1408
+ * @param {Scale} xAxis
1409
+ */
999
1410
  _fitXAxis(xAxis) {
1000
1411
  if (!xAxis || xAxis.labelRotation === 0) {
1001
1412
  return;
1002
1413
  }
1003
1414
  let maxHeight = this.maxXAxesTicksHeigth,
1004
- defaultGlobal = ChartJs.defaults.global,
1005
- defaultTicks = ChartJs.defaults.scale.ticks,
1415
+ fontDefaults = ChartJs.defaults.font,
1416
+ ticksDefaults = ChartJs.defaults.scale.ticks,
1417
+ ticksFontDefaults = ticksDefaults.font || {},
1006
1418
  fontSize,
1007
1419
  maxRotation;
1008
1420
  if (this.chartJs) {
@@ -1012,10 +1424,11 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1012
1424
  }
1013
1425
  if (xAxis.options && xAxis.options.ticks) {
1014
1426
  maxRotation = xAxis.options.ticks.maxRotation;
1015
- fontSize = xAxis.options.ticks.fontSize;
1427
+ let ticksFont = xAxis.options.ticks.font || {};
1428
+ fontSize = ticksFont.size;
1016
1429
  }
1017
- maxRotation = maxRotation || defaultTicks.maxRotation;
1018
- fontSize = fontSize || defaultTicks.fontSize || defaultGlobal.defaultFontSize;
1430
+ maxRotation = maxRotation || ticksDefaults.maxRotation;
1431
+ fontSize = fontSize || ticksFontDefaults.size || fontDefaults.size;
1019
1432
  // if the chart is very narrow, chart.js sometimes calculates with a negative width of the canvas
1020
1433
  // this causes NaN for labelRotation and height
1021
1434
  if (isNaN(xAxis.labelRotation)) {
@@ -1028,13 +1441,15 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1028
1441
  // => height = sin(labelRotation) * labelLength + sin(90° - labelRotation) * fontSize
1029
1442
  // <=> labelLength = (height - sin(90° - labelRotation) * fontSize) / sin(labelRotation)
1030
1443
  maxLabelLength = (maxHeight - (fontSize * Math.sin(((90 - labelRotation) / 180) * Math.PI))) / Math.sin((labelRotation / 180) * Math.PI);
1031
- if (xAxis.longestLabelWidth > maxLabelLength) {
1444
+ let labelSizes = xAxis._labelSizes || {},
1445
+ widest = labelSizes.widest || {};
1446
+ if (widest.width > maxLabelLength) {
1032
1447
  let measureText = xAxis.ctx.measureText.bind(xAxis.ctx);
1033
- xAxis._ticks.forEach(tick => {
1448
+ xAxis.ticks.forEach(tick => {
1034
1449
  tick.label = strings.truncateText(tick.label, maxLabelLength, measureText);
1035
1450
  });
1036
1451
  // reset label sizes, chart.js will recalculate them using the new truncated labels
1037
- xAxis._labelSizes = null;
1452
+ xAxis._labelSizes = undefined;
1038
1453
  }
1039
1454
  }
1040
1455
 
@@ -1043,36 +1458,41 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1043
1458
  return;
1044
1459
  }
1045
1460
  let padding = 0,
1046
- tickMarkLength = 0;
1461
+ tickLength = 0;
1047
1462
  if (yAxis.options && yAxis.options.ticks) {
1048
1463
  padding = yAxis.options.ticks.padding || 0;
1049
1464
  }
1050
- if (yAxis.options && yAxis.options.gridLines) {
1051
- tickMarkLength = yAxis.options.gridLines.tickMarkLength || 0;
1465
+ if (yAxis.options && yAxis.options.grid) {
1466
+ tickLength = yAxis.options.grid.tickLength || 0;
1052
1467
  }
1053
- if (yAxis.longestLabelWidth > yAxis.maxWidth - padding) {
1054
- let horizontalSpace = yAxis.maxWidth - padding - tickMarkLength,
1468
+ let labelSizes = yAxis._labelSizes || {},
1469
+ widest = labelSizes.widest || {};
1470
+ if (widest.width > yAxis.maxWidth - padding) {
1471
+ let horizontalSpace = yAxis.maxWidth - padding - tickLength,
1055
1472
  measureText = yAxis.ctx.measureText.bind(yAxis.ctx);
1056
- yAxis._ticks.forEach(tick => {
1473
+ yAxis.ticks.forEach(tick => {
1057
1474
  tick.label = strings.truncateText(tick.label, horizontalSpace, measureText);
1058
1475
  });
1059
1476
  }
1060
1477
  }
1061
1478
 
1062
1479
  _displayDatalabelsOnRadialChart(context) {
1063
- let data = context.chart.getDatasetMeta(context.datasetIndex).data[context.dataIndex],
1064
- model = data._model,
1065
- // Compute the biggest circle that fits inside sector/arc with center in the middle between inner and outer radius.
1066
- // First compute a circle C1 that touches the straight boundaries of the sector/arc. Then compute a circle C2 that touches the inner and the outer radius.
1067
- // The smaller one of these two circles is the biggest possible circle that fits inside sector/arc with center in the middle between inner and outer radius.
1068
- // circle C1:
1069
- midRadius = (model.outerRadius + model.innerRadius) / 2,
1480
+ // noinspection JSValidateTypes
1481
+ /**
1482
+ * @type ArcElement
1483
+ */
1484
+ let element = context.chart.getDatasetMeta(context.datasetIndex).data[context.dataIndex];
1485
+ // Compute the biggest circle that fits inside sector/arc with center in the middle between inner and outer radius.
1486
+ // First compute a circle C1 that touches the straight boundaries of the sector/arc. Then compute a circle C2 that touches the inner and the outer radius.
1487
+ // The smaller one of these two circles is the biggest possible circle that fits inside sector/arc with center in the middle between inner and outer radius.
1488
+ // circle C1:
1489
+ let midRadius = (element.outerRadius + element.innerRadius) / 2,
1070
1490
  // If the difference between the angles is greater than pi, it is no longer possible for a circle to be inside the sector/arc and touch both straight boundaries.
1071
- angle = Math.min((model.endAngle - model.startAngle), Math.PI) / 2,
1491
+ angle = Math.min((element.endAngle - element.startAngle), Math.PI) / 2,
1072
1492
  radius1 = Math.abs(Math.sin(angle)) * midRadius,
1073
1493
  diameter1 = radius1 * 2,
1074
1494
  // circle C2:
1075
- diameter2 = model.outerRadius - model.innerRadius;
1495
+ diameter2 = element.outerRadius - element.innerRadius;
1076
1496
  return Math.min(diameter1, diameter2) > this.minRadialChartDatalabelSpace;
1077
1497
  }
1078
1498
 
@@ -1092,10 +1512,10 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1092
1512
 
1093
1513
  _computeSumOfVisibleElements(context) {
1094
1514
  let dataset = context.dataset,
1095
- meta = context.chart.getDatasetMeta(context.datasetIndex),
1515
+ chart = context.chart,
1096
1516
  sum = 0;
1097
1517
  for (let i = 0; i < dataset.data.length; i++) {
1098
- if (meta.data[i] && !meta.data[i].hidden) {
1518
+ if (chart.getDataVisibility(i)) {
1099
1519
  sum += dataset.data[i];
1100
1520
  }
1101
1521
  }
@@ -1117,9 +1537,8 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1117
1537
  this._adjustColorSchemeCssClass(config);
1118
1538
  this._adjustDatasetColors(config);
1119
1539
  this._adjustLegendColors(config);
1120
- this._adjustTooltipColors(config);
1121
- this._adjustScaleColors(config);
1122
- this._adjustScalesColors(config);
1540
+ this._adjustScalesRColors(config);
1541
+ this._adjustScalesXYColors(config);
1123
1542
  this._adjustPluginColors(config);
1124
1543
  }
1125
1544
 
@@ -1148,7 +1567,7 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1148
1567
  checkedBackgroundColors: [],
1149
1568
  checkedHoverBackgroundColors: [],
1150
1569
  legendColors: [],
1151
- pointHoverColor: this._computePointHoverColor(type)
1570
+ pointHoverColors: []
1152
1571
  };
1153
1572
 
1154
1573
  colors = $.extend(true, colors, this._computeDatasetColors(config, multipleColorsPerDataset));
@@ -1159,9 +1578,12 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1159
1578
  hoverBackgroundColor = (multipleColorsPerDataset ? colors.hoverBackgroundColors : colors.hoverBackgroundColors[idx]),
1160
1579
  hoverBorderColor = (multipleColorsPerDataset ? colors.hoverBorderColors : colors.hoverBorderColors[idx]),
1161
1580
  legendColor = (multipleColorsPerDataset ? colors.legendColors : colors.legendColors[idx]),
1162
- pointHoverBackgroundColor = colors.pointHoverColor;
1581
+ pointHoverBackgroundColor = (multipleColorsPerDataset ? colors.pointHoverColors : colors.legendColors[idx]);
1163
1582
 
1164
1583
  let setProperty = (identifier, value) => {
1584
+ if (typeof elem[identifier] === 'function') {
1585
+ return;
1586
+ }
1165
1587
  if (value && value.length) {
1166
1588
  elem[identifier] = Array.isArray(value) ? [...value] : value;
1167
1589
  }
@@ -1171,10 +1593,14 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1171
1593
  setProperty('hoverBackgroundColor', hoverBackgroundColor);
1172
1594
  setProperty('hoverBorderColor', hoverBorderColor);
1173
1595
  setProperty('legendColor', legendColor);
1174
- setProperty('pointHoverBackgroundColor', pointHoverBackgroundColor);
1596
+ if (scout.isOneOf(type, Chart.Type.LINE, Chart.Type.RADAR) || (type === Chart.Type.BAR && elem.type === Chart.Type.LINE)) {
1597
+ setProperty('pointHoverBackgroundColor', pointHoverBackgroundColor);
1598
+ setProperty('pointBorderColor', this.firstOpaqueBackgroundColor);
1599
+ setProperty('pointHoverBorderColor', this.firstOpaqueBackgroundColor);
1600
+ }
1175
1601
  if (checkable) {
1176
1602
  let datasetLength = elem.data.length;
1177
- if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.BAR_HORIZONTAL, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA, Chart.Type.BUBBLE) || (type === Chart.Type.BAR && (elem.type || Chart.Type.BAR) === Chart.Type.BAR)) {
1603
+ if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA, Chart.Type.BUBBLE) || (type === Chart.Type.BAR && (elem.type || Chart.Type.BAR) === Chart.Type.BAR)) {
1178
1604
  let uncheckedBackgroundColor = (multipleColorsPerDataset ? colors.backgroundColors : arrays.init(datasetLength, colors.backgroundColors[idx])),
1179
1605
  uncheckedHoverBackgroundColor = (multipleColorsPerDataset ? colors.hoverBackgroundColors : arrays.init(datasetLength, colors.hoverBackgroundColors[idx])),
1180
1606
 
@@ -1201,24 +1627,21 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1201
1627
  setProperty('pointBackgroundColor', elem.uncheckedPointBackgroundColor);
1202
1628
  setProperty('pointHoverBackgroundColor', elem.uncheckedPointHoverBackgroundColor);
1203
1629
 
1204
- let uncheckedPointRadius = arrays.init(datasetLength, ((config.options.elements || {}).point || {}).radius || ChartJs.defaults.global.elements.point.radius),
1205
- checkedPointRadius = arrays.init(datasetLength, ((config.options.elements || {}).point || {}).hoverRadius || ChartJs.defaults.global.elements.point.hoverRadius);
1630
+ let uncheckedPointRadius = arrays.init(datasetLength, ((config.options.elements || {}).point || {}).radius || ChartJs.defaults.elements.point.radius),
1631
+ checkedPointRadius = arrays.init(datasetLength, (((config.options.elements || {}).point || {}).hoverRadius || ChartJs.defaults.elements.point.hoverRadius) - 1);
1206
1632
  setProperty('uncheckedPointRadius', uncheckedPointRadius);
1207
1633
  setProperty('checkedPointRadius', checkedPointRadius);
1208
1634
 
1209
1635
  setProperty('pointRadius', elem.uncheckedPointRadius);
1210
1636
  }
1211
1637
  }
1638
+ this._adjustDatasetBorderWidths(elem);
1212
1639
  });
1213
1640
  if (checkable) {
1214
1641
  this._checkItems(config);
1215
1642
  }
1216
1643
  }
1217
1644
 
1218
- _computePointHoverColor(type) {
1219
- return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'point hover'], 'fill').fill;
1220
- }
1221
-
1222
1645
  _computeDatasetColors(config, multipleColorsPerDataset) {
1223
1646
  if (!config || !config.data || !config.type) {
1224
1647
  return {};
@@ -1258,7 +1681,8 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1258
1681
  hoverBorderColors: [],
1259
1682
  checkedBackgroundColors: [],
1260
1683
  checkedHoverBackgroundColors: [],
1261
- legendColors: []
1684
+ legendColors: [],
1685
+ pointHoverColors: []
1262
1686
  };
1263
1687
 
1264
1688
  let types = [];
@@ -1277,6 +1701,8 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1277
1701
  colors.checkedHoverBackgroundColors.push(this._computeCheckedHoverBackgroundColor(type, index, checkable));
1278
1702
 
1279
1703
  colors.legendColors.push(this._computeLegendColor(type, index));
1704
+
1705
+ colors.pointHoverColors.push(this._computePointHoverColor(type, index));
1280
1706
  });
1281
1707
 
1282
1708
  return colors;
@@ -1318,6 +1744,10 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1318
1744
  return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'color' + (index % this.numSupportedColors) + ' legend'], 'fill').fill;
1319
1745
  }
1320
1746
 
1747
+ _computePointHoverColor(type, index) {
1748
+ return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'color' + (index % this.numSupportedColors) + ' point hover'], 'fill').fill;
1749
+ }
1750
+
1321
1751
  _computeDatasetColorsChartValueGroups(config, multipleColorsPerDataset) {
1322
1752
  if (!config || !config.type || !this.chart.data) {
1323
1753
  return {};
@@ -1333,7 +1763,8 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1333
1763
  hoverBorderColors: [],
1334
1764
  checkedBackgroundColors: [],
1335
1765
  checkedHoverBackgroundColors: [],
1336
- legendColors: []
1766
+ legendColors: [],
1767
+ pointHoverColors: []
1337
1768
  };
1338
1769
 
1339
1770
  this.chart.data.chartValueGroups.forEach(elem => {
@@ -1392,6 +1823,8 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1392
1823
  colors.checkedHoverBackgroundColors.push(adjustColor(checkedHoverBackgroundOpacity, checkedHoverBackgroundDarker));
1393
1824
 
1394
1825
  colors.legendColors.push(adjustColor(1, 0));
1826
+
1827
+ colors.pointHoverColors.push(adjustColor(1, 0));
1395
1828
  });
1396
1829
  colors.datalabelColor = this._computeDatalabelColor(type);
1397
1830
 
@@ -1434,10 +1867,12 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1434
1867
  }
1435
1868
 
1436
1869
  config.options = $.extend(true, {}, config.options, {
1437
- legend: {
1438
- labels: {
1439
- fontColor: this._computeLabelColor(config.type),
1440
- generateLabels: this._legendLabelGenerator
1870
+ plugins: {
1871
+ legend: {
1872
+ labels: {
1873
+ color: this._computeLabelColor(config.type),
1874
+ generateLabels: this._legendLabelGenerator
1875
+ }
1441
1876
  }
1442
1877
  }
1443
1878
  });
@@ -1449,33 +1884,50 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1449
1884
 
1450
1885
  _generateLegendLabels(chart) {
1451
1886
  let config = chart.config,
1452
- data = config.data,
1887
+ defaultTypeGenerateLabels;
1888
+ // noinspection DuplicatedCode
1889
+ if (ChartJs.overrides[config.type] && ChartJs.overrides[config.type].plugins && ChartJs.overrides[config.type].plugins.legend && ChartJs.overrides[config.type].plugins.legend.labels) {
1890
+ defaultTypeGenerateLabels = ChartJs.overrides[config.type].plugins.legend.labels.generateLabels;
1891
+ }
1892
+ let defaultGenerateLabels = defaultTypeGenerateLabels || ChartJs.defaults.plugins.legend.labels.generateLabels;
1893
+ let labels = defaultGenerateLabels.call(chart, chart);
1894
+ if (this.removing) {
1895
+ return labels;
1896
+ }
1897
+ let data = config.data,
1453
1898
  measureText = chart.ctx.measureText.bind(chart.ctx),
1899
+ legend = chart.legend,
1900
+ legendLabelOptions = ((legend || {}).options || {}).labels || {},
1901
+ boxWidth = legendLabelOptions.boxWidth || 0,
1902
+ padding = legendLabelOptions.padding || 0,
1454
1903
  horizontalSpace;
1455
- if (scout.isOneOf(config.options.legend.position, Chart.Position.LEFT, Chart.Position.RIGHT)) {
1456
- horizontalSpace = Math.min(250, this.$canvas.cssWidth() / 3);
1904
+ if (scout.isOneOf(config.options.plugins.legend.position, Chart.Position.LEFT, Chart.Position.RIGHT)) {
1905
+ if (legend.maxWidth || legend.width) {
1906
+ horizontalSpace = Math.max((legend.maxWidth || legend.width) - boxWidth - 2 * padding, 0);
1907
+ }
1908
+ horizontalSpace = Math.min(250, horizontalSpace || 0, this.$canvas.cssWidth() / 3);
1909
+
1457
1910
  } else {
1458
1911
  horizontalSpace = Math.min(250, this.$canvas.cssWidth() * 2 / 3);
1459
1912
  }
1460
- let defaultTypeGenerateLabels;
1461
- if (ChartJs.defaults[config.type] && ChartJs.defaults[config.type].legend && ChartJs.defaults[config.type].legend.labels) {
1462
- defaultTypeGenerateLabels = ChartJs.defaults[config.type].legend.labels.generateLabels;
1463
- }
1464
- let defaultGenerateLabels = defaultTypeGenerateLabels || ChartJs.defaults.global.legend.labels.generateLabels;
1465
- let labels = defaultGenerateLabels.call(chart, chart);
1466
1913
  labels.forEach((elem, idx) => {
1467
1914
  elem.text = strings.truncateText(elem.text, horizontalSpace, measureText);
1468
1915
  let dataset = data.datasets[idx],
1469
- fillStyle;
1470
- if (dataset && scout.isOneOf((dataset.type || config.type), Chart.Type.LINE, Chart.Type.BAR, Chart.Type.BAR_HORIZONTAL, Chart.Type.RADAR, Chart.Type.BUBBLE)) {
1471
- fillStyle = dataset.legendColor || this._adjustColorOpacity(dataset.borderColor, 1);
1916
+ legendColor, borderColor, backgroundColor;
1917
+ if (dataset && scout.isOneOf((dataset.type || config.type), Chart.Type.LINE, Chart.Type.BAR, Chart.Type.RADAR, Chart.Type.BUBBLE)) {
1918
+ legendColor = dataset.legendColor;
1919
+ borderColor = this._adjustColorOpacity(dataset.borderColor, 1);
1472
1920
  } else if (data.datasets.length && scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
1473
1921
  dataset = data.datasets[0];
1474
- let legendColor = Array.isArray(dataset.legendColor) ? dataset.legendColor[idx] : dataset.legendColor,
1475
- backgroundColor = Array.isArray(dataset.backgroundColor) ? dataset.backgroundColor[idx] : dataset.backgroundColor;
1476
- fillStyle = legendColor || this._adjustColorOpacity(backgroundColor, 1);
1922
+ legendColor = Array.isArray(dataset.legendColor) ? dataset.legendColor[idx] : dataset.legendColor;
1923
+ backgroundColor = Array.isArray(dataset.backgroundColor) ? dataset.backgroundColor[idx] : dataset.backgroundColor;
1924
+ backgroundColor = this._adjustColorOpacity(backgroundColor, 1);
1925
+ }
1926
+ if (objects.isFunction(legendColor)) {
1927
+ legendColor = legendColor.call(chart, idx);
1477
1928
  }
1478
- if (typeof fillStyle !== 'function') {
1929
+ let fillStyle = legendColor || backgroundColor || borderColor;
1930
+ if (!objects.isFunction(fillStyle)) {
1479
1931
  elem.fillStyle = fillStyle;
1480
1932
  elem.strokeStyle = fillStyle;
1481
1933
  }
@@ -1483,48 +1935,24 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1483
1935
  return labels;
1484
1936
  }
1485
1937
 
1486
- _adjustTooltipColors(config) {
1487
- if (!config || !config.type || !config.options) {
1488
- return;
1489
- }
1490
-
1491
- let tooltipBackgroundColor = this._computeTooltipBackgroundColor(config.type),
1492
- tooltipBorderColor = this._computeTooltipBorderColor(config.type);
1493
-
1494
- config.options = $.extend(true, {}, config.options, {
1495
- tooltips: {
1496
- backgroundColor: tooltipBackgroundColor,
1497
- borderColor: tooltipBorderColor,
1498
- multiKeyBackground: tooltipBackgroundColor
1499
- }
1500
- });
1501
- }
1502
-
1503
- _computeTooltipBackgroundColor(type) {
1504
- return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'tooltip-background'], 'fill').fill;
1505
- }
1506
-
1507
- _computeTooltipBorderColor(type) {
1508
- return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'tooltip-border'], 'fill').fill;
1509
- }
1510
-
1511
- _adjustScaleColors(config) {
1512
- if (!config || !config.type || !config.options || !config.options.scale) {
1938
+ _adjustScalesRColors(config) {
1939
+ if (!config || !config.type || !config.options || !config.options.scales || !config.options.scales.r) {
1513
1940
  return;
1514
1941
  }
1515
1942
 
1516
1943
  let labelColor = this._computeLabelColor(config.type),
1517
1944
  labelBackdropColor = this._computeLabelBackdropColor(config.type),
1518
- gridColor = this._computeGridColor(config.type);
1945
+ gridColor = this._computeGridColor(config.type),
1946
+ scaleTicksColor = this._computeScaleTicksColor(config.type);
1519
1947
 
1520
- config.options.scale.ticks = $.extend(true, {}, config.options.scale.ticks, {
1521
- fontColor: gridColor,
1948
+ config.options.scales.r.ticks = $.extend(true, {}, config.options.scales.r.ticks, {
1949
+ color: scaleTicksColor,
1522
1950
  backdropColor: labelBackdropColor
1523
1951
  });
1524
- config.options.scale.pointLabels = $.extend(true, {}, config.options.scale.pointLabels, {
1525
- fontColor: labelColor
1952
+ config.options.scales.r.pointLabels = $.extend(true, {}, config.options.scales.r.pointLabels, {
1953
+ color: labelColor
1526
1954
  });
1527
- config.options.scale.gridLines = $.extend(true, {}, config.options.scale.gridLines, {
1955
+ config.options.scales.r.grid = $.extend(true, {}, config.options.scales.r.grid, {
1528
1956
  color: gridColor
1529
1957
  });
1530
1958
  }
@@ -1537,14 +1965,22 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1537
1965
  return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'grid'], 'fill').fill;
1538
1966
  }
1539
1967
 
1540
- _adjustScalesColors(config) {
1968
+ _computeScaleTicksColor(type) {
1969
+ return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'scale-ticks'], 'fill').fill;
1970
+ }
1971
+
1972
+ _adjustScalesXYColors(config) {
1541
1973
  if (!config || !config.type || !config.options || !config.options.scales) {
1542
1974
  return;
1543
1975
  }
1544
1976
 
1545
- let xAxes = config.options.scales.xAxes || [],
1546
- yAxes = config.options.scales.yAxes || [],
1547
- axes = [...xAxes, ...yAxes];
1977
+ let axes = [];
1978
+ if (config.options.scales.x) {
1979
+ axes.push(config.options.scales.x);
1980
+ }
1981
+ if (config.options.scales.y) {
1982
+ axes.push(config.options.scales.y);
1983
+ }
1548
1984
 
1549
1985
  if (!axes.length) {
1550
1986
  return;
@@ -1555,15 +1991,14 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1555
1991
  axisLabelColor = this._computeAxisLabelColor(config.type);
1556
1992
 
1557
1993
  axes.forEach(elem => {
1558
- elem.gridLines = $.extend(true, {}, elem.gridLines, {
1559
- zeroLineColor: gridColor,
1994
+ elem.grid = $.extend(true, {}, elem.grid, {
1560
1995
  color: gridColor
1561
1996
  });
1562
1997
  elem.ticks = $.extend(true, {}, elem.ticks, {
1563
- fontColor: labelColor
1998
+ color: labelColor
1564
1999
  });
1565
- elem.scaleLabel = $.extend(true, {}, elem.scaleLabel, {
1566
- fontColor: axisLabelColor
2000
+ elem.title = $.extend(true, {}, elem.title, {
2001
+ color: axisLabelColor
1567
2002
  });
1568
2003
  });
1569
2004
  }
@@ -1606,33 +2041,39 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1606
2041
  config.options.onHover = this._hoverHandler;
1607
2042
  }
1608
2043
 
1609
- if (!config.options.legend) {
2044
+ if (!config.options.plugins || !config.options.plugins.legend) {
1610
2045
  return;
1611
2046
  }
1612
2047
 
1613
- if (config.options.legend.clickable) {
1614
- config.options.legend.onClick = this._legendClickHandler;
1615
- config.options.legend.onHover = this._legendPointerHoverHandler;
1616
- config.options.legend.onLeave = this._legendPointerLeaveHandler;
2048
+ let legend = config.options.plugins.legend;
2049
+ if (legend.clickable) {
2050
+ legend.onClick = this._legendClickHandler;
2051
+ legend.onHover = this._legendPointerHoverHandler;
2052
+ legend.onLeave = this._legendPointerLeaveHandler;
1617
2053
  } else {
1618
- config.options.legend.onClick = e => e.stopPropagation();
1619
- config.options.legend.onHover = this._legendHoverHandler;
1620
- config.options.legend.onLeave = this._legendLeaveHandler;
2054
+ legend.onClick = e => e.native.stopPropagation();
2055
+ legend.onHover = this._legendHoverHandler;
2056
+ legend.onLeave = this._legendLeaveHandler;
1621
2057
  }
1622
2058
  }
1623
2059
 
1624
2060
  /**
1625
2061
  * @param {object[]} items
1626
- * @param {number} items._index
1627
- * @param {number} items._datasetIndex
2062
+ * @param {number} items.index
2063
+ * @param {number} items.datasetIndex
1628
2064
  */
1629
2065
  _onClick(event, items) {
1630
- if (!items.length || this._isMaxSegmentsExceeded(this.chartJs.config, items[0]._index)) {
2066
+ if (!items.length) {
2067
+ return;
2068
+ }
2069
+ let relevantItem = this._selectRelevantItem(items);
2070
+
2071
+ if (this._isMaxSegmentsExceeded(this.chartJs.config, relevantItem.index)) {
1631
2072
  return;
1632
2073
  }
1633
2074
 
1634
- let itemIndex = items[0]._index,
1635
- datasetIndex = items[0]._datasetIndex,
2075
+ let itemIndex = relevantItem.index,
2076
+ datasetIndex = relevantItem.datasetIndex,
1636
2077
  clickObject = {
1637
2078
  datasetIndex: datasetIndex,
1638
2079
  dataIndex: itemIndex
@@ -1647,10 +2088,34 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1647
2088
 
1648
2089
  let e = new Event();
1649
2090
  e.data = clickObject;
1650
- e.originalEvent = event;
2091
+ e.originalEvent = event.native;
1651
2092
  this.chart._onValueClick(e);
1652
2093
  }
1653
2094
 
2095
+ /**
2096
+ * Selects the most relevant item. Default is the first item.
2097
+ *
2098
+ * @param {object[]} items
2099
+ * @param {number} items.index
2100
+ * @param {number} items.datasetIndex
2101
+ * @private
2102
+ */
2103
+ _selectRelevantItem(items) {
2104
+ let chartDatasets = this.chartJs.config.data.datasets;
2105
+ let relevantItem = items[0];
2106
+
2107
+ if (this.chartJs.config.type === Chart.Type.BUBBLE) {
2108
+ // The smallest bubble, which is drawn in the foreground, is the most relevant item for the bubble chart.
2109
+ // If two bubbles are the same size, we choose the one which comes later in the array (bubble with array index n + 1 is draw in front of bubble with array index n)
2110
+ items.forEach(item => {
2111
+ if (chartDatasets[item.datasetIndex].data[item.index].z <= chartDatasets[relevantItem.datasetIndex].data[relevantItem.index].z) {
2112
+ relevantItem = item;
2113
+ }
2114
+ });
2115
+ }
2116
+ return relevantItem;
2117
+ }
2118
+
1654
2119
  _onHover(event, items) {
1655
2120
  if (!this.chartJs.config || !this.chartJs.config.type) {
1656
2121
  return;
@@ -1664,14 +2129,14 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1664
2129
 
1665
2130
  let update = false;
1666
2131
  if (this.resetDatasetAfterHover) {
1667
- this._adjustColors(config);
2132
+ this._restoreBackgroundColor();
1668
2133
  this.resetDatasetAfterHover = false;
1669
2134
  update = true;
1670
2135
  }
1671
2136
  items.forEach(item => {
1672
- let dataset = config.data.datasets[item._datasetIndex];
2137
+ let dataset = config.data.datasets[item.datasetIndex];
1673
2138
  if (scout.isOneOf((dataset.type || type), Chart.Type.LINE, Chart.Type.RADAR)) {
1674
- dataset.backgroundColor = dataset.hoverBackgroundColor;
2139
+ this._setHoverBackgroundColor(dataset);
1675
2140
  this.resetDatasetAfterHover = true;
1676
2141
  update = true;
1677
2142
  }
@@ -1683,35 +2148,38 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1683
2148
 
1684
2149
  _onHoverPointer(event, items) {
1685
2150
  this._onHover(event, items);
1686
- if (items.length && !this._isMaxSegmentsExceeded(this.chartJs.config, items[0]._index)) {
2151
+ if (!this.rendered || this.removing) {
2152
+ return;
2153
+ }
2154
+ if (items.length && !this._isMaxSegmentsExceeded(this.chartJs.config, items[0].index)) {
1687
2155
  this.$canvas.css('cursor', 'pointer');
1688
2156
  } else {
1689
2157
  this.$canvas.css('cursor', 'default');
1690
2158
  }
1691
2159
  }
1692
2160
 
1693
- _onLegendClick(event, item) {
2161
+ _onLegendClick(e, legendItem, legend) {
1694
2162
  if (!this.chartJs.config || !this.chartJs.config.type) {
1695
2163
  return;
1696
2164
  }
1697
2165
 
1698
2166
  let type = this.chartJs.config.type,
1699
2167
  defaultTypeLegendClick;
1700
- if (ChartJs.defaults[type] && ChartJs.defaults[type].legend) {
1701
- defaultTypeLegendClick = ChartJs.defaults[type].legend.onClick;
2168
+ if (ChartJs.overrides[type] && ChartJs.overrides[type].plugins && ChartJs.overrides[type].plugins.legend) {
2169
+ defaultTypeLegendClick = ChartJs.overrides[type].plugins.legend.onClick;
1702
2170
  }
1703
- let defaultLegendClick = defaultTypeLegendClick || ChartJs.defaults.global.legend.onClick;
1704
- defaultLegendClick.call(this.chartJs, event, item);
1705
- this._onLegendLeave(event, item);
1706
- this._onLegendHoverPointer(event, item, true);
2171
+ let defaultLegendClick = defaultTypeLegendClick || ChartJs.defaults.plugins.legend.onClick;
2172
+ defaultLegendClick.call(this.chartJs, e, legendItem, legend);
2173
+ this._onLegendLeave(e, legendItem, legend);
2174
+ this._onLegendHoverPointer(e, legendItem, true);
1707
2175
  }
1708
2176
 
1709
- _onLegendHover(event, item, animated) {
1710
- let index = item.datasetIndex,
2177
+ _onLegendHover(e, legendItem, legend) {
2178
+ let index = legendItem.datasetIndex,
1711
2179
  config = this.chartJs.config,
1712
2180
  type = config.type;
1713
2181
  if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
1714
- index = item.index;
2182
+ index = legendItem.index;
1715
2183
  }
1716
2184
 
1717
2185
  if (this.legendHoverDatasets.indexOf(index) > -1) {
@@ -1721,29 +2189,28 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1721
2189
  let dataset = config.data.datasets[index],
1722
2190
  datasetType = dataset ? dataset.type : null;
1723
2191
  if ((datasetType || type) === Chart.Type.LINE) {
1724
- dataset.backgroundColor = dataset.hoverBackgroundColor;
2192
+ this._setHoverBackgroundColor(dataset);
1725
2193
  this.chartJs.update();
1726
2194
  }
1727
2195
  this._updateHoverStyle(index, true);
1728
- if (animated) {
1729
- this.chartJs.render();
1730
- } else {
1731
- this.chartJs.render({duration: 0});
1732
- }
2196
+ this.chartJs.render();
1733
2197
  this.legendHoverDatasets.push(index);
1734
2198
  }
1735
2199
 
1736
- _onLegendHoverPointer(event, item, animated) {
1737
- this._onLegendHover(event, item, animated);
2200
+ _onLegendHoverPointer(e, legendItem, legend) {
2201
+ this._onLegendHover(e, legendItem, legend);
2202
+ if (!this.rendered || this.removing) {
2203
+ return;
2204
+ }
1738
2205
  this.$canvas.css('cursor', 'pointer');
1739
2206
  }
1740
2207
 
1741
- _onLegendLeave(event, item) {
1742
- let index = item.datasetIndex,
2208
+ _onLegendLeave(e, legendItem, legend) {
2209
+ let index = legendItem.datasetIndex,
1743
2210
  config = this.chartJs.config,
1744
2211
  type = config.type;
1745
2212
  if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
1746
- index = item.index;
2213
+ index = legendItem.index;
1747
2214
  }
1748
2215
 
1749
2216
  if (this.legendHoverDatasets.indexOf(index) < 0) {
@@ -1753,7 +2220,7 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1753
2220
  let dataset = config.data.datasets[index],
1754
2221
  datasetType = dataset ? dataset.type : null;
1755
2222
  if ((datasetType || type) === Chart.Type.LINE) {
1756
- this._adjustColors(config);
2223
+ this._restoreBackgroundColor(dataset);
1757
2224
  this.chartJs.update();
1758
2225
  }
1759
2226
  this._updateHoverStyle(index, false);
@@ -1761,30 +2228,76 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1761
2228
  this.legendHoverDatasets.splice(this.legendHoverDatasets.indexOf(index), 1);
1762
2229
  }
1763
2230
 
1764
- _onLegendLeavePointer(event, item) {
1765
- this._onLegendLeave(event, item);
2231
+ /**
2232
+ * Sets the hover background color as the datasets background color.
2233
+ * This little workaround is necessary for the line chart, which does not support a native hover effect.
2234
+ * The previous background color will be backuped on the dataset property "backgroundColorBackup"
2235
+ * and can be restored with {@link _restoreBackgroundColor}.
2236
+ * @param {Dataset} dataset
2237
+ */
2238
+ _setHoverBackgroundColor(dataset) {
2239
+ if (!dataset) {
2240
+ return;
2241
+ }
2242
+ // backup the old background color first
2243
+ dataset.backgroundColorBackup = dataset.backgroundColor;
2244
+ // overwrite the current background color with the hover color
2245
+ dataset.backgroundColor = dataset.hoverBackgroundColor;
2246
+ this._adjustDatasetBorderWidths(dataset);
2247
+ }
2248
+
2249
+ /**
2250
+ * Restores the background color of a dataset or of all datasets,
2251
+ * if they were previously overwritten by {@link _setHoverBackgroundColor}.
2252
+ * @param {Dataset} [dataset]
2253
+ */
2254
+ _restoreBackgroundColor(dataset) {
2255
+ if (dataset) {
2256
+ dataset.backgroundColor = dataset.backgroundColorBackup || dataset.backgroundColor;
2257
+ delete dataset.backgroundColorBackup;
2258
+ this._adjustDatasetBorderWidths(dataset);
2259
+ } else {
2260
+ this.chartJs.config.data.datasets.forEach(dataset => this._restoreBackgroundColor(dataset));
2261
+ }
2262
+ }
2263
+
2264
+ _onLegendLeavePointer(e, legendItem, legend) {
2265
+ this._onLegendLeave(e, legendItem, legend);
2266
+ if (!this.rendered || this.removing) {
2267
+ return;
2268
+ }
1766
2269
  this.$canvas.css('cursor', 'default');
1767
2270
  }
1768
2271
 
1769
- _updateHoverStyle(index, enabled) {
2272
+ _updateHoverStyle(datasetIndex, enabled) {
1770
2273
  let config = this.chartJs.config,
1771
2274
  type = config.type,
2275
+ mode,
2276
+ elements = [],
1772
2277
  datasets = config.data.datasets,
1773
- dataset = datasets ? datasets[index] : null,
2278
+ dataset = datasets ? datasets[datasetIndex] : null,
1774
2279
  datasetType = dataset ? dataset.type : null;
1775
- if ((datasetType || type) === Chart.Type.LINE) {
1776
- this.chartJs.updateHoverStyle(this.chartJs.getDatasetMeta(index).data, 'point', enabled);
1777
- } else if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
1778
- let elements = [];
1779
- for (let i = 0; i < datasets.length; i++) {
1780
- elements.push(this.chartJs.getDatasetMeta(i).data[index]);
1781
- }
1782
- this.chartJs.updateHoverStyle(elements, 'point', enabled);
2280
+ if (scout.isOneOf(type, Chart.Type.LINE, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA) || datasetType === Chart.Type.LINE) {
2281
+ mode = 'point';
1783
2282
  } else {
1784
- let elements = this.chartJs.getDatasetMeta(index).data;
1785
- if (elements && elements.length) {
1786
- this.chartJs.updateHoverStyle(this.chartJs.getDatasetMeta(index).data, 'dataset', enabled);
1787
- }
2283
+ mode = 'dataset';
2284
+ }
2285
+ if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
2286
+ this.chartJs.getSortedVisibleDatasetMetas().forEach((meta, index) => elements.push({
2287
+ element: meta.data[datasetIndex],
2288
+ datasetIndex: index,
2289
+ index: datasetIndex
2290
+ }));
2291
+ } else {
2292
+ this.chartJs.getDatasetMeta(datasetIndex).data.forEach((element, index) => elements.push({
2293
+ element: element,
2294
+ datasetIndex: datasetIndex,
2295
+ index: index
2296
+ }));
2297
+ }
2298
+ if (elements && elements.length) {
2299
+ // noinspection JSCheckFunctionSignatures
2300
+ this.chartJs.updateHoverStyle(elements, mode, enabled);
1788
2301
  }
1789
2302
  }
1790
2303
 
@@ -1827,8 +2340,8 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1827
2340
  // Compute a scalingFactor and an offset to get the new radius newR = r * scalingFactor + offset.
1828
2341
  bubbleScalingFactor = 1,
1829
2342
  bubbleRadiusOffset = 0,
1830
- sizeOfLargestBubble = config.bubble ? config.bubble.sizeOfLargestBubble : 0,
1831
- minBubbleSize = config.bubble ? config.bubble.minBubbleSize : 0;
2343
+ sizeOfLargestBubble = (config.options || {}).bubble ? config.options.bubble.sizeOfLargestBubble : 0,
2344
+ minBubbleSize = (config.options || {}).bubble ? config.options.bubble.minBubbleSize : 0;
1832
2345
  if (sizeOfLargestBubble) {
1833
2346
  let width = Math.abs(chartArea.right - chartArea.left),
1834
2347
  height = Math.abs(chartArea.top - chartArea.bottom);
@@ -1977,39 +2490,48 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1977
2490
  }
1978
2491
 
1979
2492
  _adjustGridMaxMin(config, chartArea) {
1980
- if (!config || !config.type || !config.options || !config.options.adjustGridMaxMin || (!config.options.scale && !config.options.scales) || !chartArea) {
2493
+ if (!config || !config.type || !config.options || !config.options.adjustGridMaxMin || !config.options.scales || !chartArea) {
1981
2494
  return;
1982
2495
  }
1983
2496
 
1984
2497
  let type = config.type;
1985
- if (!scout.isOneOf(type, Chart.Type.BAR, Chart.Type.BAR_HORIZONTAL, Chart.Type.LINE, Chart.Type.POLAR_AREA, Chart.Type.RADAR, Chart.Type.BUBBLE)) {
2498
+ if (!scout.isOneOf(type, Chart.Type.BAR, Chart.Type.LINE, Chart.Type.POLAR_AREA, Chart.Type.RADAR, Chart.Type.BUBBLE)) {
1986
2499
  return;
1987
2500
  }
1988
2501
 
2502
+ let scales = config.options.scales,
2503
+ xAxis = scales.x,
2504
+ yAxis = scales.y,
2505
+ rAxis = scales.r,
2506
+ minSpaceBetweenXTicks = xAxis ? xAxis.minSpaceBetweenTicks : 1,
2507
+ minSpaceBetweenYTicks = yAxis ? yAxis.minSpaceBetweenTicks : 1;
2508
+
2509
+ if (rAxis) {
2510
+ minSpaceBetweenXTicks = rAxis.minSpaceBetweenTicks;
2511
+ minSpaceBetweenYTicks = rAxis.minSpaceBetweenTicks;
2512
+ }
2513
+
1989
2514
  let width = Math.abs(chartArea.right - chartArea.left),
1990
2515
  height = Math.abs(chartArea.top - chartArea.bottom),
1991
- maxXTicks = Math.max(Math.floor(width / this.minSpaceBetweenXTicks), 3),
1992
- maxYTicks = Math.max(Math.floor(height / this.minSpaceBetweenYTicks), 3);
2516
+ maxXTicks = Math.max(Math.floor(width / minSpaceBetweenXTicks) + 1, 3),
2517
+ maxYTicks = Math.max(Math.floor(height / minSpaceBetweenYTicks) + 1, 3);
1993
2518
 
1994
2519
  let yBoundaries = this._computeYBoundaries(config, height),
1995
2520
  yBoundary = yBoundaries.yBoundary,
1996
2521
  yBoundaryDiffType = yBoundaries.yBoundaryDiffType;
1997
2522
 
1998
- if (config.options.scale) {
1999
- this._adjustScaleMaxMin(config.options.scale, maxYTicks, yBoundary);
2523
+ if (rAxis) {
2524
+ this._adjustAxisMaxMin(rAxis, Math.ceil(Math.min(maxXTicks, maxYTicks) / 2), yBoundary);
2000
2525
  return;
2001
2526
  }
2002
2527
 
2003
- let xAxes = config.options.scales.xAxes,
2004
- yAxes = config.options.scales.yAxes;
2005
-
2006
2528
  if (yBoundaryDiffType) {
2007
- this._adjustAxes(arrays.ensure(yAxes[0]), maxYTicks, yBoundary);
2008
- this._adjustAxes(arrays.ensure(yAxes[1]), maxYTicks, yBoundaryDiffType);
2009
- } else if (type === Chart.Type.BAR_HORIZONTAL) {
2010
- this._adjustAxes(xAxes, maxXTicks, yBoundary);
2529
+ this._adjustAxisMaxMin(yAxis, maxYTicks, yBoundary);
2530
+ this._adjustAxisMaxMin(scales.yDiffType, maxYTicks, yBoundaryDiffType);
2531
+ } else if (this._isHorizontalBar(config)) {
2532
+ this._adjustAxisMaxMin(xAxis, maxXTicks, yBoundary);
2011
2533
  } else {
2012
- this._adjustAxes(yAxes, maxYTicks, yBoundary);
2534
+ this._adjustAxisMaxMin(yAxis, maxYTicks, yBoundary);
2013
2535
  }
2014
2536
 
2015
2537
  if (type !== Chart.Type.BUBBLE) {
@@ -2017,7 +2539,7 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
2017
2539
  }
2018
2540
 
2019
2541
  let xBoundary = this._computeXBoundaryBubble(config, width);
2020
- this._adjustAxes(xAxes, maxXTicks, xBoundary);
2542
+ this._adjustAxisMaxMin(xAxis, maxXTicks, xBoundary);
2021
2543
  }
2022
2544
 
2023
2545
  _computeBoundaryBubble(config, identifier, space) {
@@ -2026,10 +2548,9 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
2026
2548
  }
2027
2549
 
2028
2550
  let datasets = config.data.datasets,
2029
- axes = config.options.scales[identifier + 'Axes'],
2030
- axis = (axes && axes.length) ? axes[0] : null,
2551
+ axis = config.options.scales[identifier],
2031
2552
  offset = axis && axis.offset,
2032
- labelMap = config.options.scales[identifier + 'LabelMap'],
2553
+ labelMap = config.options[identifier + 'LabelMap'],
2033
2554
  boundary;
2034
2555
 
2035
2556
  let maxR = this._computeMaxMinValue(datasets, 'r', true).maxValue,
@@ -2105,89 +2626,83 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
2105
2626
  return;
2106
2627
  }
2107
2628
 
2108
- if (!config.options || !config.options.scales || !config.options.scales.yAxes || config.options.scales.yAxes.length !== 1) {
2629
+ if (!config.options || !config.options.scales || !config.options.scales.y || config.options.scales.yDiffType) {
2109
2630
  return;
2110
2631
  }
2111
2632
 
2112
2633
  let type = config.type,
2113
- scales = config.options.scales,
2114
- yAxis = scales.yAxes[0],
2634
+ options = config.options,
2635
+ scales = options.scales,
2636
+ yAxis = scales.y,
2115
2637
  yAxisDiffType = $.extend(true, {}, yAxis);
2116
- scales.yAxes.push(yAxisDiffType);
2638
+ scales.yDiffType = yAxisDiffType;
2117
2639
 
2118
- yAxis.id = 'yAxis';
2119
- yAxisDiffType.id = 'yAxisDiffType';
2640
+ yAxis.id = 'y';
2641
+ yAxisDiffType.id = 'yDiffType';
2120
2642
 
2121
2643
  if (config.data && config.data.datasets && config.data.datasets.length && config.data.datasets[0].type && config.data.datasets[0].type !== type) {
2122
2644
  yAxisDiffType.position = Chart.Position.LEFT;
2123
2645
  yAxis.position = Chart.Position.RIGHT;
2124
- yAxis.gridLines.drawOnChartArea = false;
2646
+ yAxis.grid.drawOnChartArea = false;
2125
2647
  } else {
2126
2648
  yAxis.position = Chart.Position.LEFT;
2127
2649
  yAxisDiffType.position = Chart.Position.RIGHT;
2128
- yAxisDiffType.gridLines.drawOnChartArea = false;
2650
+ yAxisDiffType.grid.drawOnChartArea = false;
2129
2651
  }
2130
2652
 
2131
- yAxis.gridLines.drawBorder = true;
2132
- yAxis.gridLines.drawTicks = true;
2133
- yAxisDiffType.gridLines.drawBorder = true;
2134
- yAxisDiffType.gridLines.drawTicks = true;
2653
+ yAxis.grid.drawBorder = true;
2654
+ yAxis.grid.drawTicks = true;
2655
+ yAxisDiffType.grid.drawBorder = true;
2656
+ yAxisDiffType.grid.drawTicks = true;
2135
2657
 
2136
2658
  let yAxisType = (datasets[0].type || type),
2137
2659
  yAxisDiffTypeType = (datasetsDiffType[0].type || type),
2138
2660
  yAxisTypeLabel = this.chart.session.text('ui.' + yAxisType),
2139
2661
  yAxisDiffTypeTypeLabel = this.chart.session.text('ui.' + yAxisDiffTypeType),
2140
- yAxisScaleLabel = scales.scaleLabelByTypeMap ? scales.scaleLabelByTypeMap[yAxisType] : null,
2141
- yAxisDiffTypeScaleLabel = scales.scaleLabelByTypeMap ? scales.scaleLabelByTypeMap[yAxisDiffTypeType] : null;
2662
+ yAxisScaleLabel = options.scaleLabelByTypeMap ? options.scaleLabelByTypeMap[yAxisType] : null,
2663
+ yAxisDiffTypeScaleLabel = options.scaleLabelByTypeMap ? options.scaleLabelByTypeMap[yAxisDiffTypeType] : null;
2142
2664
 
2143
- yAxis.scaleLabel.display = true;
2144
- yAxis.scaleLabel.labelString = yAxisScaleLabel ? yAxisScaleLabel + ' (' + yAxisTypeLabel + ')' : yAxisTypeLabel;
2145
- yAxisDiffType.scaleLabel.display = true;
2146
- yAxisDiffType.scaleLabel.labelString = yAxisDiffTypeScaleLabel ? yAxisDiffTypeScaleLabel + ' (' + yAxisDiffTypeTypeLabel + ')' : yAxisDiffTypeTypeLabel;
2665
+ yAxis.title.display = true;
2666
+ yAxis.title.text = yAxisScaleLabel ? yAxisScaleLabel + ' (' + yAxisTypeLabel + ')' : yAxisTypeLabel;
2667
+ yAxisDiffType.title.display = true;
2668
+ yAxisDiffType.title.text = yAxisDiffTypeScaleLabel ? yAxisDiffTypeScaleLabel + ' (' + yAxisDiffTypeTypeLabel + ')' : yAxisDiffTypeTypeLabel;
2147
2669
 
2148
2670
  datasets.forEach(dataset => {
2149
- dataset.yAxisID = 'yAxis';
2671
+ dataset.yAxisID = 'y';
2150
2672
  });
2151
2673
  datasetsDiffType.forEach(dataset => {
2152
- dataset.yAxisID = 'yAxisDiffType';
2674
+ dataset.yAxisID = 'yDiffType';
2153
2675
  });
2154
2676
  }
2155
2677
 
2156
- _adjustScaleMaxMin(scale, maxTicks, maxMinValue) {
2157
- scale.ticks = $.extend(true, {}, scale.ticks, {
2158
- maxTicksLimit: Math.ceil(maxTicks / 2),
2159
- stepSize: (this.onlyIntegers ? 1 : undefined)
2160
- });
2161
- if (maxMinValue) {
2162
- scale.ticks.suggestedMax = maxMinValue.maxValue;
2163
- scale.ticks.suggestedMin = maxMinValue.minValue;
2164
- }
2165
- }
2166
-
2167
- _adjustAxes(axes, maxTicks, maxMinValue) {
2168
- if (!axes || !Array.isArray(axes) || !axes.length) {
2678
+ _adjustAxisMaxMin(axis, maxTicks, maxMinValue) {
2679
+ if (!axis) {
2169
2680
  return;
2170
2681
  }
2171
2682
 
2172
- for (let i = 0; i < axes.length; i++) {
2173
- axes[i].ticks = $.extend(true, {}, axes[i].ticks, {
2174
- maxTicksLimit: maxTicks,
2175
- stepSize: (this.onlyIntegers ? 1 : undefined)
2176
- });
2177
- if (maxMinValue) {
2178
- axes[i].ticks.suggestedMax = maxMinValue.maxValue;
2179
- axes[i].ticks.suggestedMin = maxMinValue.minValue;
2180
- }
2683
+ let maxRangeBetweenTwoTicks = 1;
2684
+
2685
+ axis.ticks = $.extend(true, {}, axis.ticks, {
2686
+ maxTicksLimit: maxTicks
2687
+ });
2688
+ if (maxMinValue) {
2689
+ axis.suggestedMax = maxMinValue.maxValue;
2690
+ axis.suggestedMin = maxMinValue.minValue;
2691
+
2692
+ maxRangeBetweenTwoTicks = (maxMinValue.maxValue - maxMinValue.minValue) / (maxTicks - 1);
2181
2693
  }
2694
+ axis.ticks.stepSize = this.onlyIntegers && maxRangeBetweenTwoTicks < 1 ? 1 : undefined;
2182
2695
  }
2183
2696
 
2184
2697
  _remove(afterRemoveFunc) {
2185
- if (this.rendered) {
2186
- this.$canvas.remove();
2187
- this.$canvas = null;
2698
+ if (this.rendered && !this.removing) {
2699
+ this.removing = true;
2188
2700
  this.chartJs.destroy();
2189
2701
  this.chartJs = null;
2702
+ this.$canvas.remove();
2703
+ this.$canvas = null;
2190
2704
  }
2191
2705
  super._remove(afterRemoveFunc);
2706
+ this.removing = false;
2192
2707
  }
2193
2708
  }