@eclipse-scout/chart 11.0.39 → 22.0.0-beta.1

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 +23316 -6685
  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 +40 -9
  13. package/src/chart/Chart.less +121 -78
  14. package/src/chart/ChartJsRenderer.js +1092 -580
  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
+ */
18
23
 
19
24
  /**
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]
25
+ * @typedef TickOptions
26
+ * @property {number} maxRotation
31
27
  */
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;
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
+ */
60
+
61
+ /**
62
+ * @typedef TooltipOptions
63
+ * @property {string} titleFont.family
64
+ */
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,305 @@ 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
+ let tooltip = context.tooltip,
920
+ tooltipItems = tooltip._tooltipItems;
921
+ if (tooltipItems.length < 1) {
922
+ return;
923
+ }
924
+ if (this._tooltip) {
925
+ this._tooltip.destroy();
926
+ this._tooltip = null;
927
+ }
928
+
929
+ let tooltipOptions = tooltip.options || {},
930
+ tooltipCallbacks = tooltipOptions.callbacks || {},
931
+ tooltipTitle = tooltipCallbacks.title,
932
+ tooltipLabel = tooltipCallbacks.label,
933
+ tooltipLabelValue = tooltipCallbacks.labelValue,
934
+ tooltipColor = tooltipCallbacks.labelColor,
935
+ tooltipText = '';
936
+
937
+ if (objects.isFunction(tooltipTitle)) {
938
+ tooltipText += arrays.ensure(tooltipTitle(tooltipItems)).join('');
939
+ }
940
+ tooltipItems.forEach(tooltipItem => {
941
+ let label, labelValue, labelColor;
942
+ if (objects.isFunction(tooltipLabel)) {
943
+ label = tooltipLabel(tooltipItem);
944
+ label = objects.isString(label) ? label : '';
945
+ }
946
+ if (objects.isFunction(tooltipLabelValue)) {
947
+ labelValue = tooltipLabelValue(tooltipItem);
948
+ labelValue = objects.isString(labelValue) ? labelValue : '';
949
+ }
950
+ if (objects.isFunction(tooltipColor)) {
951
+ labelColor = tooltipColor(tooltipItem);
952
+ labelColor = objects.isPlainObject(labelColor) ? (labelColor.backgroundColor || '') : '';
953
+ }
954
+ tooltipText += this._createTooltipAttribute(label, labelValue, false, labelColor);
955
+ });
956
+
957
+ let positionAndOffset = this._computeTooltipPositionAndOffset(tooltipItems[0]),
958
+ origin = graphics.offsetBounds(this.$canvas);
959
+ origin.x += tooltip.caretX + positionAndOffset.offsetX;
960
+ origin.y += tooltip.caretY + positionAndOffset.offsetY;
961
+ // Tooltip adds origin.height to origin.y in 'bottom' mode, but origin.y is already the correct value for 'top' and 'bottom' mode
962
+ origin.height = 0;
963
+
964
+ this._tooltip = scout.create({
965
+ objectType: 'Tooltip',
966
+ parent: this.chart,
967
+ $anchor: this.$canvas,
968
+ text: tooltipText,
969
+ htmlEnabled: true,
970
+ cssClass: strings.join(' ', 'chart-tooltip', tooltipOptions.cssClass),
971
+ tooltipPosition: positionAndOffset.tooltipPosition,
972
+ tooltipDirection: positionAndOffset.tooltipDirection,
973
+ origin: origin
974
+ });
975
+ this._tooltip.render();
976
+
977
+ this._tooltip.$container
978
+ .css('pointer-events', 'none');
979
+
980
+ if ((tooltipOptions.titleFont || {}).family) {
981
+ this._tooltip.$container
982
+ .css('--chart-tooltip-font-family', tooltipOptions.titleFont.family);
983
+ }
984
+
985
+ let maxLabelPrefSize = 0;
986
+ this._tooltip.$container.find('label').each((idx, elem) => maxLabelPrefSize = Math.max(maxLabelPrefSize, graphics.prefSize($(elem)).width));
987
+ if (maxLabelPrefSize > 0) {
988
+ this._tooltip.$container
989
+ .css('--chart-tooltip-label-width', Math.min(maxLabelPrefSize, 120) + 'px');
990
+ }
991
+ }
992
+
993
+ _computeTooltipPositionAndOffset(tooltipItem) {
994
+ let tooltipPosition = 'top',
995
+ tooltipDirection = 'right',
996
+ offsetX = 0,
997
+ offsetY = 0;
998
+
999
+ let chart = tooltipItem.chart,
1000
+ datasetIndex = tooltipItem.datasetIndex,
1001
+ dataIndex = tooltipItem.dataIndex,
1002
+ config = chart.config,
1003
+ datasets = config.data.datasets,
1004
+ dataset = datasets[datasetIndex],
1005
+ value = dataset.data[dataIndex];
1006
+
1007
+ if (this._isHorizontalBar(config)) {
1008
+ tooltipDirection = value < 0 ? 'left' : 'right';
1009
+ } else if ((dataset.type || config.type) === Chart.Type.BAR) {
1010
+ tooltipPosition = value < 0 ? 'bottom' : 'top';
1011
+ } else if (scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
1012
+ // noinspection JSValidateTypes
1013
+ /**
1014
+ * @type ArcElement
1015
+ */
1016
+ let element = chart.getDatasetMeta(datasetIndex).data[dataIndex];
1017
+ let startAngle = element.startAngle,
1018
+ endAngle = element.endAngle,
1019
+ angle = (startAngle + endAngle) / 2;
1020
+ tooltipPosition = (0 <= angle && angle < Math.PI) ? 'bottom' : 'top';
1021
+ tooltipDirection = (-Math.PI / 2 <= angle && angle < Math.PI / 2) ? 'right' : 'left';
1022
+ } else if (config.type === Chart.Type.RADAR) {
1023
+ // noinspection JSValidateTypes
1024
+ /**
1025
+ * @type PointElement
1026
+ */
1027
+ let element = chart.getDatasetMeta(datasetIndex).data[dataIndex];
1028
+ let angle = element.angle;
1029
+ tooltipPosition = (0 <= angle && angle < Math.PI) ? 'bottom' : 'top';
1030
+ tooltipDirection = (-Math.PI / 2 <= angle && angle < Math.PI / 2) ? 'right' : 'left';
1031
+ } else if (config.type === Chart.Type.BUBBLE) {
1032
+ let element = chart.getDatasetMeta(datasetIndex).data[dataIndex];
1033
+ let chartArea = chart.chartArea,
1034
+ mid = chartArea.left + (chartArea.width / 2);
1035
+ tooltipDirection = element.x < mid ? 'left' : 'right';
1036
+ }
1037
+
1038
+ if (this._isHorizontalBar(config)) {
1039
+ // noinspection JSValidateTypes
1040
+ /**
1041
+ * @type BarElement
1042
+ */
1043
+ let element = chart.getDatasetMeta(datasetIndex).data[dataIndex];
1044
+ let height = element.height,
1045
+ width = element.width,
1046
+ // golden ratio: (a + b) / a = a / b = PHI
1047
+ // and a + b = width
1048
+ // -> b = width / (PHI + 1)
1049
+ b = width / (PHI + 1);
1050
+
1051
+ offsetY = -height / 2;
1052
+ offsetX = tooltipDirection === 'left' ? b : -b;
1053
+ } else if (scout.isOneOf(config.type, Chart.Type.LINE, Chart.Type.BUBBLE, Chart.Type.RADAR) || dataset.type === Chart.Type.LINE) {
1054
+ // noinspection JSValidateTypes
1055
+ /**
1056
+ * @type PointElement
1057
+ */
1058
+ let element = chart.getDatasetMeta(datasetIndex).data[dataIndex];
1059
+ let options = element.options,
1060
+ offset = options.hoverRadius + options.hoverBorderWidth;
1061
+ if (config.type === Chart.Type.BUBBLE) {
1062
+ offset += value.r;
1063
+ }
1064
+
1065
+ offsetY = tooltipPosition === 'top' ? -offset : offset;
1066
+ } else if (scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
1067
+ // noinspection JSValidateTypes
1068
+ /**
1069
+ * @type ArcElement
1070
+ */
1071
+ let element = chart.getDatasetMeta(datasetIndex).data[dataIndex];
1072
+ let startAngle = element.startAngle,
1073
+ endAngle = element.endAngle,
1074
+ angle = (startAngle + endAngle) / 2,
1075
+ innerRadius = element.innerRadius,
1076
+ outerRadius = element.outerRadius,
1077
+ offset = (outerRadius - innerRadius) / 2;
1078
+ offsetX = offset * Math.cos(angle);
1079
+ offsetY = offset * Math.sin(angle);
1080
+ }
1081
+
1082
+ return {
1083
+ tooltipPosition: tooltipPosition,
1084
+ tooltipDirection: tooltipDirection,
1085
+ offsetX: offsetX,
1086
+ offsetY: offsetY
734
1087
  };
735
1088
  }
736
1089
 
@@ -741,139 +1094,140 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
741
1094
 
742
1095
  config.options = $.extend(true, {}, config.options);
743
1096
 
744
- this._adjustScale(config);
745
- this._adjustScales(config);
1097
+ this._adjustScalesR(config);
1098
+ this._adjustScalesXY(config);
746
1099
  }
747
1100
 
748
- _adjustScale(config) {
1101
+ _adjustScalesR(config) {
749
1102
  if (!config || !config.type || !config.options) {
750
1103
  return;
751
1104
  }
752
1105
 
753
1106
  if (scout.isOneOf(config.type, Chart.Type.POLAR_AREA, Chart.Type.RADAR)) {
754
1107
  config.options = $.extend(true, {}, {
755
- scale: {}
1108
+ scales: {
1109
+ r: {}
1110
+ }
756
1111
  }, config.options);
757
1112
  }
758
1113
 
759
- let options = config.options;
760
- if (options.scale) {
761
- options.scale = $.extend(true, {}, {
1114
+ let options = config.options,
1115
+ scales = options ? options.scales : {};
1116
+ if (scales && scales.r) {
1117
+ scales.r = $.extend(true, {}, {
1118
+ minSpaceBetweenTicks: 35,
1119
+ beginAtZero: true,
762
1120
  angleLines: {
763
1121
  display: false
764
1122
  },
765
- gridLines: {
766
- borderDash: [2, 4]
767
- },
768
1123
  ticks: {
769
- beginAtZero: true,
770
1124
  callback: this._labelFormatter
771
1125
  },
772
1126
  pointLabels: {
773
- fontSize: ChartJs.defaults.global.defaultFontSize
1127
+ font: {
1128
+ size: ChartJs.defaults.font.size
1129
+ }
774
1130
  }
775
- }, options.scale);
1131
+ }, scales.r);
776
1132
  }
777
1133
  }
778
1134
 
779
- _adjustScales(config) {
1135
+ _adjustScalesXY(config) {
780
1136
  if (!config || !config.type || !config.options) {
781
1137
  return;
782
1138
  }
783
1139
 
784
- if (scout.isOneOf(config.type, Chart.Type.BAR, Chart.Type.BAR_HORIZONTAL, Chart.Type.LINE, Chart.Type.BUBBLE)) {
1140
+ if (scout.isOneOf(config.type, Chart.Type.BAR, Chart.Type.LINE, Chart.Type.BUBBLE)) {
785
1141
  config.options = $.extend(true, {}, {
786
1142
  scales: {
787
- xAxes: [{}],
788
- yAxes: [{}]
1143
+ x: {
1144
+ minSpaceBetweenTicks: 150
1145
+ },
1146
+ y: {
1147
+ minSpaceBetweenTicks: 35
1148
+ }
789
1149
  }
790
1150
  }, config.options);
791
1151
  }
792
1152
 
793
- this._adjustXAxes(config);
794
- this._adjustYAxes(config);
1153
+ this._adjustXAxis(config);
1154
+ this._adjustYAxis(config);
795
1155
  }
796
1156
 
797
- _adjustXAxes(config) {
798
- if (!config || !config.type || !config.options || !config.options.scales || !config.options.scales.xAxes) {
1157
+ _adjustXAxis(config) {
1158
+ if (!config || !config.type || !config.options || !config.options.scales || !config.options.scales.x) {
799
1159
  return;
800
1160
  }
801
1161
 
802
1162
  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;
1163
+ scales = config.options.scales;
1164
+
1165
+ if (this._isHorizontalBar(config) || type === Chart.Type.BUBBLE) {
1166
+ scales.x = $.extend(true, {}, {
1167
+ beginAtZero: this._isHorizontalBar(config),
1168
+ offset: type === Chart.Type.BUBBLE,
1169
+ grid: {
1170
+ drawBorder: false,
1171
+ drawTicks: false
1172
+ },
1173
+ ticks: {
1174
+ padding: 5
1175
+ }
1176
+ }, scales.x);
1177
+ } else {
1178
+ scales.x = $.extend(true, {}, {
1179
+ offset: true,
1180
+ grid: {
1181
+ display: false,
1182
+ drawBorder: false
1183
+ }
1184
+ }, scales.x);
836
1185
  }
1186
+ if (this._isHorizontalBar(config) || type === Chart.Type.BUBBLE || config.options.reformatLabels) {
1187
+ scales.x = $.extend(true, {}, {
1188
+ ticks: {
1189
+ callback: this._xLabelFormatter
1190
+ }
1191
+ }, scales.x);
1192
+ }
1193
+ scales.x.afterCalculateLabelRotation = this._xAxisFitter;
837
1194
  }
838
1195
 
839
- _adjustYAxes(config) {
840
- if (!config || !config.type || !config.options || !config.options.scales || !config.options.scales.yAxes) {
1196
+ _adjustYAxis(config) {
1197
+ if (!config || !config.type || !config.options || !config.options.scales || !config.options.scales.y) {
841
1198
  return;
842
1199
  }
843
1200
 
844
1201
  let type = config.type,
845
- yAxes = config.options.scales.yAxes;
1202
+ scales = config.options.scales;
846
1203
 
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;
1204
+ if (this._isHorizontalBar(config)) {
1205
+ scales.y = $.extend(true, {}, {
1206
+ grid: {
1207
+ display: false,
1208
+ drawBorder: false
1209
+ }
1210
+ }, scales.y);
1211
+ } else {
1212
+ scales.y = $.extend(true, {}, {
1213
+ beginAtZero: type !== Chart.Type.BUBBLE,
1214
+ grid: {
1215
+ drawBorder: false,
1216
+ drawTicks: false
1217
+ },
1218
+ ticks: {
1219
+ padding: 5
1220
+ }
1221
+ }, scales.y);
876
1222
  }
1223
+ if (!this._isHorizontalBar(config) || config.options.reformatLabels) {
1224
+ scales.y = $.extend(true, {}, {
1225
+ ticks: {
1226
+ callback: this._yLabelFormatter
1227
+ }
1228
+ }, scales.y);
1229
+ }
1230
+ scales.y.afterFit = this._yAxisFitter;
877
1231
  }
878
1232
 
879
1233
  _adjustPlugins(config) {
@@ -891,10 +1245,61 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
891
1245
  formatter: this._radialChartDatalabelsFormatter
892
1246
  }, plugins.datalabels);
893
1247
  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)) {
1248
+ // since the this._radialChartDatalabelsDisplayHandler depends on values that are animated, we need to update the labels during the animation
1249
+ this._updatingDatalabels = false;
1250
+ let animation = config.options.animation || {},
1251
+ onProgress = animation.onProgress,
1252
+ onComplete = animation.onComplete,
1253
+ updateDatalabels = animation => {
1254
+ if (this._updatingDatalabels) {
1255
+ return;
1256
+ }
1257
+ this._updatingDatalabels = true;
1258
+ // invert the _actives of the datalabel plugin and call its afterEvent-hook
1259
+ // this hook will update its _actives to chart.getActiveElements() and recalculate the labels for all elements that changed
1260
+ // setting _actives to the complement of chart.getActiveElements() guarantees that all labels are updated
1261
+ let chart = animation.chart,
1262
+ metas = chart.getSortedVisibleDatasetMetas(),
1263
+ activeElements = [...chart.getActiveElements()],
1264
+ inactiveElements = [];
1265
+
1266
+ metas.forEach((meta, datasetIndex) => {
1267
+ meta.data.forEach((element, index) => {
1268
+ let activeIndex = arrays.findIndex(activeElements, activeElement => activeElement.datasetIndex === datasetIndex && activeElement.index === index);
1269
+ if (activeIndex > 0) {
1270
+ activeElements.splice(activeIndex, 1);
1271
+ } else {
1272
+ inactiveElements.push({
1273
+ datasetIndex: datasetIndex,
1274
+ index: index,
1275
+ element: element
1276
+ });
1277
+ }
1278
+ });
1279
+ });
1280
+
1281
+ // the datalabels plugin stores its data on the chart in $datalabels (see EXPANDO_KEY in chartjs-plugin-datalabels)
1282
+ chart['$' + ChartDataLabels.id]._actives = inactiveElements;
1283
+ // noinspection JSCheckFunctionSignatures
1284
+ ChartDataLabels.afterEvent(chart);
1285
+ this._updatingDatalabels = false;
1286
+ },
1287
+ updateDatalabelsAndDefaultCallback = (animation, defaultCallback) => {
1288
+ updateDatalabels(animation);
1289
+ if (defaultCallback) {
1290
+ defaultCallback(animation);
1291
+ }
1292
+ };
1293
+
1294
+ config.options.animation = $.extend(true, {}, config.options.animation, {
1295
+ onProgress: animation => updateDatalabelsAndDefaultCallback(animation, onProgress),
1296
+ onComplete: animation => updateDatalabelsAndDefaultCallback(animation, onComplete)
1297
+ });
1298
+
1299
+ } else if (scout.isOneOf(config.type, Chart.Type.BAR, Chart.Type.LINE, Chart.Type.POLAR_AREA, Chart.Type.RADAR, Chart.Type.BUBBLE)) {
895
1300
  plugins.datalabels = $.extend(true, {}, {
896
1301
  backgroundColor: this._datalabelBackgroundColorHandler,
897
- borderRadius: 4
1302
+ borderRadius: 3
898
1303
  }, plugins.datalabels);
899
1304
  plugins.datalabels.display = 'auto';
900
1305
  }
@@ -951,8 +1356,8 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
951
1356
  }
952
1357
 
953
1358
  _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];
1359
+ if (this.chartJs && this.chartJs.config && this.chartJs.config.options) {
1360
+ return this.chartJs.config.options[identifier];
956
1361
  }
957
1362
  }
958
1363
 
@@ -996,13 +1401,17 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
996
1401
  return this.session.locale.decimalFormat.format(Math.sign(label) * abs) + abbreviation;
997
1402
  }
998
1403
 
1404
+ /**
1405
+ * @param {Scale} xAxis
1406
+ */
999
1407
  _fitXAxis(xAxis) {
1000
1408
  if (!xAxis || xAxis.labelRotation === 0) {
1001
1409
  return;
1002
1410
  }
1003
1411
  let maxHeight = this.maxXAxesTicksHeigth,
1004
- defaultGlobal = ChartJs.defaults.global,
1005
- defaultTicks = ChartJs.defaults.scale.ticks,
1412
+ fontDefaults = ChartJs.defaults.font,
1413
+ ticksDefaults = ChartJs.defaults.scale.ticks,
1414
+ ticksFontDefaults = ticksDefaults.font || {},
1006
1415
  fontSize,
1007
1416
  maxRotation;
1008
1417
  if (this.chartJs) {
@@ -1012,10 +1421,11 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1012
1421
  }
1013
1422
  if (xAxis.options && xAxis.options.ticks) {
1014
1423
  maxRotation = xAxis.options.ticks.maxRotation;
1015
- fontSize = xAxis.options.ticks.fontSize;
1424
+ let ticksFont = xAxis.options.ticks.font || {};
1425
+ fontSize = ticksFont.size;
1016
1426
  }
1017
- maxRotation = maxRotation || defaultTicks.maxRotation;
1018
- fontSize = fontSize || defaultTicks.fontSize || defaultGlobal.defaultFontSize;
1427
+ maxRotation = maxRotation || ticksDefaults.maxRotation;
1428
+ fontSize = fontSize || ticksFontDefaults.size || fontDefaults.size;
1019
1429
  // if the chart is very narrow, chart.js sometimes calculates with a negative width of the canvas
1020
1430
  // this causes NaN for labelRotation and height
1021
1431
  if (isNaN(xAxis.labelRotation)) {
@@ -1028,13 +1438,15 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1028
1438
  // => height = sin(labelRotation) * labelLength + sin(90° - labelRotation) * fontSize
1029
1439
  // <=> labelLength = (height - sin(90° - labelRotation) * fontSize) / sin(labelRotation)
1030
1440
  maxLabelLength = (maxHeight - (fontSize * Math.sin(((90 - labelRotation) / 180) * Math.PI))) / Math.sin((labelRotation / 180) * Math.PI);
1031
- if (xAxis.longestLabelWidth > maxLabelLength) {
1441
+ let labelSizes = xAxis._labelSizes || {},
1442
+ widest = labelSizes.widest || {};
1443
+ if (widest.width > maxLabelLength) {
1032
1444
  let measureText = xAxis.ctx.measureText.bind(xAxis.ctx);
1033
- xAxis._ticks.forEach(tick => {
1445
+ xAxis.ticks.forEach(tick => {
1034
1446
  tick.label = strings.truncateText(tick.label, maxLabelLength, measureText);
1035
1447
  });
1036
1448
  // reset label sizes, chart.js will recalculate them using the new truncated labels
1037
- xAxis._labelSizes = null;
1449
+ xAxis._labelSizes = undefined;
1038
1450
  }
1039
1451
  }
1040
1452
 
@@ -1043,36 +1455,41 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1043
1455
  return;
1044
1456
  }
1045
1457
  let padding = 0,
1046
- tickMarkLength = 0;
1458
+ tickLength = 0;
1047
1459
  if (yAxis.options && yAxis.options.ticks) {
1048
1460
  padding = yAxis.options.ticks.padding || 0;
1049
1461
  }
1050
- if (yAxis.options && yAxis.options.gridLines) {
1051
- tickMarkLength = yAxis.options.gridLines.tickMarkLength || 0;
1462
+ if (yAxis.options && yAxis.options.grid) {
1463
+ tickLength = yAxis.options.grid.tickLength || 0;
1052
1464
  }
1053
- if (yAxis.longestLabelWidth > yAxis.maxWidth - padding) {
1054
- let horizontalSpace = yAxis.maxWidth - padding - tickMarkLength,
1465
+ let labelSizes = yAxis._labelSizes || {},
1466
+ widest = labelSizes.widest || {};
1467
+ if (widest.width > yAxis.maxWidth - padding) {
1468
+ let horizontalSpace = yAxis.maxWidth - padding - tickLength,
1055
1469
  measureText = yAxis.ctx.measureText.bind(yAxis.ctx);
1056
- yAxis._ticks.forEach(tick => {
1470
+ yAxis.ticks.forEach(tick => {
1057
1471
  tick.label = strings.truncateText(tick.label, horizontalSpace, measureText);
1058
1472
  });
1059
1473
  }
1060
1474
  }
1061
1475
 
1062
1476
  _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,
1477
+ // noinspection JSValidateTypes
1478
+ /**
1479
+ * @type ArcElement
1480
+ */
1481
+ let element = context.chart.getDatasetMeta(context.datasetIndex).data[context.dataIndex];
1482
+ // Compute the biggest circle that fits inside sector/arc with center in the middle between inner and outer radius.
1483
+ // 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.
1484
+ // 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.
1485
+ // circle C1:
1486
+ let midRadius = (element.outerRadius + element.innerRadius) / 2,
1070
1487
  // 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,
1488
+ angle = Math.min((element.endAngle - element.startAngle), Math.PI) / 2,
1072
1489
  radius1 = Math.abs(Math.sin(angle)) * midRadius,
1073
1490
  diameter1 = radius1 * 2,
1074
1491
  // circle C2:
1075
- diameter2 = model.outerRadius - model.innerRadius;
1492
+ diameter2 = element.outerRadius - element.innerRadius;
1076
1493
  return Math.min(diameter1, diameter2) > this.minRadialChartDatalabelSpace;
1077
1494
  }
1078
1495
 
@@ -1092,10 +1509,10 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1092
1509
 
1093
1510
  _computeSumOfVisibleElements(context) {
1094
1511
  let dataset = context.dataset,
1095
- meta = context.chart.getDatasetMeta(context.datasetIndex),
1512
+ chart = context.chart,
1096
1513
  sum = 0;
1097
1514
  for (let i = 0; i < dataset.data.length; i++) {
1098
- if (meta.data[i] && !meta.data[i].hidden) {
1515
+ if (chart.getDataVisibility(i)) {
1099
1516
  sum += dataset.data[i];
1100
1517
  }
1101
1518
  }
@@ -1117,9 +1534,8 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1117
1534
  this._adjustColorSchemeCssClass(config);
1118
1535
  this._adjustDatasetColors(config);
1119
1536
  this._adjustLegendColors(config);
1120
- this._adjustTooltipColors(config);
1121
- this._adjustScaleColors(config);
1122
- this._adjustScalesColors(config);
1537
+ this._adjustScalesRColors(config);
1538
+ this._adjustScalesXYColors(config);
1123
1539
  this._adjustPluginColors(config);
1124
1540
  }
1125
1541
 
@@ -1148,7 +1564,7 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1148
1564
  checkedBackgroundColors: [],
1149
1565
  checkedHoverBackgroundColors: [],
1150
1566
  legendColors: [],
1151
- pointHoverColor: this._computePointHoverColor(type)
1567
+ pointHoverColors: []
1152
1568
  };
1153
1569
 
1154
1570
  colors = $.extend(true, colors, this._computeDatasetColors(config, multipleColorsPerDataset));
@@ -1159,9 +1575,12 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1159
1575
  hoverBackgroundColor = (multipleColorsPerDataset ? colors.hoverBackgroundColors : colors.hoverBackgroundColors[idx]),
1160
1576
  hoverBorderColor = (multipleColorsPerDataset ? colors.hoverBorderColors : colors.hoverBorderColors[idx]),
1161
1577
  legendColor = (multipleColorsPerDataset ? colors.legendColors : colors.legendColors[idx]),
1162
- pointHoverBackgroundColor = colors.pointHoverColor;
1578
+ pointHoverBackgroundColor = (multipleColorsPerDataset ? colors.pointHoverColors : colors.legendColors[idx]);
1163
1579
 
1164
1580
  let setProperty = (identifier, value) => {
1581
+ if (typeof elem[identifier] === 'function') {
1582
+ return;
1583
+ }
1165
1584
  if (value && value.length) {
1166
1585
  elem[identifier] = Array.isArray(value) ? [...value] : value;
1167
1586
  }
@@ -1171,10 +1590,14 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1171
1590
  setProperty('hoverBackgroundColor', hoverBackgroundColor);
1172
1591
  setProperty('hoverBorderColor', hoverBorderColor);
1173
1592
  setProperty('legendColor', legendColor);
1174
- setProperty('pointHoverBackgroundColor', pointHoverBackgroundColor);
1593
+ if (scout.isOneOf(type, Chart.Type.LINE, Chart.Type.RADAR) || (type === Chart.Type.BAR && elem.type === Chart.Type.LINE)) {
1594
+ setProperty('pointHoverBackgroundColor', pointHoverBackgroundColor);
1595
+ setProperty('pointBorderColor', this.firstOpaqueBackgroundColor);
1596
+ setProperty('pointHoverBorderColor', this.firstOpaqueBackgroundColor);
1597
+ }
1175
1598
  if (checkable) {
1176
1599
  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)) {
1600
+ 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
1601
  let uncheckedBackgroundColor = (multipleColorsPerDataset ? colors.backgroundColors : arrays.init(datasetLength, colors.backgroundColors[idx])),
1179
1602
  uncheckedHoverBackgroundColor = (multipleColorsPerDataset ? colors.hoverBackgroundColors : arrays.init(datasetLength, colors.hoverBackgroundColors[idx])),
1180
1603
 
@@ -1201,24 +1624,21 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1201
1624
  setProperty('pointBackgroundColor', elem.uncheckedPointBackgroundColor);
1202
1625
  setProperty('pointHoverBackgroundColor', elem.uncheckedPointHoverBackgroundColor);
1203
1626
 
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);
1627
+ let uncheckedPointRadius = arrays.init(datasetLength, ((config.options.elements || {}).point || {}).radius || ChartJs.defaults.elements.point.radius),
1628
+ checkedPointRadius = arrays.init(datasetLength, (((config.options.elements || {}).point || {}).hoverRadius || ChartJs.defaults.elements.point.hoverRadius) - 1);
1206
1629
  setProperty('uncheckedPointRadius', uncheckedPointRadius);
1207
1630
  setProperty('checkedPointRadius', checkedPointRadius);
1208
1631
 
1209
1632
  setProperty('pointRadius', elem.uncheckedPointRadius);
1210
1633
  }
1211
1634
  }
1635
+ this._adjustDatasetBorderWidths(elem);
1212
1636
  });
1213
1637
  if (checkable) {
1214
1638
  this._checkItems(config);
1215
1639
  }
1216
1640
  }
1217
1641
 
1218
- _computePointHoverColor(type) {
1219
- return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'point hover'], 'fill').fill;
1220
- }
1221
-
1222
1642
  _computeDatasetColors(config, multipleColorsPerDataset) {
1223
1643
  if (!config || !config.data || !config.type) {
1224
1644
  return {};
@@ -1258,7 +1678,8 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1258
1678
  hoverBorderColors: [],
1259
1679
  checkedBackgroundColors: [],
1260
1680
  checkedHoverBackgroundColors: [],
1261
- legendColors: []
1681
+ legendColors: [],
1682
+ pointHoverColors: []
1262
1683
  };
1263
1684
 
1264
1685
  let types = [];
@@ -1277,6 +1698,8 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1277
1698
  colors.checkedHoverBackgroundColors.push(this._computeCheckedHoverBackgroundColor(type, index, checkable));
1278
1699
 
1279
1700
  colors.legendColors.push(this._computeLegendColor(type, index));
1701
+
1702
+ colors.pointHoverColors.push(this._computePointHoverColor(type, index));
1280
1703
  });
1281
1704
 
1282
1705
  return colors;
@@ -1318,6 +1741,10 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1318
1741
  return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'color' + (index % this.numSupportedColors) + ' legend'], 'fill').fill;
1319
1742
  }
1320
1743
 
1744
+ _computePointHoverColor(type, index) {
1745
+ return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'color' + (index % this.numSupportedColors) + ' point hover'], 'fill').fill;
1746
+ }
1747
+
1321
1748
  _computeDatasetColorsChartValueGroups(config, multipleColorsPerDataset) {
1322
1749
  if (!config || !config.type || !this.chart.data) {
1323
1750
  return {};
@@ -1333,7 +1760,8 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1333
1760
  hoverBorderColors: [],
1334
1761
  checkedBackgroundColors: [],
1335
1762
  checkedHoverBackgroundColors: [],
1336
- legendColors: []
1763
+ legendColors: [],
1764
+ pointHoverColors: []
1337
1765
  };
1338
1766
 
1339
1767
  this.chart.data.chartValueGroups.forEach(elem => {
@@ -1392,6 +1820,8 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1392
1820
  colors.checkedHoverBackgroundColors.push(adjustColor(checkedHoverBackgroundOpacity, checkedHoverBackgroundDarker));
1393
1821
 
1394
1822
  colors.legendColors.push(adjustColor(1, 0));
1823
+
1824
+ colors.pointHoverColors.push(adjustColor(1, 0));
1395
1825
  });
1396
1826
  colors.datalabelColor = this._computeDatalabelColor(type);
1397
1827
 
@@ -1434,10 +1864,12 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1434
1864
  }
1435
1865
 
1436
1866
  config.options = $.extend(true, {}, config.options, {
1437
- legend: {
1438
- labels: {
1439
- fontColor: this._computeLabelColor(config.type),
1440
- generateLabels: this._legendLabelGenerator
1867
+ plugins: {
1868
+ legend: {
1869
+ labels: {
1870
+ color: this._computeLabelColor(config.type),
1871
+ generateLabels: this._legendLabelGenerator
1872
+ }
1441
1873
  }
1442
1874
  }
1443
1875
  });
@@ -1449,33 +1881,50 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1449
1881
 
1450
1882
  _generateLegendLabels(chart) {
1451
1883
  let config = chart.config,
1452
- data = config.data,
1884
+ defaultTypeGenerateLabels;
1885
+ // noinspection DuplicatedCode
1886
+ if (ChartJs.overrides[config.type] && ChartJs.overrides[config.type].plugins && ChartJs.overrides[config.type].plugins.legend && ChartJs.overrides[config.type].plugins.legend.labels) {
1887
+ defaultTypeGenerateLabels = ChartJs.overrides[config.type].plugins.legend.labels.generateLabels;
1888
+ }
1889
+ let defaultGenerateLabels = defaultTypeGenerateLabels || ChartJs.defaults.plugins.legend.labels.generateLabels;
1890
+ let labels = defaultGenerateLabels.call(chart, chart);
1891
+ if (!this.rendered || this.removing) {
1892
+ return labels;
1893
+ }
1894
+ let data = config.data,
1453
1895
  measureText = chart.ctx.measureText.bind(chart.ctx),
1896
+ legend = chart.legend,
1897
+ legendLabelOptions = ((legend || {}).options || {}).labels || {},
1898
+ boxWidth = legendLabelOptions.boxWidth || 0,
1899
+ padding = legendLabelOptions.padding || 0,
1454
1900
  horizontalSpace;
1455
- if (scout.isOneOf(config.options.legend.position, Chart.Position.LEFT, Chart.Position.RIGHT)) {
1456
- horizontalSpace = Math.min(250, this.$canvas.cssWidth() / 3);
1901
+ if (scout.isOneOf(config.options.plugins.legend.position, Chart.Position.LEFT, Chart.Position.RIGHT)) {
1902
+ if (legend.maxWidth || legend.width) {
1903
+ horizontalSpace = Math.max((legend.maxWidth || legend.width) - boxWidth - 2 * padding, 0);
1904
+ }
1905
+ horizontalSpace = Math.min(250, horizontalSpace || 0, this.$canvas.cssWidth() / 3);
1906
+
1457
1907
  } else {
1458
1908
  horizontalSpace = Math.min(250, this.$canvas.cssWidth() * 2 / 3);
1459
1909
  }
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
1910
  labels.forEach((elem, idx) => {
1467
1911
  elem.text = strings.truncateText(elem.text, horizontalSpace, measureText);
1468
1912
  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);
1913
+ legendColor, borderColor, backgroundColor;
1914
+ if (dataset && scout.isOneOf((dataset.type || config.type), Chart.Type.LINE, Chart.Type.BAR, Chart.Type.RADAR, Chart.Type.BUBBLE)) {
1915
+ legendColor = dataset.legendColor;
1916
+ borderColor = this._adjustColorOpacity(dataset.borderColor, 1);
1472
1917
  } else if (data.datasets.length && scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
1473
1918
  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);
1919
+ legendColor = Array.isArray(dataset.legendColor) ? dataset.legendColor[idx] : dataset.legendColor;
1920
+ backgroundColor = Array.isArray(dataset.backgroundColor) ? dataset.backgroundColor[idx] : dataset.backgroundColor;
1921
+ backgroundColor = this._adjustColorOpacity(backgroundColor, 1);
1922
+ }
1923
+ if (objects.isFunction(legendColor)) {
1924
+ legendColor = legendColor.call(chart, idx);
1477
1925
  }
1478
- if (typeof fillStyle !== 'function') {
1926
+ let fillStyle = legendColor || backgroundColor || borderColor;
1927
+ if (!objects.isFunction(fillStyle)) {
1479
1928
  elem.fillStyle = fillStyle;
1480
1929
  elem.strokeStyle = fillStyle;
1481
1930
  }
@@ -1483,48 +1932,24 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1483
1932
  return labels;
1484
1933
  }
1485
1934
 
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) {
1935
+ _adjustScalesRColors(config) {
1936
+ if (!config || !config.type || !config.options || !config.options.scales || !config.options.scales.r) {
1513
1937
  return;
1514
1938
  }
1515
1939
 
1516
1940
  let labelColor = this._computeLabelColor(config.type),
1517
1941
  labelBackdropColor = this._computeLabelBackdropColor(config.type),
1518
- gridColor = this._computeGridColor(config.type);
1942
+ gridColor = this._computeGridColor(config.type),
1943
+ scaleTicksColor = this._computeScaleTicksColor(config.type);
1519
1944
 
1520
- config.options.scale.ticks = $.extend(true, {}, config.options.scale.ticks, {
1521
- fontColor: gridColor,
1945
+ config.options.scales.r.ticks = $.extend(true, {}, config.options.scales.r.ticks, {
1946
+ color: scaleTicksColor,
1522
1947
  backdropColor: labelBackdropColor
1523
1948
  });
1524
- config.options.scale.pointLabels = $.extend(true, {}, config.options.scale.pointLabels, {
1525
- fontColor: labelColor
1949
+ config.options.scales.r.pointLabels = $.extend(true, {}, config.options.scales.r.pointLabels, {
1950
+ color: labelColor
1526
1951
  });
1527
- config.options.scale.gridLines = $.extend(true, {}, config.options.scale.gridLines, {
1952
+ config.options.scales.r.grid = $.extend(true, {}, config.options.scales.r.grid, {
1528
1953
  color: gridColor
1529
1954
  });
1530
1955
  }
@@ -1537,14 +1962,22 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1537
1962
  return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'grid'], 'fill').fill;
1538
1963
  }
1539
1964
 
1540
- _adjustScalesColors(config) {
1965
+ _computeScaleTicksColor(type) {
1966
+ return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'scale-ticks'], 'fill').fill;
1967
+ }
1968
+
1969
+ _adjustScalesXYColors(config) {
1541
1970
  if (!config || !config.type || !config.options || !config.options.scales) {
1542
1971
  return;
1543
1972
  }
1544
1973
 
1545
- let xAxes = config.options.scales.xAxes || [],
1546
- yAxes = config.options.scales.yAxes || [],
1547
- axes = [...xAxes, ...yAxes];
1974
+ let axes = [];
1975
+ if (config.options.scales.x) {
1976
+ axes.push(config.options.scales.x);
1977
+ }
1978
+ if (config.options.scales.y) {
1979
+ axes.push(config.options.scales.y);
1980
+ }
1548
1981
 
1549
1982
  if (!axes.length) {
1550
1983
  return;
@@ -1555,15 +1988,14 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1555
1988
  axisLabelColor = this._computeAxisLabelColor(config.type);
1556
1989
 
1557
1990
  axes.forEach(elem => {
1558
- elem.gridLines = $.extend(true, {}, elem.gridLines, {
1559
- zeroLineColor: gridColor,
1991
+ elem.grid = $.extend(true, {}, elem.grid, {
1560
1992
  color: gridColor
1561
1993
  });
1562
1994
  elem.ticks = $.extend(true, {}, elem.ticks, {
1563
- fontColor: labelColor
1995
+ color: labelColor
1564
1996
  });
1565
- elem.scaleLabel = $.extend(true, {}, elem.scaleLabel, {
1566
- fontColor: axisLabelColor
1997
+ elem.title = $.extend(true, {}, elem.title, {
1998
+ color: axisLabelColor
1567
1999
  });
1568
2000
  });
1569
2001
  }
@@ -1606,33 +2038,39 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1606
2038
  config.options.onHover = this._hoverHandler;
1607
2039
  }
1608
2040
 
1609
- if (!config.options.legend) {
2041
+ if (!config.options.plugins || !config.options.plugins.legend) {
1610
2042
  return;
1611
2043
  }
1612
2044
 
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;
2045
+ let legend = config.options.plugins.legend;
2046
+ if (legend.clickable) {
2047
+ legend.onClick = this._legendClickHandler;
2048
+ legend.onHover = this._legendPointerHoverHandler;
2049
+ legend.onLeave = this._legendPointerLeaveHandler;
1617
2050
  } else {
1618
- config.options.legend.onClick = e => e.stopPropagation();
1619
- config.options.legend.onHover = this._legendHoverHandler;
1620
- config.options.legend.onLeave = this._legendLeaveHandler;
2051
+ legend.onClick = e => e.native.stopPropagation();
2052
+ legend.onHover = this._legendHoverHandler;
2053
+ legend.onLeave = this._legendLeaveHandler;
1621
2054
  }
1622
2055
  }
1623
2056
 
1624
2057
  /**
1625
2058
  * @param {object[]} items
1626
- * @param {number} items._index
1627
- * @param {number} items._datasetIndex
2059
+ * @param {number} items.index
2060
+ * @param {number} items.datasetIndex
1628
2061
  */
1629
2062
  _onClick(event, items) {
1630
- if (!items.length || this._isMaxSegmentsExceeded(this.chartJs.config, items[0]._index)) {
2063
+ if (!items.length) {
2064
+ return;
2065
+ }
2066
+ let relevantItem = this._selectRelevantItem(items);
2067
+
2068
+ if (this._isMaxSegmentsExceeded(this.chartJs.config, relevantItem.index)) {
1631
2069
  return;
1632
2070
  }
1633
2071
 
1634
- let itemIndex = items[0]._index,
1635
- datasetIndex = items[0]._datasetIndex,
2072
+ let itemIndex = relevantItem.index,
2073
+ datasetIndex = relevantItem.datasetIndex,
1636
2074
  clickObject = {
1637
2075
  datasetIndex: datasetIndex,
1638
2076
  dataIndex: itemIndex
@@ -1647,10 +2085,34 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1647
2085
 
1648
2086
  let e = new Event();
1649
2087
  e.data = clickObject;
1650
- e.originalEvent = event;
2088
+ e.originalEvent = event.native;
1651
2089
  this.chart._onValueClick(e);
1652
2090
  }
1653
2091
 
2092
+ /**
2093
+ * Selects the most relevant item. Default is the first item.
2094
+ *
2095
+ * @param {object[]} items
2096
+ * @param {number} items.index
2097
+ * @param {number} items.datasetIndex
2098
+ * @private
2099
+ */
2100
+ _selectRelevantItem(items) {
2101
+ let chartDatasets = this.chartJs.config.data.datasets;
2102
+ let relevantItem = items[0];
2103
+
2104
+ if (this.chartJs.config.type === Chart.Type.BUBBLE) {
2105
+ // The smallest bubble, which is drawn in the foreground, is the most relevant item for the bubble chart.
2106
+ // 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)
2107
+ items.forEach(item => {
2108
+ if (chartDatasets[item.datasetIndex].data[item.index].z <= chartDatasets[relevantItem.datasetIndex].data[relevantItem.index].z) {
2109
+ relevantItem = item;
2110
+ }
2111
+ });
2112
+ }
2113
+ return relevantItem;
2114
+ }
2115
+
1654
2116
  _onHover(event, items) {
1655
2117
  if (!this.chartJs.config || !this.chartJs.config.type) {
1656
2118
  return;
@@ -1664,14 +2126,14 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1664
2126
 
1665
2127
  let update = false;
1666
2128
  if (this.resetDatasetAfterHover) {
1667
- this._adjustColors(config);
2129
+ this._restoreBackgroundColor();
1668
2130
  this.resetDatasetAfterHover = false;
1669
2131
  update = true;
1670
2132
  }
1671
2133
  items.forEach(item => {
1672
- let dataset = config.data.datasets[item._datasetIndex];
2134
+ let dataset = config.data.datasets[item.datasetIndex];
1673
2135
  if (scout.isOneOf((dataset.type || type), Chart.Type.LINE, Chart.Type.RADAR)) {
1674
- dataset.backgroundColor = dataset.hoverBackgroundColor;
2136
+ this._setHoverBackgroundColor(dataset);
1675
2137
  this.resetDatasetAfterHover = true;
1676
2138
  update = true;
1677
2139
  }
@@ -1683,35 +2145,38 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1683
2145
 
1684
2146
  _onHoverPointer(event, items) {
1685
2147
  this._onHover(event, items);
1686
- if (items.length && !this._isMaxSegmentsExceeded(this.chartJs.config, items[0]._index)) {
2148
+ if (!this.rendered || this.removing) {
2149
+ return;
2150
+ }
2151
+ if (items.length && !this._isMaxSegmentsExceeded(this.chartJs.config, items[0].index)) {
1687
2152
  this.$canvas.css('cursor', 'pointer');
1688
2153
  } else {
1689
2154
  this.$canvas.css('cursor', 'default');
1690
2155
  }
1691
2156
  }
1692
2157
 
1693
- _onLegendClick(event, item) {
2158
+ _onLegendClick(e, legendItem, legend) {
1694
2159
  if (!this.chartJs.config || !this.chartJs.config.type) {
1695
2160
  return;
1696
2161
  }
1697
2162
 
1698
2163
  let type = this.chartJs.config.type,
1699
2164
  defaultTypeLegendClick;
1700
- if (ChartJs.defaults[type] && ChartJs.defaults[type].legend) {
1701
- defaultTypeLegendClick = ChartJs.defaults[type].legend.onClick;
2165
+ if (ChartJs.overrides[type] && ChartJs.overrides[type].plugins && ChartJs.overrides[type].plugins.legend) {
2166
+ defaultTypeLegendClick = ChartJs.overrides[type].plugins.legend.onClick;
1702
2167
  }
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);
2168
+ let defaultLegendClick = defaultTypeLegendClick || ChartJs.defaults.plugins.legend.onClick;
2169
+ defaultLegendClick.call(this.chartJs, e, legendItem, legend);
2170
+ this._onLegendLeave(e, legendItem, legend);
2171
+ this._onLegendHoverPointer(e, legendItem, true);
1707
2172
  }
1708
2173
 
1709
- _onLegendHover(event, item, animated) {
1710
- let index = item.datasetIndex,
2174
+ _onLegendHover(e, legendItem, legend) {
2175
+ let index = legendItem.datasetIndex,
1711
2176
  config = this.chartJs.config,
1712
2177
  type = config.type;
1713
2178
  if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
1714
- index = item.index;
2179
+ index = legendItem.index;
1715
2180
  }
1716
2181
 
1717
2182
  if (this.legendHoverDatasets.indexOf(index) > -1) {
@@ -1721,29 +2186,28 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1721
2186
  let dataset = config.data.datasets[index],
1722
2187
  datasetType = dataset ? dataset.type : null;
1723
2188
  if ((datasetType || type) === Chart.Type.LINE) {
1724
- dataset.backgroundColor = dataset.hoverBackgroundColor;
2189
+ this._setHoverBackgroundColor(dataset);
1725
2190
  this.chartJs.update();
1726
2191
  }
1727
2192
  this._updateHoverStyle(index, true);
1728
- if (animated) {
1729
- this.chartJs.render();
1730
- } else {
1731
- this.chartJs.render({duration: 0});
1732
- }
2193
+ this.chartJs.render();
1733
2194
  this.legendHoverDatasets.push(index);
1734
2195
  }
1735
2196
 
1736
- _onLegendHoverPointer(event, item, animated) {
1737
- this._onLegendHover(event, item, animated);
2197
+ _onLegendHoverPointer(e, legendItem, legend) {
2198
+ this._onLegendHover(e, legendItem, legend);
2199
+ if (!this.rendered || this.removing) {
2200
+ return;
2201
+ }
1738
2202
  this.$canvas.css('cursor', 'pointer');
1739
2203
  }
1740
2204
 
1741
- _onLegendLeave(event, item) {
1742
- let index = item.datasetIndex,
2205
+ _onLegendLeave(e, legendItem, legend) {
2206
+ let index = legendItem.datasetIndex,
1743
2207
  config = this.chartJs.config,
1744
2208
  type = config.type;
1745
2209
  if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
1746
- index = item.index;
2210
+ index = legendItem.index;
1747
2211
  }
1748
2212
 
1749
2213
  if (this.legendHoverDatasets.indexOf(index) < 0) {
@@ -1753,7 +2217,7 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1753
2217
  let dataset = config.data.datasets[index],
1754
2218
  datasetType = dataset ? dataset.type : null;
1755
2219
  if ((datasetType || type) === Chart.Type.LINE) {
1756
- this._adjustColors(config);
2220
+ this._restoreBackgroundColor(dataset);
1757
2221
  this.chartJs.update();
1758
2222
  }
1759
2223
  this._updateHoverStyle(index, false);
@@ -1761,30 +2225,76 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1761
2225
  this.legendHoverDatasets.splice(this.legendHoverDatasets.indexOf(index), 1);
1762
2226
  }
1763
2227
 
1764
- _onLegendLeavePointer(event, item) {
1765
- this._onLegendLeave(event, item);
2228
+ /**
2229
+ * Sets the hover background color as the datasets background color.
2230
+ * This little workaround is necessary for the line chart, which does not support a native hover effect.
2231
+ * The previous background color will be backuped on the dataset property "backgroundColorBackup"
2232
+ * and can be restored with {@link _restoreBackgroundColor}.
2233
+ * @param {Dataset} dataset
2234
+ */
2235
+ _setHoverBackgroundColor(dataset) {
2236
+ if (!dataset) {
2237
+ return;
2238
+ }
2239
+ // backup the old background color first
2240
+ dataset.backgroundColorBackup = dataset.backgroundColor;
2241
+ // overwrite the current background color with the hover color
2242
+ dataset.backgroundColor = dataset.hoverBackgroundColor;
2243
+ this._adjustDatasetBorderWidths(dataset);
2244
+ }
2245
+
2246
+ /**
2247
+ * Restores the background color of a dataset or of all datasets,
2248
+ * if they were previously overwritten by {@link _setHoverBackgroundColor}.
2249
+ * @param {Dataset} [dataset]
2250
+ */
2251
+ _restoreBackgroundColor(dataset) {
2252
+ if (dataset) {
2253
+ dataset.backgroundColor = dataset.backgroundColorBackup || dataset.backgroundColor;
2254
+ delete dataset.backgroundColorBackup;
2255
+ this._adjustDatasetBorderWidths(dataset);
2256
+ } else {
2257
+ this.chartJs.config.data.datasets.forEach(dataset => this._restoreBackgroundColor(dataset));
2258
+ }
2259
+ }
2260
+
2261
+ _onLegendLeavePointer(e, legendItem, legend) {
2262
+ this._onLegendLeave(e, legendItem, legend);
2263
+ if (!this.rendered || this.removing) {
2264
+ return;
2265
+ }
1766
2266
  this.$canvas.css('cursor', 'default');
1767
2267
  }
1768
2268
 
1769
- _updateHoverStyle(index, enabled) {
2269
+ _updateHoverStyle(datasetIndex, enabled) {
1770
2270
  let config = this.chartJs.config,
1771
2271
  type = config.type,
2272
+ mode,
2273
+ elements = [],
1772
2274
  datasets = config.data.datasets,
1773
- dataset = datasets ? datasets[index] : null,
2275
+ dataset = datasets ? datasets[datasetIndex] : null,
1774
2276
  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);
2277
+ if (scout.isOneOf(type, Chart.Type.LINE, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA) || datasetType === Chart.Type.LINE) {
2278
+ mode = 'point';
1783
2279
  } 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
- }
2280
+ mode = 'dataset';
2281
+ }
2282
+ if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
2283
+ this.chartJs.getSortedVisibleDatasetMetas().forEach((meta, index) => elements.push({
2284
+ element: meta.data[datasetIndex],
2285
+ datasetIndex: index,
2286
+ index: datasetIndex
2287
+ }));
2288
+ } else {
2289
+ this.chartJs.getDatasetMeta(datasetIndex).data.forEach((element, index) => elements.push({
2290
+ element: element,
2291
+ datasetIndex: datasetIndex,
2292
+ index: index
2293
+ }));
2294
+ }
2295
+ if (elements && elements.length) {
2296
+ // noinspection JSCheckFunctionSignatures
2297
+ this.chartJs.updateHoverStyle(elements, mode, enabled);
1788
2298
  }
1789
2299
  }
1790
2300
 
@@ -1827,8 +2337,8 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1827
2337
  // Compute a scalingFactor and an offset to get the new radius newR = r * scalingFactor + offset.
1828
2338
  bubbleScalingFactor = 1,
1829
2339
  bubbleRadiusOffset = 0,
1830
- sizeOfLargestBubble = config.bubble ? config.bubble.sizeOfLargestBubble : 0,
1831
- minBubbleSize = config.bubble ? config.bubble.minBubbleSize : 0;
2340
+ sizeOfLargestBubble = (config.options || {}).bubble ? config.options.bubble.sizeOfLargestBubble : 0,
2341
+ minBubbleSize = (config.options || {}).bubble ? config.options.bubble.minBubbleSize : 0;
1832
2342
  if (sizeOfLargestBubble) {
1833
2343
  let width = Math.abs(chartArea.right - chartArea.left),
1834
2344
  height = Math.abs(chartArea.top - chartArea.bottom);
@@ -1977,39 +2487,48 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
1977
2487
  }
1978
2488
 
1979
2489
  _adjustGridMaxMin(config, chartArea) {
1980
- if (!config || !config.type || !config.options || !config.options.adjustGridMaxMin || (!config.options.scale && !config.options.scales) || !chartArea) {
2490
+ if (!config || !config.type || !config.options || !config.options.adjustGridMaxMin || !config.options.scales || !chartArea) {
1981
2491
  return;
1982
2492
  }
1983
2493
 
1984
2494
  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)) {
2495
+ if (!scout.isOneOf(type, Chart.Type.BAR, Chart.Type.LINE, Chart.Type.POLAR_AREA, Chart.Type.RADAR, Chart.Type.BUBBLE)) {
1986
2496
  return;
1987
2497
  }
1988
2498
 
2499
+ let scales = config.options.scales,
2500
+ xAxis = scales.x,
2501
+ yAxis = scales.y,
2502
+ rAxis = scales.r,
2503
+ minSpaceBetweenXTicks = xAxis ? xAxis.minSpaceBetweenTicks : 1,
2504
+ minSpaceBetweenYTicks = yAxis ? yAxis.minSpaceBetweenTicks : 1;
2505
+
2506
+ if (rAxis) {
2507
+ minSpaceBetweenXTicks = rAxis.minSpaceBetweenTicks;
2508
+ minSpaceBetweenYTicks = rAxis.minSpaceBetweenTicks;
2509
+ }
2510
+
1989
2511
  let width = Math.abs(chartArea.right - chartArea.left),
1990
2512
  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);
2513
+ maxXTicks = Math.max(Math.floor(width / minSpaceBetweenXTicks) + 1, 3),
2514
+ maxYTicks = Math.max(Math.floor(height / minSpaceBetweenYTicks) + 1, 3);
1993
2515
 
1994
2516
  let yBoundaries = this._computeYBoundaries(config, height),
1995
2517
  yBoundary = yBoundaries.yBoundary,
1996
2518
  yBoundaryDiffType = yBoundaries.yBoundaryDiffType;
1997
2519
 
1998
- if (config.options.scale) {
1999
- this._adjustScaleMaxMin(config.options.scale, maxYTicks, yBoundary);
2520
+ if (rAxis) {
2521
+ this._adjustAxisMaxMin(rAxis, Math.ceil(Math.min(maxXTicks, maxYTicks) / 2), yBoundary);
2000
2522
  return;
2001
2523
  }
2002
2524
 
2003
- let xAxes = config.options.scales.xAxes,
2004
- yAxes = config.options.scales.yAxes;
2005
-
2006
2525
  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);
2526
+ this._adjustAxisMaxMin(yAxis, maxYTicks, yBoundary);
2527
+ this._adjustAxisMaxMin(scales.yDiffType, maxYTicks, yBoundaryDiffType);
2528
+ } else if (this._isHorizontalBar(config)) {
2529
+ this._adjustAxisMaxMin(xAxis, maxXTicks, yBoundary);
2011
2530
  } else {
2012
- this._adjustAxes(yAxes, maxYTicks, yBoundary);
2531
+ this._adjustAxisMaxMin(yAxis, maxYTicks, yBoundary);
2013
2532
  }
2014
2533
 
2015
2534
  if (type !== Chart.Type.BUBBLE) {
@@ -2017,7 +2536,7 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
2017
2536
  }
2018
2537
 
2019
2538
  let xBoundary = this._computeXBoundaryBubble(config, width);
2020
- this._adjustAxes(xAxes, maxXTicks, xBoundary);
2539
+ this._adjustAxisMaxMin(xAxis, maxXTicks, xBoundary);
2021
2540
  }
2022
2541
 
2023
2542
  _computeBoundaryBubble(config, identifier, space) {
@@ -2026,10 +2545,9 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
2026
2545
  }
2027
2546
 
2028
2547
  let datasets = config.data.datasets,
2029
- axes = config.options.scales[identifier + 'Axes'],
2030
- axis = (axes && axes.length) ? axes[0] : null,
2548
+ axis = config.options.scales[identifier],
2031
2549
  offset = axis && axis.offset,
2032
- labelMap = config.options.scales[identifier + 'LabelMap'],
2550
+ labelMap = config.options[identifier + 'LabelMap'],
2033
2551
  boundary;
2034
2552
 
2035
2553
  let maxR = this._computeMaxMinValue(datasets, 'r', true).maxValue,
@@ -2105,89 +2623,83 @@ export default class ChartJsRenderer extends AbstractChartRenderer {
2105
2623
  return;
2106
2624
  }
2107
2625
 
2108
- if (!config.options || !config.options.scales || !config.options.scales.yAxes || config.options.scales.yAxes.length !== 1) {
2626
+ if (!config.options || !config.options.scales || !config.options.scales.y || config.options.scales.yDiffType) {
2109
2627
  return;
2110
2628
  }
2111
2629
 
2112
2630
  let type = config.type,
2113
- scales = config.options.scales,
2114
- yAxis = scales.yAxes[0],
2631
+ options = config.options,
2632
+ scales = options.scales,
2633
+ yAxis = scales.y,
2115
2634
  yAxisDiffType = $.extend(true, {}, yAxis);
2116
- scales.yAxes.push(yAxisDiffType);
2635
+ scales.yDiffType = yAxisDiffType;
2117
2636
 
2118
- yAxis.id = 'yAxis';
2119
- yAxisDiffType.id = 'yAxisDiffType';
2637
+ yAxis.id = 'y';
2638
+ yAxisDiffType.id = 'yDiffType';
2120
2639
 
2121
2640
  if (config.data && config.data.datasets && config.data.datasets.length && config.data.datasets[0].type && config.data.datasets[0].type !== type) {
2122
2641
  yAxisDiffType.position = Chart.Position.LEFT;
2123
2642
  yAxis.position = Chart.Position.RIGHT;
2124
- yAxis.gridLines.drawOnChartArea = false;
2643
+ yAxis.grid.drawOnChartArea = false;
2125
2644
  } else {
2126
2645
  yAxis.position = Chart.Position.LEFT;
2127
2646
  yAxisDiffType.position = Chart.Position.RIGHT;
2128
- yAxisDiffType.gridLines.drawOnChartArea = false;
2647
+ yAxisDiffType.grid.drawOnChartArea = false;
2129
2648
  }
2130
2649
 
2131
- yAxis.gridLines.drawBorder = true;
2132
- yAxis.gridLines.drawTicks = true;
2133
- yAxisDiffType.gridLines.drawBorder = true;
2134
- yAxisDiffType.gridLines.drawTicks = true;
2650
+ yAxis.grid.drawBorder = true;
2651
+ yAxis.grid.drawTicks = true;
2652
+ yAxisDiffType.grid.drawBorder = true;
2653
+ yAxisDiffType.grid.drawTicks = true;
2135
2654
 
2136
2655
  let yAxisType = (datasets[0].type || type),
2137
2656
  yAxisDiffTypeType = (datasetsDiffType[0].type || type),
2138
2657
  yAxisTypeLabel = this.chart.session.text('ui.' + yAxisType),
2139
2658
  yAxisDiffTypeTypeLabel = this.chart.session.text('ui.' + yAxisDiffTypeType),
2140
- yAxisScaleLabel = scales.scaleLabelByTypeMap ? scales.scaleLabelByTypeMap[yAxisType] : null,
2141
- yAxisDiffTypeScaleLabel = scales.scaleLabelByTypeMap ? scales.scaleLabelByTypeMap[yAxisDiffTypeType] : null;
2659
+ yAxisScaleLabel = options.scaleLabelByTypeMap ? options.scaleLabelByTypeMap[yAxisType] : null,
2660
+ yAxisDiffTypeScaleLabel = options.scaleLabelByTypeMap ? options.scaleLabelByTypeMap[yAxisDiffTypeType] : null;
2142
2661
 
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;
2662
+ yAxis.title.display = true;
2663
+ yAxis.title.text = yAxisScaleLabel ? yAxisScaleLabel + ' (' + yAxisTypeLabel + ')' : yAxisTypeLabel;
2664
+ yAxisDiffType.title.display = true;
2665
+ yAxisDiffType.title.text = yAxisDiffTypeScaleLabel ? yAxisDiffTypeScaleLabel + ' (' + yAxisDiffTypeTypeLabel + ')' : yAxisDiffTypeTypeLabel;
2147
2666
 
2148
2667
  datasets.forEach(dataset => {
2149
- dataset.yAxisID = 'yAxis';
2668
+ dataset.yAxisID = 'y';
2150
2669
  });
2151
2670
  datasetsDiffType.forEach(dataset => {
2152
- dataset.yAxisID = 'yAxisDiffType';
2153
- });
2154
- }
2155
-
2156
- _adjustScaleMaxMin(scale, maxTicks, maxMinValue) {
2157
- scale.ticks = $.extend(true, {}, scale.ticks, {
2158
- maxTicksLimit: Math.ceil(maxTicks / 2),
2159
- stepSize: (this.onlyIntegers ? 1 : undefined)
2671
+ dataset.yAxisID = 'yDiffType';
2160
2672
  });
2161
- if (maxMinValue) {
2162
- scale.ticks.suggestedMax = maxMinValue.maxValue;
2163
- scale.ticks.suggestedMin = maxMinValue.minValue;
2164
- }
2165
2673
  }
2166
2674
 
2167
- _adjustAxes(axes, maxTicks, maxMinValue) {
2168
- if (!axes || !Array.isArray(axes) || !axes.length) {
2675
+ _adjustAxisMaxMin(axis, maxTicks, maxMinValue) {
2676
+ if (!axis) {
2169
2677
  return;
2170
2678
  }
2171
2679
 
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
- }
2680
+ let maxRangeBetweenTwoTicks = 1;
2681
+
2682
+ axis.ticks = $.extend(true, {}, axis.ticks, {
2683
+ maxTicksLimit: maxTicks
2684
+ });
2685
+ if (maxMinValue) {
2686
+ axis.suggestedMax = maxMinValue.maxValue;
2687
+ axis.suggestedMin = maxMinValue.minValue;
2688
+
2689
+ maxRangeBetweenTwoTicks = (maxMinValue.maxValue - maxMinValue.minValue) / (maxTicks - 1);
2181
2690
  }
2691
+ axis.ticks.stepSize = this.onlyIntegers && maxRangeBetweenTwoTicks < 1 ? 1 : undefined;
2182
2692
  }
2183
2693
 
2184
2694
  _remove(afterRemoveFunc) {
2185
- if (this.rendered) {
2695
+ if (this.rendered && !this.removing) {
2696
+ this.removing = true;
2186
2697
  this.$canvas.remove();
2187
2698
  this.$canvas = null;
2188
2699
  this.chartJs.destroy();
2189
2700
  this.chartJs = null;
2190
2701
  }
2191
2702
  super._remove(afterRemoveFunc);
2703
+ this.removing = false;
2192
2704
  }
2193
2705
  }