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