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