blob: bc71d93324726b9b5a91361f35e4d93b4edb35db [file] [log] [blame]
/*
* Copyright (c) 2010-2020 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
*/
import {AbstractChartRenderer, Chart} from '../index';
import ChartJs from 'chart.js';
import {arrays, colorSchemes, Event, objects, strings, styles} from '@eclipse-scout/core';
// noinspection ES6UnusedImports
import chartjs_plugin_datalabels from 'chartjs-plugin-datalabels';
// noinspection ES6UnusedImports
import ChartJsTooltipDelay from './ChartJsTooltipDelay';
/**
* @typedef ChartJs
* @property {object} [defaults]
* @property {object} [defaults.global]
* @property {object} [defaults.global.legend]
* @property {object} [defaults.global.legend.labels]
* @property {object} [defaults.global.elements]
* @property {object} [defaults.global.elements.line]
* @property {object} [defaults.global.elements.point]
* @property {object} [defaults.global.elements.arc]
* @property {object} [defaults.global.elements.rectangle]
* @property {object} [defaults.global.tooltips]
*/
ChartJs.defaults.global.maintainAspectRatio = false;
ChartJs.defaults.global.legend.labels.usePointStyle = true;
ChartJs.defaults.global.legend.labels.boxWidth = 7;
ChartJs.defaults.global.elements.line.tension = 0;
ChartJs.defaults.global.elements.line.fill = false;
ChartJs.defaults.global.elements.line.borderWidth = 2;
ChartJs.defaults.global.elements.point.radius = 0;
ChartJs.defaults.global.elements.point.hitRadius = 10;
ChartJs.defaults.global.elements.point.hoverRadius = 5;
ChartJs.defaults.global.elements.point.hoverBorderWidth = 2;
ChartJs.defaults.global.elements.arc.borderWidth = 1;
ChartJs.defaults.global.elements.rectangle.borderWidth = 1;
ChartJs.defaults.global.elements.rectangle.borderSkipped = '';
ChartJs.defaults.horizontalBar.elements.rectangle.borderSkipped = '';
ChartJs.defaults.global.tooltips.borderWidth = 1;
ChartJs.defaults.global.tooltips.cornerRadius = 4;
ChartJs.defaults.global.tooltips.xPadding = 8;
ChartJs.defaults.global.tooltips.yPadding = 8;
ChartJs.defaults.global.tooltips.titleSpacing = 4;
ChartJs.defaults.global.tooltips.titleMarginBottom = 8;
ChartJs.defaults.global.tooltips.bodySpacing = 4;
let chartJsGlobalsInitialized = false;
/**
* @typedef Dataset
* @property {array|string} [pointBackgroundColor]
* @property {array|string} [pointHoverBackgroundColor]
* @property {array|number} [pointRadius]
*
* @property {array|string} [uncheckedBackgroundColor]
* @property {array|string} [uncheckedHoverBackgroundColor]
* @property {array|string} [uncheckedPointBackgroundColor]
* @property {array|string} [uncheckedPointHoverBackgroundColor]
* @property {array|number} [uncheckedPointRadius]
*
* @property {array|string} [checkedBackgroundColor]
* @property {array|string} [checkedHoverBackgroundColor]
* @property {array|string} [checkedPointBackgroundColor]
* @property {array|string} [checkedPointHoverBackgroundColor]
* @property {array|number} [checkedPointRadius]
*
* @property {array|string} [legendColor]
*/
export default class ChartJsRenderer extends AbstractChartRenderer {
static ARROW_LEFT_RIGHT = '\u2194';
static ARROW_UP_DOWN = '\u2195';
constructor(chart) {
super(chart);
this.chartJs = null;
this.minSpaceBetweenYTicks = 35;
this.minSpaceBetweenXTicks = 150;
this.maxXAxesTicksHeigth = 75;
this.numSupportedColors = 6;
this.colorSchemeCssClass = '';
this.minRadialChartDatalabelSpace = 25;
this._tooltipTitle = this._formatTooltipTitle.bind(this);
this._tooltipLabel = this._formatTooltipLabel.bind(this);
this._tooltipLabelColor = this._computeTooltipLabelColor.bind(this);
this._labelFormatter = this._formatLabel.bind(this);
this._xLabelFormatter = this._formatXLabel.bind(this);
this._yLabelFormatter = this._formatYLabel.bind(this);
this._xAxisFitter = this._fitXAxis.bind(this);
this._yAxisFitter = this._fitYAxis.bind(this);
this._radialChartDatalabelsDisplayHandler = this._displayDatalabelsOnRadialChart.bind(this);
this._radialChartDatalabelsFormatter = this._formatDatalabelsOnRadialChart.bind(this);
this._datalabelsFormatter = this._formatDatalabels.bind(this);
this._datalabelBackgroundColorHandler = this._getBackgroundColorOfDataset.bind(this);
this._legendLabelGenerator = this._generateLegendLabels.bind(this);
this.resetDatasetAfterHover = false;
this._clickHandler = this._onClick.bind(this);
this._hoverHandler = this._onHover.bind(this);
this._pointerHoverHandler = this._onHoverPointer.bind(this);
this.legendHoverDatasets = [];
this._legendClickHandler = this._onLegendClick.bind(this);
this._legendHoverHandler = this._onLegendHover.bind(this);
this._legendPointerHoverHandler = this._onLegendHoverPointer.bind(this);
this._legendLeaveHandler = this._onLegendLeave.bind(this);
this._legendPointerLeaveHandler = this._onLegendLeavePointer.bind(this);
this._resizeHandler = this._onResize.bind(this);
}
_validateChartData() {
let chartDataValid = true;
let chartData = this.chart && this.chart.data;
if (!chartData || !chartData.chartValueGroups || chartData.chartValueGroups.length === 0) {
chartDataValid = false;
}
if (chartDataValid && scout.isOneOf(this.chart.config.type, Chart.Type.POLAR_AREA, Chart.Type.RADAR)) {
// check lengths
let i, length = 0;
for (i = 0; i < chartData.chartValueGroups.length; i++) {
let chartValueGroup = chartData.chartValueGroups[i];
if (!chartValueGroup.values) {
chartDataValid = false;
}
// Length of all "values" arrays have to be equal
if (i === 0) {
length = chartValueGroup.values.length;
} else {
if (chartValueGroup.values.length !== length) {
chartDataValid = false;
}
}
}
for (i = 0; i < chartData.axes.length; i++) {
if (chartData.axes[i].length !== length) {
chartDataValid = false;
}
}
}
if (chartDataValid) {
return true;
}
let chartConfigDataValid = true;
let config = this.chart && this.chart.config;
if (!config || !config.data || !config.data.datasets || config.data.datasets.length === 0) {
chartConfigDataValid = false;
}
if (chartConfigDataValid && scout.isOneOf(this.chart.config.type, Chart.Type.POLAR_AREA, Chart.Type.RADAR)) {
// check lengths
let i, length = 0;
for (i = 0; i < config.data.datasets.length; i++) {
let dataset = config.data.datasets[i];
if (!dataset.data) {
chartConfigDataValid = false;
}
// Length of all "data" arrays have to be equal
if (i === 0) {
length = dataset.data.length;
} else {
if (dataset.data.length !== length) {
chartConfigDataValid = false;
}
}
}
}
return chartConfigDataValid;
}
_render() {
if (!this.$canvas) {
this.$canvas = this.chart.$container.appendElement('<canvas>');
}
this.firstOpaqueBackgroundColor = styles.getFirstOpaqueBackgroundColor(this.$canvas);
if (!chartJsGlobalsInitialized) {
ChartJs.defaults.global.defaultFontFamily = this.$canvas.css('font-family');
chartJsGlobalsInitialized = true;
}
/**
* @property {number} options.bubble.sizeOfLargestBubble
* @property {object} options.numberFormatter
* @property {number} options.tooltips.titleFontFamily
* @property {object} options.scales.scaleLabelByTypeMap
* @property {object} options.scales.xLabelMap
* @property {object} options.scales.yLabelMap
*/
let config = $.extend(true, {}, this.chart.config);
this._adjustConfig(config);
this._renderChart(config, true);
}
_renderChart(config, animated) {
if (this.chartJs) {
this.chartJs.destroy();
}
config = $.extend(true, {}, config, {
options: {
animation: {}
}
}, config);
config.options.animation.duration = animated ? this.animationDuration : 0;
/**
* @type {Chart}
* @property {object} config
* @property {object} chartArea
*/
this.chartJs = new ChartJs(this.$canvas[0].getContext('2d'), config);
this._adjustSize(this.chartJs.config, this.chartJs.chartArea);
this.chartJs.update();
}
_updateChart(animated) {
let config = this.chartJs.config;
this._adjustColors(config);
this._renderChart(config, animated);
}
_renderCheckedItems() {
if (this.chartJs && this._checkItems(this.chartJs.config)) {
this.chartJs.update();
}
}
_checkItems(config) {
if (!config || !config.data) {
return false;
}
let transferArrayValues = (target, source, indices) => {
if (Array.isArray(target) && Array.isArray(source)) {
let changed = 0;
arrays.ensure(indices)
.filter(index => !isNaN(index) && index < Math.min(target.length, source.length))
.forEach(index => {
if (target[index] !== source[index]) {
target[index] = source[index];
changed++;
}
});
return changed;
}
return 0;
};
let changed = 0;
config.data.datasets.forEach((dataset, datasetIndex) => {
let checkedIndices = this.chart.checkedItems.filter(item => item.datasetIndex === datasetIndex)
.map(item => item.dataIndex),
uncheckedIndices = arrays.init(dataset.data.length).map((elem, idx) => idx);
arrays.removeAll(uncheckedIndices, checkedIndices);
changed = changed +
// check
transferArrayValues(dataset.backgroundColor, dataset.checkedBackgroundColor, checkedIndices) +
transferArrayValues(dataset.hoverBackgroundColor, dataset.checkedHoverBackgroundColor, checkedIndices) +
transferArrayValues(dataset.pointBackgroundColor, dataset.checkedPointBackgroundColor, checkedIndices) +
transferArrayValues(dataset.pointHoverBackgroundColor, dataset.checkedPointHoverBackgroundColor, checkedIndices) +
transferArrayValues(dataset.pointRadius, dataset.checkedPointRadius, checkedIndices) +
// uncheck
transferArrayValues(dataset.backgroundColor, dataset.uncheckedBackgroundColor, uncheckedIndices) +
transferArrayValues(dataset.hoverBackgroundColor, dataset.uncheckedHoverBackgroundColor, uncheckedIndices) +
transferArrayValues(dataset.pointBackgroundColor, dataset.uncheckedPointBackgroundColor, uncheckedIndices) +
transferArrayValues(dataset.pointHoverBackgroundColor, dataset.uncheckedPointHoverBackgroundColor, uncheckedIndices) +
transferArrayValues(dataset.pointRadius, dataset.uncheckedPointRadius, uncheckedIndices);
});
return 0 < changed;
}
_adjustConfig(config) {
if (!config || !config.type) {
return;
}
this._adjustType(config);
if (this.chart.data) {
this._computeDatasets(this.chart.data, config);
}
this._adjustData(config);
this._adjustTooltip(config);
this._adjustGrid(config);
this._adjustPlugins(config);
this._adjustColors(config);
this._adjustClickHandler(config);
this._adjustResizeHandler(config);
}
_adjustType(config) {
if (config.type === Chart.Type.COMBO_BAR_LINE) {
config.type = Chart.Type.BAR;
let scaleLabelByTypeMap = ((config.options || {}).scales || {}).scaleLabelByTypeMap;
if (scaleLabelByTypeMap) {
scaleLabelByTypeMap[Chart.Type.BAR] = scaleLabelByTypeMap[Chart.Type.COMBO_BAR_LINE];
}
}
}
_computeDatasets(chartData, config) {
let labels = [],
datasets = [];
let setLabelMap = (identifier, labelMap) => {
if (!$.isEmptyObject(labelMap)) {
config.options = $.extend(true, {}, {
scales: {}
}, config.options);
config.options.scales[identifier] = labelMap;
}
};
(chartData.axes[0] || []).forEach(elem => labels.push(elem.label));
setLabelMap(config.type === Chart.Type.BAR_HORIZONTAL ? 'yLabelMap' : 'xLabelMap', this._computeLabelMap(chartData.axes[0]));
setLabelMap(config.type === Chart.Type.BAR_HORIZONTAL ? 'xLabelMap' : 'yLabelMap', this._computeLabelMap(chartData.axes[1]));
chartData.chartValueGroups.forEach(elem => datasets.push({
type: elem.type,
label: elem.groupName,
data: $.extend(true, [], elem.values)
}));
/**
* @type {object}
* @property {Dataset[]} datasets
*/
config.data = {
labels: labels,
datasets: datasets
};
}
_computeLabelMap(axis) {
let labelMap = {};
(axis || []).forEach((elem, idx) => {
labelMap[idx] = elem.label;
});
return labelMap;
}
_adjustData(config) {
if (!config || !config.data || !config.type) {
return;
}
this._adjustBarBorderWidth(config);
this._adjustMaxSegments(config);
this._adjustBubbleRadii(config);
}
_adjustBarBorderWidth(config) {
if (!config || !config.data || !config.type || !scout.isOneOf(config.type, Chart.Type.BAR, Chart.Type.BAR_HORIZONTAL)) {
return;
}
config.data.datasets.forEach(dataset => {
if ((dataset.type || Chart.Type.BAR) === Chart.Type.BAR) {
dataset.hoverBorderWidth = dataset.hoverBorderWidth || 2;
}
});
}
_adjustMaxSegments(config) {
if (!config || !config.data || !config.type || !scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA, Chart.Type.RADAR)) {
return;
}
let maxSegments = config.options.maxSegments;
if (!(maxSegments && config.data.datasets.length && maxSegments < config.data.datasets[0].data.length)) {
return;
}
config.data.datasets.forEach(elem => {
let newData = elem.data.slice(0, maxSegments);
newData[maxSegments - 1] = elem.data.slice(maxSegments - 1, elem.data.length).reduce((x, y) => {
return x + y;
}, 0);
elem.data = newData;
});
let newLabels = config.data.labels.slice(0, maxSegments);
newLabels[maxSegments - 1] = this.chart.session.text('ui.OtherValues');
config.data.labels = newLabels;
config.data.maxSegmentsExceeded = true;
}
_isMaxSegmentsExceeded(config, index) {
if (!scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA, Chart.Type.RADAR)) {
return false;
}
if (config.options.otherSegmentClickable) {
return false;
}
if (!config.data.maxSegmentsExceeded || !config.options.maxSegments) {
return false;
}
return config.options.maxSegments - 1 <= index;
}
_adjustBubbleRadii(config) {
if (!config || !config.data || !config.type || config.type !== Chart.Type.BUBBLE) {
return;
}
config.data.datasets.forEach(dataset => dataset.data.forEach(data => {
if (!isNaN(data.r)) {
data.z = Math.pow(data.r, 2);
} else if (!isNaN(data.z)) {
data.r = Math.sqrt(data.z);
}
}));
}
_adjustTooltip(config) {
if (!config) {
return;
}
config.options = $.extend(true, {}, {
hover: {
mode: 'nearest'
},
tooltips: {
mode: 'nearest',
callbacks: {
title: this._tooltipTitle,
label: this._tooltipLabel,
labelColor: this._tooltipLabelColor
}
}
}, config.options);
}
_formatTooltipTitle(tooltipItems, data) {
let config = this.chartJs.config,
ctx = this.chartJs.ctx,
tooltipItem = tooltipItems[0],
title,
defaultGlobal = ChartJs.defaults.global,
defaultTypeTooltips = {},
defaultGlobalTooltips = defaultGlobal.tooltips;
if (ChartJs.defaults[config.type]) {
defaultTypeTooltips = $.extend(true, {}, defaultTypeTooltips, ChartJs.defaults[config.type].tooltips);
}
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)) {
let label = data.labels[tooltipItem.index];
title = config.options.reformatLabels ? this._formatLabel(label) : label;
} else if (config.type === Chart.Type.BUBBLE) {
let xAxis = config.options.scales.xAxes[0],
yAxis = config.options.scales.yAxes[0],
xAxisLabel = xAxis.scaleLabel.labelString,
yAxisLabel = yAxis.scaleLabel.labelString;
xAxisLabel = xAxisLabel ? (xAxisLabel + ':') : ChartJsRenderer.ARROW_LEFT_RIGHT;
yAxisLabel = yAxisLabel ? (yAxisLabel + ':') : ' ' + ChartJsRenderer.ARROW_UP_DOWN + ' ';
title = [xAxisLabel + ' ' + xAxis.ticks.callback(tooltipItem.xLabel),
yAxisLabel + ' ' + yAxis.ticks.callback(tooltipItem.yLabel)];
} else {
let defaultTypeTooltipTitle;
if (defaultTypeTooltips.callbacks) {
defaultTypeTooltipTitle = defaultTypeTooltips.callbacks.title;
}
let defaultTooltipTitle = defaultTypeTooltipTitle || defaultGlobalTooltips.callbacks.title;
title = defaultTooltipTitle.call(this.chartJs, tooltipItems, data);
}
let horizontalSpace = this.$canvas.cssWidth() - (2 * config.options.tooltips.xPadding),
measureText = ctx.measureText.bind(ctx),
oldFont = ctx.font,
titleFontStyle = config.options.tooltips.titleFontStyle || defaultTypeTooltips.titleFontStyle || defaultGlobalTooltips.titleFontStyle || defaultGlobal.defaultFontStyle,
titleFontSize = config.options.tooltips.titleFontSize || defaultTypeTooltips.titleFontSize || defaultGlobalTooltips.titleFontSize || defaultGlobal.defaultFontSize,
titleFontFamily = config.options.tooltips.titleFontFamily || defaultTypeTooltips.titleFontFamily || defaultGlobalTooltips.titleFontFamily || defaultGlobal.defaultFontFamily,
result = [];
ctx.font = titleFontStyle + ' ' + titleFontSize + 'px ' + titleFontFamily;
arrays.ensure(title).forEach(titleLine => result.push(strings.truncateText(titleLine, horizontalSpace, measureText)));
ctx.font = oldFont;
return result;
}
_formatTooltipLabel(tooltipItem, data) {
let config = this.chartJs.config,
ctx = this.chartJs.ctx,
datasets = data ? data.datasets : null,
dataset = datasets ? datasets[tooltipItem.datasetIndex] : null,
label, value,
defaultGlobal = ChartJs.defaults.global,
defaultTypeTooltips = {},
defaultGlobalTooltips = defaultGlobal.tooltips;
if (ChartJs.defaults[config.type]) {
defaultTypeTooltips = $.extend(true, {}, defaultTypeTooltips, ChartJs.defaults[config.type].tooltips);
}
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)) {
label = dataset.label;
value = this._formatLabel(dataset.data[tooltipItem.index]);
} else if (config.type === Chart.Type.BUBBLE) {
label = dataset.label;
value = this._formatLabel(dataset.data[tooltipItem.index].z);
} else {
let defaultTypeTooltipLabel;
if (defaultTypeTooltips.callbacks) {
defaultTypeTooltipLabel = defaultTypeTooltips.callbacks.label;
}
let defaultTooltipLabel = defaultTypeTooltipLabel || defaultGlobalTooltips.callbacks.label;
label = defaultTooltipLabel.call(this.chartJs, tooltipItem, data);
}
label = ' ' + label;
value = value ? ' ' + value : '';
let colorRectSize = config.options.tooltips.displayColors ? config.options.tooltips.bodyFontSize || defaultTypeTooltips.bodyFontSize || defaultGlobalTooltips.bodyFontSize || defaultGlobal.defaultFontSize : 0,
horizontalSpace = this.$canvas.cssWidth() - (2 * config.options.tooltips.xPadding) - colorRectSize,
measureText = ctx.measureText.bind(ctx),
result = label + (value ? ':' + value : '');
if (measureText(result).width > horizontalSpace) {
if (measureText(value).width > horizontalSpace / 2) {
return strings.truncateText(value, horizontalSpace, measureText);
}
return strings.truncateText(label, horizontalSpace - measureText(value ? ':' + value : '').width, measureText) + (value ? ':' + value : '');
}
return result;
}
_computeTooltipLabelColor(tooltipItem, chart) {
let config = chart.config,
tooltips = config.options ? config.options.tooltips : null,
tooltipBackgroundColor = tooltips ? tooltips.backgroundColor : null,
datasets = chart.data ? chart.data.datasets : null,
dataset = datasets ? datasets[tooltipItem.datasetIndex] : null,
backgroundColor;
if (scout.isOneOf((dataset.type || config.type), Chart.Type.LINE, Chart.Type.BAR, Chart.Type.BAR_HORIZONTAL, Chart.Type.RADAR, Chart.Type.BUBBLE)) {
backgroundColor = dataset.legendColor || dataset.borderColor;
}
if (scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
let legendColor = Array.isArray(dataset.legendColor) ? dataset.legendColor[tooltipItem.index] : dataset.legendColor,
datasetBackgroundColor = Array.isArray(dataset.backgroundColor) ? dataset.backgroundColor[tooltipItem.index] : dataset.backgroundColor;
backgroundColor = legendColor || this._adjustColorOpacity(datasetBackgroundColor, 1);
}
if (!backgroundColor || typeof backgroundColor === 'function') {
let defaultTypeTooltipLabelColor;
if (ChartJs.defaults[config.type] && ChartJs.defaults[config.type].callbacks) {
defaultTypeTooltipLabelColor = ChartJs.defaults[config.type].callbacks.labelColor;
}
let defaultTooltipLabelColor = defaultTypeTooltipLabelColor || ChartJs.defaults.global.tooltips.callbacks.labelColor;
backgroundColor = defaultTooltipLabelColor.call(chart, tooltipItem, chart).backgroundColor;
}
return {
borderColor: tooltipBackgroundColor,
backgroundColor: backgroundColor
};
}
_adjustGrid(config) {
if (!config) {
return;
}
config.options = $.extend(true, {}, config.options);
this._adjustScale(config);
this._adjustScales(config);
}
_adjustScale(config) {
if (!config || !config.type || !config.options) {
return;
}
if (scout.isOneOf(config.type, Chart.Type.POLAR_AREA, Chart.Type.RADAR)) {
config.options = $.extend(true, {}, {
scale: {}
}, config.options);
}
let options = config.options;
if (options.scale) {
options.scale = $.extend(true, {}, {
angleLines: {
display: false
}, gridLines: {
borderDash: [2, 4]
},
ticks: {
beginAtZero: true,
callback: this._labelFormatter
},
pointLabels: {
fontSize: ChartJs.defaults.global.defaultFontSize
}
}, options.scale);
}
}
_adjustScales(config) {
if (!config || !config.type || !config.options) {
return;
}
if (scout.isOneOf(config.type, Chart.Type.BAR, Chart.Type.BAR_HORIZONTAL, Chart.Type.LINE, Chart.Type.BUBBLE)) {
config.options = $.extend(true, {}, {
scales: {
xAxes: [{}],
yAxes: [{}]
}
}, config.options);
}
this._adjustXAxes(config);
this._adjustYAxes(config);
}
_adjustXAxes(config) {
if (!config || !config.type || !config.options || !config.options.scales || !config.options.scales.xAxes) {
return;
}
let type = config.type,
xAxes = config.options.scales.xAxes;
for (let i = 0; i < xAxes.length; i++) {
if (scout.isOneOf(type, Chart.Type.BAR_HORIZONTAL, Chart.Type.BUBBLE)) {
xAxes[i] = $.extend(true, {}, {
offset: type === Chart.Type.BUBBLE,
gridLines: {
drawBorder: false,
drawTicks: false,
zeroLineBorderDash: [2, 4],
borderDash: [2, 4]
},
ticks: {
padding: 5,
beginAtZero: type === Chart.Type.BAR_HORIZONTAL
}
}, xAxes[i]);
} else {
xAxes[i] = $.extend(true, {}, {
offset: true,
gridLines: {
display: false
}
}, xAxes[i]);
}
if (scout.isOneOf(type, Chart.Type.BAR_HORIZONTAL, Chart.Type.BUBBLE) || config.options.reformatLabels) {
xAxes[i] = $.extend(true, {}, {
ticks: {
callback: this._xLabelFormatter
}
}, xAxes[i]);
}
xAxes[i].afterCalculateTickRotation = this._xAxisFitter;
}
}
_adjustYAxes(config) {
if (!config || !config.type || !config.options || !config.options.scales || !config.options.scales.yAxes) {
return;
}
let type = config.type,
yAxes = config.options.scales.yAxes;
for (let i = 0; i < yAxes.length; i++) {
if (type === Chart.Type.BAR_HORIZONTAL) {
yAxes[i] = $.extend(true, {}, {
gridLines: {
display: false
}
}, yAxes[i]);
} else {
yAxes[i] = $.extend(true, {}, {
gridLines: {
drawBorder: false,
drawTicks: false,
zeroLineBorderDash: [2, 4],
borderDash: [2, 4]
},
ticks: {
padding: 5,
beginAtZero: type !== Chart.Type.BUBBLE
}
}, yAxes[i]);
}
if (type !== Chart.Type.BAR_HORIZONTAL || config.options.reformatLabels) {
yAxes[i] = $.extend(true, {}, {
ticks: {
callback: this._yLabelFormatter
}
}, yAxes[i]);
}
yAxes[i].afterFit = this._yAxisFitter;
}
}
_adjustPlugins(config) {
this._adjustPluginsDatalabels(config);
}
_adjustPluginsDatalabels(config) {
if (!config || !config.type || !config.options || !config.options.plugins || !config.options.plugins.datalabels || !config.options.plugins.datalabels.display) {
return;
}
let plugins = config.options.plugins;
if (scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT)) {
plugins.datalabels = $.extend(true, {}, {
formatter: this._radialChartDatalabelsFormatter
}, plugins.datalabels);
plugins.datalabels.display = this._radialChartDatalabelsDisplayHandler;
} else if (scout.isOneOf(config.type, Chart.Type.BAR, Chart.Type.BAR_HORIZONTAL, Chart.Type.LINE, Chart.Type.POLAR_AREA, Chart.Type.RADAR, Chart.Type.BUBBLE)) {
plugins.datalabels = $.extend(true, {}, {
backgroundColor: this._datalabelBackgroundColorHandler,
borderRadius: 4
}, plugins.datalabels);
plugins.datalabels.display = 'auto';
}
if (config.options.reformatLabels) {
let handleFormatter = formatter => {
return (value, context) => {
let label = formatter.call(context.chart, value, context);
return this._formatLabel(label);
};
};
if (config.data) {
let datasets = config.data.datasets;
datasets.forEach(dataset => {
if (dataset.datalabels && dataset.datalabels.formatter) {
dataset.datalabels.formatter = handleFormatter(dataset.datalabels.formatter);
}
});
}
if (plugins.datalabels.formatter) {
plugins.datalabels.formatter = handleFormatter(plugins.datalabels.formatter);
}
}
plugins.datalabels = $.extend(true, {}, {
formatter: this._datalabelsFormatter
}, plugins.datalabels);
}
_formatLabel(label) {
return this._formatLabelMap(label, null, this._getNumberFormatter());
}
_getNumberFormatter() {
if (this.chartJs && this.chartJs.config && this.chartJs.config.options) {
return this.chartJs.config.options.numberFormatter;
}
}
_formatXLabel(label) {
return this._formatLabelMap(label, this._getXLabelMap(), this._getNumberFormatter());
}
_formatYLabel(label) {
return this._formatLabelMap(label, this._getYLabelMap(), this._getNumberFormatter());
}
_getXLabelMap() {
return this._getLabelMap('xLabelMap');
}
_getYLabelMap() {
return this._getLabelMap('yLabelMap');
}
_getLabelMap(identifier) {
if (this.chartJs && this.chartJs.config && this.chartJs.config.options && this.chartJs.config.options.scales) {
return this.chartJs.config.options.scales[identifier];
}
}
_formatLabelMap(label, labelMap, numberFormatter) {
if (labelMap) {
return labelMap[label];
}
if (isNaN(label)) {
return label;
}
if (numberFormatter) {
return numberFormatter(label, this._formatNumberLabel.bind(this));
}
return this._formatNumberLabel(label);
}
_formatNumberLabel(label) {
if (isNaN(label)) {
return label;
}
let abs = Math.abs(label);
let abbreviation = '';
if (abs >= 1000000) {
abs = abs / 1000000;
abbreviation = ' ' + this.chart.session.text('ui.Mio');
let abbreviations = [
this.chart.session.text('ui.Mrd'),
this.chart.session.text('ui.Bio'),
this.chart.session.text('ui.Brd'),
this.chart.session.text('ui.Tri'),
this.chart.session.text('ui.Trd')];
for (let i = 0; i < abbreviations.length; i++) {
if (abs >= 1000000) {
abs = abs / 1000;
abbreviation = ' ' + abbreviations[i];
} else {
break;
}
}
}
return this.session.locale.decimalFormat.format(Math.sign(label) * abs) + abbreviation;
}
_fitXAxis(xAxis) {
if (!xAxis || xAxis.labelRotation === 0) {
return;
}
let maxHeight = this.maxXAxesTicksHeigth,
defaultGlobal = ChartJs.defaults.global,
defaultTicks = ChartJs.defaults.scale.ticks,
fontSize,
maxRotation;
if (this.chartJs) {
let chartArea = this.chartJs.chartArea,
chartAreaHeight = Math.abs(chartArea.top - chartArea.bottom);
maxHeight = Math.min(maxHeight, chartAreaHeight / 3);
}
if (xAxis.options && xAxis.options.ticks) {
maxRotation = xAxis.options.ticks.maxRotation;
fontSize = xAxis.options.ticks.fontSize;
}
maxRotation = maxRotation || defaultTicks.maxRotation;
fontSize = fontSize || defaultTicks.fontSize || defaultGlobal.defaultFontSize;
// if the chart is very narrow, chart.js sometimes calculates with a negative width of the canvas
// this causes NaN for labelRotation and height
if (isNaN(xAxis.labelRotation)) {
xAxis.labelRotation = maxRotation;
}
xAxis.height = isNaN(xAxis.height) ? maxHeight : Math.min(xAxis.height, maxHeight);
// the rotation (degrees), needs to be transformed to radians ((labelRotation / 180) * pi)
let labelRotation = xAxis.labelRotation,
// the label is a rectangle (labelWidth x fontSize) which is rotated
// => height = sin(labelRotation) * labelLength + sin(90° - labelRotation) * fontSize
// <=> labelLength = (height - sin(90° - labelRotation) * fontSize) / sin(labelRotation)
maxLabelLength = (maxHeight - (fontSize * Math.sin(((90 - labelRotation) / 180) * Math.PI))) / Math.sin((labelRotation / 180) * Math.PI);
if (xAxis.longestLabelWidth > maxLabelLength) {
let measureText = xAxis.ctx.measureText.bind(xAxis.ctx);
xAxis._ticks.forEach(tick => {
tick.label = strings.truncateText(tick.label, maxLabelLength, measureText);
});
// reset label sizes, chart.js will recalculate them using the new truncated labels
xAxis._labelSizes = null;
}
}
_fitYAxis(yAxis) {
if (!yAxis) {
return;
}
let padding = 0,
tickMarkLength = 0;
if (yAxis.options && yAxis.options.ticks) {
padding = yAxis.options.ticks.padding || 0;
}
if (yAxis.options && yAxis.options.gridLines) {
tickMarkLength = yAxis.options.gridLines.tickMarkLength || 0;
}
if (yAxis.longestLabelWidth > yAxis.maxWidth - padding) {
let horizontalSpace = yAxis.maxWidth - padding - tickMarkLength,
measureText = yAxis.ctx.measureText.bind(yAxis.ctx);
yAxis._ticks.forEach(tick => {
tick.label = strings.truncateText(tick.label, horizontalSpace, measureText);
});
}
}
_displayDatalabelsOnRadialChart(context) {
let data = context.chart.getDatasetMeta(context.datasetIndex).data[context.dataIndex],
model = data._model,
// Compute the biggest circle that fits inside sector/arc with center in the middle between inner and outer radius.
// 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.
// 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.
// circle C1:
midRadius = (model.outerRadius + model.innerRadius) / 2,
// 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.
angle = Math.min((model.endAngle - model.startAngle), Math.PI) / 2,
radius1 = Math.abs(Math.sin(angle)) * midRadius,
diameter1 = radius1 * 2,
// circle C2:
diameter2 = model.outerRadius - model.innerRadius;
return Math.min(diameter1, diameter2) > this.minRadialChartDatalabelSpace;
}
_formatDatalabelsOnRadialChart(value, context) {
let sum = this._computeSumOfVisibleElements(context),
dataset = context.dataset,
roundingError = 0,
roundedResults = [];
for (let i = 0; i < context.dataIndex + 1; i++) {
let result = dataset.data[i] / sum * 100 - roundingError,
roundedResult = Math.round(result);
roundingError = roundedResult - result;
roundedResults.push(roundedResult + '%');
}
return roundedResults[context.dataIndex];
}
_computeSumOfVisibleElements(context) {
let dataset = context.dataset,
meta = context.chart.getDatasetMeta(context.datasetIndex),
sum = 0;
for (let i = 0; i < dataset.data.length; i++) {
if (meta.data[i] && !meta.data[i].hidden) {
sum += dataset.data[i];
}
}
return sum;
}
_formatDatalabels(value, context) {
if (context.chart.config.type === Chart.Type.BUBBLE) {
return this._formatLabel(value.z);
}
return this._formatLabel(value);
}
_getBackgroundColorOfDataset(context) {
return context.dataset.backgroundColor;
}
_adjustColors(config) {
this._adjustColorSchemeCssClass(config);
this._adjustDatasetColors(config);
this._adjustLegendColors(config);
this._adjustTooltipColors(config);
this._adjustScaleColors(config);
this._adjustScalesColors(config);
this._adjustPluginColors(config);
}
_adjustColorSchemeCssClass(config) {
if (!config || !config.options) {
return;
}
this.colorSchemeCssClass = colorSchemes.getCssClasses(config.options.colorScheme).join(' ');
}
_adjustDatasetColors(config) {
if (!config || !config.data || !config.type) {
return;
}
let data = config.data,
type = config.type,
autoColor = config.options && config.options.autoColor,
checkable = config.options && config.options.checkable,
multipleColorsPerDataset = autoColor && scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA),
colors = {
backgroundColors: [],
borderColors: [],
hoverBackgroundColors: [],
hoverBorderColors: [],
checkedBackgroundColors: [],
checkedHoverBackgroundColors: [],
legendColors: [],
pointHoverColor: this._computePointHoverColor(type)
};
colors = $.extend(true, colors, this._computeDatasetColors(config, multipleColorsPerDataset));
data.datasets.forEach((elem, idx) => {
let backgroundColor = (multipleColorsPerDataset ? colors.backgroundColors : colors.backgroundColors[idx]),
borderColor = (multipleColorsPerDataset ? colors.borderColors : colors.borderColors[idx]),
hoverBackgroundColor = (multipleColorsPerDataset ? colors.hoverBackgroundColors : colors.hoverBackgroundColors[idx]),
hoverBorderColor = (multipleColorsPerDataset ? colors.hoverBorderColors : colors.hoverBorderColors[idx]),
legendColor = (multipleColorsPerDataset ? colors.legendColors : colors.legendColors[idx]),
pointHoverBackgroundColor = colors.pointHoverColor;
let setProperty = (identifier, value) => {
if (value && value.length) {
elem[identifier] = Array.isArray(value) ? [...value] : value;
}
};
setProperty('backgroundColor', backgroundColor);
setProperty('borderColor', borderColor);
setProperty('hoverBackgroundColor', hoverBackgroundColor);
setProperty('hoverBorderColor', hoverBorderColor);
setProperty('legendColor', legendColor);
setProperty('pointHoverBackgroundColor', pointHoverBackgroundColor);
if (checkable) {
let datasetLength = elem.data.length;
if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.BAR_HORIZONTAL, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA, Chart.Type.BUBBLE) || (type === Chart.Type.BAR && (elem.type || Chart.Type.BAR) === Chart.Type.BAR)) {
let uncheckedBackgroundColor = (multipleColorsPerDataset ? colors.backgroundColors : arrays.init(datasetLength, colors.backgroundColors[idx])),
uncheckedHoverBackgroundColor = (multipleColorsPerDataset ? colors.hoverBackgroundColors : arrays.init(datasetLength, colors.hoverBackgroundColors[idx])),
checkedBackgroundColor = (multipleColorsPerDataset ? colors.checkedBackgroundColors : arrays.init(datasetLength, colors.checkedBackgroundColors[idx])),
checkedHoverBackgroundColor = (multipleColorsPerDataset ? colors.checkedHoverBackgroundColors : arrays.init(datasetLength, colors.checkedHoverBackgroundColors[idx]));
setProperty('uncheckedBackgroundColor', uncheckedBackgroundColor);
setProperty('uncheckedHoverBackgroundColor', uncheckedHoverBackgroundColor);
setProperty('checkedBackgroundColor', checkedBackgroundColor);
setProperty('checkedHoverBackgroundColor', checkedHoverBackgroundColor);
setProperty('backgroundColor', elem.uncheckedBackgroundColor);
setProperty('hoverBackgroundColor', elem.uncheckedHoverBackgroundColor);
} else if (scout.isOneOf(type, Chart.Type.LINE, Chart.Type.RADAR) || (type === Chart.Type.BAR && elem.type === Chart.Type.LINE)) {
let uncheckedPointBackgroundColor = arrays.init(datasetLength, pointHoverBackgroundColor),
uncheckedPointHoverBackgroundColor = arrays.init(datasetLength, pointHoverBackgroundColor),
checkedPointBackgroundColor = arrays.init(datasetLength, borderColor),
checkedPointHoverBackgroundColor = arrays.init(datasetLength, hoverBorderColor || borderColor);
setProperty('uncheckedPointBackgroundColor', uncheckedPointBackgroundColor);
setProperty('uncheckedPointHoverBackgroundColor', uncheckedPointHoverBackgroundColor);
setProperty('checkedPointBackgroundColor', checkedPointBackgroundColor);
setProperty('checkedPointHoverBackgroundColor', checkedPointHoverBackgroundColor);
setProperty('pointBackgroundColor', elem.uncheckedPointBackgroundColor);
setProperty('pointHoverBackgroundColor', elem.uncheckedPointHoverBackgroundColor);
let uncheckedPointRadius = arrays.init(datasetLength, ((config.options.elements || {}).point || {}).radius || ChartJs.defaults.global.elements.point.radius),
checkedPointRadius = arrays.init(datasetLength, ((config.options.elements || {}).point || {}).hoverRadius || ChartJs.defaults.global.elements.point.hoverRadius);
setProperty('uncheckedPointRadius', uncheckedPointRadius);
setProperty('checkedPointRadius', checkedPointRadius);
setProperty('pointRadius', elem.uncheckedPointRadius);
}
}
});
if (checkable) {
this._checkItems(config);
}
}
_computePointHoverColor(type) {
return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'point hover'], 'fill').fill;
}
_computeDatasetColors(config, multipleColorsPerDataset) {
if (!config || !config.data || !config.type) {
return {};
}
let data = config.data,
type = config.type,
colors = {};
if (config.options && config.options.autoColor) {
colors = this._computeDatasetColorsAutoColor(config, multipleColorsPerDataset);
} else {
colors = this._computeDatasetColorsChartValueGroups(config, multipleColorsPerDataset);
if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
let borderColor = this._computeBorderColor(type, 0);
colors.borderColors = arrays.init(data.datasets.length, borderColor);
colors.hoverBorderColors = colors.borderColors;
}
}
return colors;
}
_computeDatasetColorsAutoColor(config, multipleColorsPerDataset) {
if (!config || !config.data || !config.type || !config.options || !config.options.autoColor) {
return {};
}
let data = config.data,
type = config.type,
checkable = config.options && config.options.checkable,
transparent = config.options && config.options.transparent,
colors = {
backgroundColors: [],
borderColors: [],
hoverBackgroundColors: [],
hoverBorderColors: [],
checkedBackgroundColors: [],
checkedHoverBackgroundColors: [],
legendColors: []
};
let types = [];
if (multipleColorsPerDataset) {
types = arrays.init((data.datasets.length && data.datasets[0].data.length) || 0, type);
} else {
data.datasets.forEach(dataset => types.push(dataset.type || type));
}
types.forEach((type, index) => {
colors.backgroundColors.push(this._computeBackgroundColor(type, index, checkable || transparent));
colors.borderColors.push(this._computeBorderColor(type, index));
colors.hoverBackgroundColors.push(this._computeHoverBackgroundColor(type, index, checkable || transparent));
colors.hoverBorderColors.push(this._computeHoverBorderColor(type, index));
colors.checkedBackgroundColors.push(this._computeCheckedBackgroundColor(type, index, checkable));
colors.checkedHoverBackgroundColors.push(this._computeCheckedHoverBackgroundColor(type, index, checkable));
colors.legendColors.push(this._computeLegendColor(type, index));
});
return colors;
}
_computeBackgroundColor(type, index, checkable) {
return styles.get([this.colorSchemeCssClass, type + '-chart' + (checkable ? ' checkable' : ''), 'elements', 'color' + (index % this.numSupportedColors)], 'fill').fill;
}
_computeBorderColor(type, index) {
let additionalProperties;
if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
additionalProperties = {stroke: this.firstOpaqueBackgroundColor};
}
return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'stroke-color' + (index % this.numSupportedColors)], 'stroke', additionalProperties).stroke;
}
_computeHoverBackgroundColor(type, index, checkable) {
return styles.get([this.colorSchemeCssClass, type + '-chart' + (checkable ? ' checkable' : ''), 'elements', 'color' + (index % this.numSupportedColors) + ' hover'], 'fill').fill;
}
_computeHoverBorderColor(type, index) {
let additionalProperties;
if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
additionalProperties = {stroke: this.firstOpaqueBackgroundColor};
}
return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'stroke-color' + (index % this.numSupportedColors) + ' hover'], 'stroke', additionalProperties).stroke;
}
_computeCheckedBackgroundColor(type, index, checkable) {
return styles.get([this.colorSchemeCssClass, type + '-chart' + (checkable ? ' checkable' : ''), 'elements', 'color' + (index % this.numSupportedColors) + ' checked'], 'fill').fill;
}
_computeCheckedHoverBackgroundColor(type, index, checkable) {
return styles.get([this.colorSchemeCssClass, type + '-chart' + (checkable ? ' checkable' : ''), 'elements', 'color' + (index % this.numSupportedColors) + ' hover checked'], 'fill').fill;
}
_computeLegendColor(type, index) {
return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'color' + (index % this.numSupportedColors) + ' legend'], 'fill').fill;
}
_computeDatasetColorsChartValueGroups(config, multipleColorsPerDataset) {
if (!config || !config.type || !this.chart.data) {
return {};
}
let type = config.type,
checkable = config.options && config.options.checkable,
transparent = config.options && config.options.transparent,
colors = {
backgroundColors: [],
borderColors: [],
hoverBackgroundColors: [],
hoverBorderColors: [],
checkedBackgroundColors: [],
checkedHoverBackgroundColors: [],
legendColors: []
};
this.chart.data.chartValueGroups.forEach(elem => {
let rgbColor = styles.hexToRgb(elem.colorHexValue),
adjustColor = (opacity, darker) => this._adjustColorOpacity(styles.darkerColor(rgbColor, darker), opacity);
let backgroundOpacity = 1,
hoverBackgroundOpacity = 1,
hoverBackgroundDarker = 0.1,
hoverBorderDarker = 0.1,
uncheckedBackgroundOpacity = 0.2,
uncheckedHoverBackgroundOpacity = 0.35,
checkedBackgroundOpacity = 1,
checkedBackgroundDarker = 0,
checkedHoverBackgroundOpacity = 1,
checkedHoverBackgroundDarker = 0.1;
if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT)) {
uncheckedBackgroundOpacity = 0.7;
uncheckedHoverBackgroundOpacity = 0.85;
checkedBackgroundDarker = 0.1;
checkedHoverBackgroundDarker = 0;
} else if (type === Chart.Type.POLAR_AREA) {
backgroundOpacity = 0.7;
hoverBackgroundOpacity = 0.85;
uncheckedBackgroundOpacity = 0.7;
uncheckedHoverBackgroundOpacity = 0.85;
checkedBackgroundDarker = 0.1;
checkedHoverBackgroundDarker = 0;
} else if (scout.isOneOf((elem.type || type), Chart.Type.LINE, Chart.Type.RADAR)) {
backgroundOpacity = 0.2;
hoverBackgroundOpacity = 0.35;
hoverBackgroundDarker = 0;
hoverBorderDarker = 0;
checkedBackgroundOpacity = 0.2;
checkedHoverBackgroundOpacity = 0.35;
checkedHoverBackgroundDarker = 0;
} else if (type === Chart.Type.BUBBLE) {
backgroundOpacity = 0.2;
hoverBackgroundOpacity = 0.35;
hoverBackgroundDarker = 0;
}
colors.backgroundColors.push(adjustColor((checkable || transparent) ? uncheckedBackgroundOpacity : backgroundOpacity, 0));
colors.borderColors.push(adjustColor(1, 0));
colors.hoverBackgroundColors.push(adjustColor((checkable || transparent) ? uncheckedHoverBackgroundOpacity : hoverBackgroundOpacity, (checkable || transparent) ? 0 : hoverBackgroundDarker));
colors.hoverBorderColors.push(adjustColor(1, hoverBorderDarker));
colors.checkedBackgroundColors.push(adjustColor(checkedBackgroundOpacity, checkedBackgroundDarker));
colors.checkedHoverBackgroundColors.push(adjustColor(checkedHoverBackgroundOpacity, checkedHoverBackgroundDarker));
colors.legendColors.push(adjustColor(1, 0));
});
colors.datalabelColor = this._computeDatalabelColor(type);
return colors;
}
_adjustColorOpacity(color, opacity = 1) {
if (!color || typeof color === 'function') {
return color;
}
if (color.indexOf('rgb') === 0) {
return this._adjustRgbColorOpacity(color, opacity);
}
if (color.indexOf('#') === 0) {
return this._adjustHexColorOpacity(color, opacity);
}
return color;
}
_adjustRgbColorOpacity(rgbColor, opacity = 1) {
if (!rgbColor || rgbColor.indexOf('rgb') !== 0) {
return rgbColor;
}
let rgba = styles.rgb(rgbColor);
rgba.alpha = opacity;
return 'rgba(' + rgba.red + ', ' + rgba.green + ', ' + rgba.blue + ', ' + rgba.alpha + ')';
}
_adjustHexColorOpacity(hexColor, opacity = 1) {
if (!hexColor || hexColor.indexOf('#') !== 0 || !(hexColor.length === 4 || hexColor.length === 5 || hexColor.length === 7 || hexColor.length === 9)) {
return hexColor;
}
return this._adjustRgbColorOpacity(styles.hexToRgb(hexColor), opacity);
}
_adjustLegendColors(config) {
if (!config || !config.type || !config.options) {
return;
}
config.options = $.extend(true, {}, config.options, {
legend: {
labels: {
fontColor: this._computeLabelColor(config.type),
generateLabels: this._legendLabelGenerator
}
}
});
}
_computeLabelColor(type) {
return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'label'], 'fill').fill;
}
_generateLegendLabels(chart) {
let config = chart.config,
data = config.data,
measureText = chart.ctx.measureText.bind(chart.ctx),
horizontalSpace;
if (scout.isOneOf(config.options.legend.position, Chart.Position.LEFT, Chart.Position.RIGHT)) {
horizontalSpace = Math.min(250, this.$canvas.cssWidth() / 3);
} else {
horizontalSpace = Math.min(250, this.$canvas.cssWidth() * 2 / 3);
}
let defaultTypeGenerateLabels;
if (ChartJs.defaults[config.type] && ChartJs.defaults[config.type].legend && ChartJs.defaults[config.type].legend.labels) {
defaultTypeGenerateLabels = ChartJs.defaults[config.type].legend.labels.generateLabels;
}
let defaultGenerateLabels = defaultTypeGenerateLabels || ChartJs.defaults.global.legend.labels.generateLabels;
let labels = defaultGenerateLabels.call(chart, chart);
labels.forEach((elem, idx) => {
elem.text = strings.truncateText(elem.text, horizontalSpace, measureText);
let dataset = data.datasets[idx],
fillStyle;
if (dataset && scout.isOneOf((dataset.type || config.type), Chart.Type.LINE, Chart.Type.BAR, Chart.Type.BAR_HORIZONTAL, Chart.Type.RADAR, Chart.Type.BUBBLE)) {
fillStyle = dataset.legendColor || this._adjustColorOpacity(dataset.borderColor, 1);
} else if (data.datasets.length && scout.isOneOf(config.type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
dataset = data.datasets[0];
let legendColor = Array.isArray(dataset.legendColor) ? dataset.legendColor[idx] : dataset.legendColor,
backgroundColor = Array.isArray(dataset.backgroundColor) ? dataset.backgroundColor[idx] : dataset.backgroundColor;
fillStyle = legendColor || this._adjustColorOpacity(backgroundColor, 1);
}
if (typeof fillStyle !== 'function') {
elem.fillStyle = fillStyle;
elem.strokeStyle = fillStyle;
}
});
return labels;
}
_adjustTooltipColors(config) {
if (!config || !config.type || !config.options) {
return;
}
let tooltipBackgroundColor = this._computeTooltipBackgroundColor(config.type),
tooltipBorderColor = this._computeTooltipBorderColor(config.type);
config.options = $.extend(true, {}, config.options, {
tooltips: {
backgroundColor: tooltipBackgroundColor,
borderColor: tooltipBorderColor,
multiKeyBackground: tooltipBackgroundColor
}
});
}
_computeTooltipBackgroundColor(type) {
return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'tooltip-background'], 'fill').fill;
}
_computeTooltipBorderColor(type) {
return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'tooltip-border'], 'fill').fill;
}
_adjustScaleColors(config) {
if (!config || !config.type || !config.options || !config.options.scale) {
return;
}
let labelColor = this._computeLabelColor(config.type),
labelBackdropColor = this._computeLabelBackdropColor(config.type),
gridColor = this._computeGridColor(config.type);
config.options.scale.ticks = $.extend(true, {}, config.options.scale.ticks, {
fontColor: gridColor,
backdropColor: labelBackdropColor
});
config.options.scale.pointLabels = $.extend(true, {}, config.options.scale.pointLabels, {
fontColor: labelColor
});
config.options.scale.gridLines = $.extend(true, {}, config.options.scale.gridLines, {
color: gridColor
});
}
_computeLabelBackdropColor(type) {
return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'label-backdrop'], 'fill', {fill: this.firstOpaqueBackgroundColor}).fill;
}
_computeGridColor(type) {
return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'grid'], 'fill').fill;
}
_adjustScalesColors(config) {
if (!config || !config.type || !config.options || !config.options.scales) {
return;
}
let xAxes = config.options.scales.xAxes || [],
yAxes = config.options.scales.yAxes || [],
axes = [...xAxes, ...yAxes];
if (!axes.length) {
return;
}
let labelColor = this._computeLabelColor(config.type),
gridColor = this._computeGridColor(config.type),
axisLabelColor = this._computeAxisLabelColor(config.type);
axes.forEach(elem => {
elem.gridLines = $.extend(true, {}, elem.gridLines, {
zeroLineColor: gridColor,
color: gridColor
});
elem.ticks = $.extend(true, {}, elem.ticks, {
fontColor: labelColor
});
elem.scaleLabel = $.extend(true, {}, elem.scaleLabel, {
fontColor: axisLabelColor
});
});
}
_computeAxisLabelColor(type) {
return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'axis-label'], 'fill').fill;
}
_adjustPluginColors(config) {
if (!config || !config.type || !config.options || !config.options.plugins) {
return;
}
this._adjustPluginsDatalabelColors(config);
}
_adjustPluginsDatalabelColors(config) {
if (!config || !config.type || !config.options || !config.options.plugins || !config.options.plugins.datalabels) {
return;
}
config.options.plugins.datalabels = $.extend(true, {}, config.options.plugins.datalabels, {
color: this._computeDatalabelColor(config.type)
});
}
_computeDatalabelColor(type) {
return styles.get([this.colorSchemeCssClass, type + '-chart', 'elements', 'datalabel'], 'fill').fill;
}
_adjustClickHandler(config) {
if (!config || !config.options) {
return;
}
if (config.options.clickable) {
config.options.onClick = this._clickHandler;
config.options.onHover = this._pointerHoverHandler;
} else {
config.options.onHover = this._hoverHandler;
}
if (!config.options.legend) {
return;
}
if (config.options.legend.clickable) {
config.options.legend.onClick = this._legendClickHandler;
config.options.legend.onHover = this._legendPointerHoverHandler;
config.options.legend.onLeave = this._legendPointerLeaveHandler;
} else {
config.options.legend.onClick = e => e.stopPropagation();
config.options.legend.onHover = this._legendHoverHandler;
config.options.legend.onLeave = this._legendLeaveHandler;
}
}
/**
* @param {object[]} items
* @param {number} items._index
* @param {number} items._datasetIndex
*/
_onClick(event, items) {
if (!items.length || this._isMaxSegmentsExceeded(this.chartJs.config, items[0]._index)) {
return;
}
let itemIndex = items[0]._index,
datasetIndex = items[0]._datasetIndex,
clickObject = {
datasetIndex: datasetIndex,
dataIndex: itemIndex
};
if (this.chartJs.config.type === Chart.Type.BUBBLE) {
let data = this.chartJs.config.data.datasets[datasetIndex].data[itemIndex];
clickObject.xIndex = data.x;
clickObject.yIndex = data.y;
} else {
clickObject.xIndex = itemIndex;
}
let e = new Event();
e.data = clickObject;
e.originalEvent = event;
this.chart._onValueClick(e);
}
_onHover(event, items) {
if (!this.chartJs.config || !this.chartJs.config.type) {
return;
}
let config = this.chartJs.config,
type = config.type;
if (!scout.isOneOf(type, Chart.Type.LINE, Chart.Type.BAR, Chart.Type.RADAR)) {
return;
}
let update = false;
if (this.resetDatasetAfterHover) {
this._adjustColors(config);
this.resetDatasetAfterHover = false;
update = true;
}
items.forEach(item => {
let dataset = config.data.datasets[item._datasetIndex];
if (scout.isOneOf((dataset.type || type), Chart.Type.LINE, Chart.Type.RADAR)) {
dataset.backgroundColor = dataset.hoverBackgroundColor;
this.resetDatasetAfterHover = true;
update = true;
}
});
if (update) {
this.chartJs.update();
}
}
_onHoverPointer(event, items) {
this._onHover(event, items);
if (items.length && !this._isMaxSegmentsExceeded(this.chartJs.config, items[0]._index)) {
this.$canvas.css('cursor', 'pointer');
} else {
this.$canvas.css('cursor', 'default');
}
}
_onLegendClick(event, item) {
if (!this.chartJs.config || !this.chartJs.config.type) {
return;
}
let type = this.chartJs.config.type,
defaultTypeLegendClick;
if (ChartJs.defaults[type] && ChartJs.defaults[type].legend) {
defaultTypeLegendClick = ChartJs.defaults[type].legend.onClick;
}
let defaultLegendClick = defaultTypeLegendClick || ChartJs.defaults.global.legend.onClick;
defaultLegendClick.call(this.chartJs, event, item);
this._onLegendLeave(event, item);
this._onLegendHoverPointer(event, item, true);
}
_onLegendHover(event, item, animated) {
let index = item.datasetIndex,
config = this.chartJs.config,
type = config.type;
if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
index = item.index;
}
if (this.legendHoverDatasets.indexOf(index) > -1) {
return;
}
let dataset = config.data.datasets[index],
datasetType = dataset ? dataset.type : null;
if ((datasetType || type) === Chart.Type.LINE) {
dataset.backgroundColor = dataset.hoverBackgroundColor;
this.chartJs.update();
}
this._updateHoverStyle(index, true);
if (animated) {
this.chartJs.render();
} else {
this.chartJs.render({duration: 0});
}
this.legendHoverDatasets.push(index);
}
_onLegendHoverPointer(event, item, animated) {
this._onLegendHover(event, item, animated);
this.$canvas.css('cursor', 'pointer');
}
_onLegendLeave(event, item) {
let index = item.datasetIndex,
config = this.chartJs.config,
type = config.type;
if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
index = item.index;
}
if (this.legendHoverDatasets.indexOf(index) < 0) {
return;
}
let dataset = config.data.datasets[index],
datasetType = dataset ? dataset.type : null;
if ((datasetType || type) === Chart.Type.LINE) {
this._adjustColors(config);
this.chartJs.update();
}
this._updateHoverStyle(index, false);
this.chartJs.render();
this.legendHoverDatasets.splice(this.legendHoverDatasets.indexOf(index), 1);
}
_onLegendLeavePointer(event, item) {
this._onLegendLeave(event, item);
this.$canvas.css('cursor', 'default');
}
_updateHoverStyle(index, enabled) {
let config = this.chartJs.config,
type = config.type,
datasets = config.data.datasets,
dataset = datasets ? datasets[index] : null,
datasetType = dataset ? dataset.type : null;
if ((datasetType || type) === Chart.Type.LINE) {
this.chartJs.updateHoverStyle(this.chartJs.getDatasetMeta(index).data, 'point', enabled);
} else if (scout.isOneOf(type, Chart.Type.PIE, Chart.Type.DOUGHNUT, Chart.Type.POLAR_AREA)) {
let elements = [];
for (let i = 0; i < datasets.length; i++) {
elements.push(this.chartJs.getDatasetMeta(i).data[index]);
}
this.chartJs.updateHoverStyle(elements, 'point', enabled);
} else {
let elements = this.chartJs.getDatasetMeta(index).data;
if (elements && elements.length) {
this.chartJs.updateHoverStyle(this.chartJs.getDatasetMeta(index).data, 'dataset', enabled);
}
}
}
_adjustResizeHandler(config) {
if (!config || !config.options) {
return;
}
if (config.options.handleResize) {
config.options.onResize = this._resizeHandler;
}
}
_onResize(chart, size) {
chart.update();
this._adjustSize(chart.config, chart.chartArea);
}
_adjustSize(config, chartArea) {
this._adjustBubbleSizes(config, chartArea);
this._adjustGridMaxMin(config, chartArea);
}
_adjustBubbleSizes(config, chartArea) {
if (config.type !== Chart.Type.BUBBLE) {
return;
}
let datasets = config.data.datasets;
// Scale all bubbles so that the largest radius is equal to sizeOfLargestBubble and the smallest greater than or equal to minBubbleSize.
// First reset all radii.
datasets.forEach(dataset => dataset.data.forEach(data => {
if (!isNaN(data.z)) {
data.r = Math.sqrt(data.z);
}
}));
let maxMinR = this._computeMaxMinValue(datasets, 'r', true),
maxR = maxMinR.maxValue,
minR = maxMinR.minValue,
// Compute a scalingFactor and an offset to get the new radius newR = r * scalingFactor + offset.
bubbleScalingFactor = 1,
bubbleRadiusOffset = 0,
sizeOfLargestBubble = config.bubble ? config.bubble.sizeOfLargestBubble : 0,
minBubbleSize = config.bubble ? config.bubble.minBubbleSize : 0;
if (sizeOfLargestBubble) {
let width = Math.abs(chartArea.right - chartArea.left),
height = Math.abs(chartArea.top - chartArea.bottom);
sizeOfLargestBubble = Math.min(sizeOfLargestBubble, Math.floor(Math.min(width, height) / 6));
if (maxR === 0) {
// If maxR is equal to 0, all radii are equal to 0, therefore set bubbleRadiusOffset to sizeOfLargestBubble.
bubbleRadiusOffset = sizeOfLargestBubble;
} else if (minBubbleSize && sizeOfLargestBubble > minBubbleSize && (minR / maxR) < (minBubbleSize / sizeOfLargestBubble)) {
// If minR/maxR is smaller than minBubbleSize/sizeOfLargestBubble, then it is not sufficient to scale all radii.
// The scalingFactor and the result from the following two conditions:
// (1) minBubbleSize = offset + scalingFactor * minR
// (2) sizeOfLargestBubble = offset + scalingFactor * maxR
// Therefore
// (1*) offset = minBubbleSize - scalingFactor * minR
// (2*) offset = sizeOfLargestBubble - scalingFactor * maxR
// (1*) = (2*):
// minBubbleSize - scalingFactor * minR = sizeOfLargestBubble - scalingFactor * maxR
// <=> scalingFactor * maxR - scalingFactor * minR = sizeOfLargestBubble - minBubbleSize
// <=> scalingFactor * (maxR - minR) = sizeOfLargestBubble - minBubbleSize
// <=> scalingFactor = (sizeOfLargestBubble - minBubbleSize) / (maxR - minR)
bubbleScalingFactor = (sizeOfLargestBubble - minBubbleSize) / (maxR - minR);
bubbleRadiusOffset = minBubbleSize - bubbleScalingFactor * minR;
} else {
// Scaling is sufficient.
bubbleScalingFactor = sizeOfLargestBubble / maxR;
}
} else if (minBubbleSize && minBubbleSize > minR) {
// sizeOfLargestBubble is not set
if (minR === 0) {
// If the smallest radius equals 0 scaling will have no effect.
bubbleRadiusOffset = minBubbleSize;
} else {
// Scaling is sufficient.
bubbleScalingFactor = minBubbleSize / minR;
}
}
datasets.forEach(dataset => dataset.data.forEach(data => {
if (!objects.isNullOrUndefined(data.r)) {
data.r = data.r * bubbleScalingFactor + bubbleRadiusOffset;
}
}));
}
_computeMaxMinValue(datasets, identifier, exact, boundRange, padding, space) {
if (!datasets) {
return;
}
let maxValue, minValue;
for (let i = 0; i < datasets.length; i++) {
for (let j = 0; j < datasets[i].data.length; j++) {
let value;
if (identifier) {
value = datasets[i].data[j][identifier];
} else {
value = datasets[i].data[j];
}
if (isNaN(maxValue)) {
maxValue = value;
} else {
maxValue = Math.max(value, maxValue);
}
if (isNaN(minValue)) {
minValue = value;
} else {
minValue = Math.min(value, minValue);
}
}
}
if (isNaN(maxValue)) {
maxValue = 0;
}
if (isNaN(minValue)) {
minValue = 0;
}
let adjust = 0,
maxBoundary = maxValue,
minBoundary = minValue;
if (!exact) {
if (boundRange && Math.sign(minValue) === Math.sign(maxValue)) {
adjust = Math.floor(minValue);
}
maxBoundary = this._calculateBoundary(maxValue - adjust, Math.ceil, Math.floor);
minBoundary = this._calculateBoundary(minValue - adjust, Math.floor, Math.ceil);
}
if (padding && space && space > 2 * padding) {
let valuePerPixel = (maxValue - minValue) / (space - 2 * padding),
paddingValue = valuePerPixel * padding;
maxBoundary = Math.max(maxBoundary, maxValue - adjust + paddingValue);
minBoundary = Math.min(minBoundary, minValue - adjust - paddingValue);
}
if (!exact) {
return {
maxValue: maxBoundary + adjust,
minValue: minBoundary + adjust
};
}
return {
maxValue: maxBoundary,
minValue: minBoundary
};
}
_calculateBoundary(value, roundingFunctionPositive, roundingFunctionNegative) {
let roundingFunction = roundingFunctionPositive;
let changeValueSign = false;
if (value < 0) {
changeValueSign = true;
value = value * (-1);
roundingFunction = roundingFunctionNegative;
}
value = this._calculateBoundaryPositive(value, roundingFunction);
if (changeValueSign) {
value = value * (-1);
}
return value;
}
_calculateBoundaryPositive(value, roundingFunction) {
if (!(value > 0) || !roundingFunction) {
return value;
}
// example: the value 32689 should be rounded to 30000 for the roundingFunction Math.floor or 35000 for Math.ceil or Math.round
// first calculate the exponent p of the largest 1ep smaller than the given value
// example: the largest 1ep smaller than the value 32689 is 10000 = 1e4 and therefore p = 4
let p = Math.floor(Math.log(value) / Math.LN10);
// divide by 5e(p-1), round and multiply with 5e(p-1) to round the value in 5e(p-1) steps
// example: the value is now divided by 5e(p-1) which means 32689 / 5e(4-1) = 32689 / 5e3 = 32689 / 5000 = 6.5378
// this result is now rounded (Math.floor gives 6, Math.ceil and Math.round gives 7) and multiplied again with 5000 which results in 30000 or 35000 respectively
if (p < 0) {
value = roundingFunction(value * Math.pow(10, Math.abs(p)) / 5) * 5 / Math.pow(10, Math.abs(p));
} else {
value = roundingFunction(value / (5 * Math.pow(10, p - 1))) * 5 * Math.pow(10, p - 1);
}
return value;
}
_adjustGridMaxMin(config, chartArea) {
if (!config || !config.type || !config.options || !config.options.adjustGridMaxMin || (!config.options.scale && !config.options.scales) || !chartArea) {
return;
}
let type = config.type;
if (!scout.isOneOf(type, Chart.Type.BAR, Chart.Type.BAR_HORIZONTAL, Chart.Type.LINE, Chart.Type.POLAR_AREA, Chart.Type.RADAR, Chart.Type.BUBBLE)) {
return;
}
let width = Math.abs(chartArea.right - chartArea.left),
height = Math.abs(chartArea.top - chartArea.bottom),
maxXTicks = Math.max(Math.floor(width / this.minSpaceBetweenXTicks), 3),
maxYTicks = Math.max(Math.floor(height / this.minSpaceBetweenYTicks), 3);
let yBoundaries = this._computeYBoundaries(config, height),
yBoundary = yBoundaries.yBoundary,
yBoundaryDiffType = yBoundaries.yBoundaryDiffType;
if (config.options.scale) {
this._adjustScaleMaxMin(config.options.scale, maxYTicks, yBoundary);
return;
}
let xAxes = config.options.scales.xAxes,
yAxes = config.options.scales.yAxes;
if (yBoundaryDiffType) {
this._adjustAxes(arrays.ensure(yAxes[0]), maxYTicks, yBoundary);
this._adjustAxes(arrays.ensure(yAxes[1]), maxYTicks, yBoundaryDiffType);
} else if (type === Chart.Type.BAR_HORIZONTAL) {
this._adjustAxes(xAxes, maxXTicks, yBoundary);
} else {
this._adjustAxes(yAxes, maxYTicks, yBoundary);
}
if (type !== Chart.Type.BUBBLE) {
return;
}
let xBoundary = this._computeXBoundaryBubble(config, width);
this._adjustAxes(xAxes, maxXTicks, xBoundary);
}
_computeBoundaryBubble(config, identifier, space) {
if (!config || !config.type || config.type !== Chart.Type.BUBBLE || !config.data || !config.options || !config.options.scales || !(identifier === 'x' || identifier === 'y') || !space) {
return;
}
let datasets = config.data.datasets,
axes = config.options.scales[identifier + 'Axes'],
axis = (axes && axes.length) ? axes[0] : null,
offset = axis && axis.offset,
labelMap = config.options.scales[identifier + 'LabelMap'],
boundary;
let maxR = this._computeMaxMinValue(datasets, 'r', true).maxValue,
padding = maxR;
if (config.options.elements && config.options.elements.point && config.options.elements.point.hoverRadius) {
padding = padding + config.options.elements.point.hoverRadius;
}
if (offset) {
boundary = this._computeMaxMinValue(datasets, identifier, labelMap, true);
} else {
boundary = this._computeMaxMinValue(datasets, identifier, labelMap, true, padding, space);
}
if (labelMap) {
boundary.maxValue = Math.ceil(boundary.maxValue);
boundary.minValue = Math.floor(boundary.minValue);
}
return boundary;
}
_computeXBoundaryBubble(config, width) {
return this._computeBoundaryBubble(config, 'x', width);
}
_computeYBoundaryBubble(config, height) {
return this._computeBoundaryBubble(config, 'y', height);
}
_computeYBoundaries(config, height) {
if (!config || !config.type) {
return {};
}
let type = config.type,
yBoundary,
yBoundaryDiffType;
if (type === Chart.Type.BUBBLE) {
yBoundary = this._computeYBoundaryBubble(config, height);
} else {
let datasets = [],
datasetsDiffType = [];
if (config.data && config.data.datasets) {
config.data.datasets.forEach(dataset => {
if (dataset.type && dataset.type !== type) {
datasetsDiffType.push(dataset);
} else {
datasets.push(dataset);
}
});
}
yBoundary = this._computeMaxMinValue(datasets);
if (datasets.length && datasetsDiffType.length) {
yBoundaryDiffType = this._computeMaxMinValue(datasetsDiffType);
let yBoundaryRange = yBoundary.maxValue - yBoundary.minValue,
yBoundaryRangeDiffType = yBoundaryDiffType.maxValue - yBoundaryDiffType.minValue;
if (yBoundaryRange && yBoundaryRangeDiffType && (yBoundaryRange / yBoundaryRangeDiffType > 10 || yBoundaryRangeDiffType / yBoundaryRange > 10)) {
this._adjustYAxisDiffType(config, datasets, datasetsDiffType);
}
}
}
return {
yBoundary: yBoundary,
yBoundaryDiffType: yBoundaryDiffType
};
}
_adjustYAxisDiffType(config, datasets, datasetsDiffType) {
if (!config || !config.type || !datasets || !datasets.length || !datasetsDiffType || !datasetsDiffType.length) {
return;
}
if (!config.options || !config.options.scales || !config.options.scales.yAxes || config.options.scales.yAxes.length !== 1) {
return;
}
let type = config.type,
scales = config.options.scales,
yAxis = scales.yAxes[0],
yAxisDiffType = $.extend(true, {}, yAxis);
scales.yAxes.push(yAxisDiffType);
yAxis.id = 'yAxis';
yAxisDiffType.id = 'yAxisDiffType';
if (config.data && config.data.datasets && config.data.datasets.length && config.data.datasets[0].type && config.data.datasets[0].type !== type) {
yAxisDiffType.position = Chart.Position.LEFT;
yAxis.position = Chart.Position.RIGHT;
yAxis.gridLines.drawOnChartArea = false;
} else {
yAxis.position = Chart.Position.LEFT;
yAxisDiffType.position = Chart.Position.RIGHT;
yAxisDiffType.gridLines.drawOnChartArea = false;
}
yAxis.gridLines.drawBorder = true;
yAxis.gridLines.drawTicks = true;
yAxisDiffType.gridLines.drawBorder = true;
yAxisDiffType.gridLines.drawTicks = true;
let yAxisType = (datasets[0].type || type),
yAxisDiffTypeType = (datasetsDiffType[0].type || type),
yAxisTypeLabel = this.chart.session.text('ui.' + yAxisType),
yAxisDiffTypeTypeLabel = this.chart.session.text('ui.' + yAxisDiffTypeType),
yAxisScaleLabel = scales.scaleLabelByTypeMap ? scales.scaleLabelByTypeMap[yAxisType] : null,
yAxisDiffTypeScaleLabel = scales.scaleLabelByTypeMap ? scales.scaleLabelByTypeMap[yAxisDiffTypeType] : null;
yAxis.scaleLabel.display = true;
yAxis.scaleLabel.labelString = yAxisScaleLabel ? yAxisScaleLabel + ' (' + yAxisTypeLabel + ')' : yAxisTypeLabel;
yAxisDiffType.scaleLabel.display = true;
yAxisDiffType.scaleLabel.labelString = yAxisDiffTypeScaleLabel ? yAxisDiffTypeScaleLabel + ' (' + yAxisDiffTypeTypeLabel + ')' : yAxisDiffTypeTypeLabel;
datasets.forEach(dataset => {
dataset.yAxisID = 'yAxis';
});
datasetsDiffType.forEach(dataset => {
dataset.yAxisID = 'yAxisDiffType';
});
}
_adjustScaleMaxMin(scale, maxTicks, maxMinValue) {
scale.ticks = $.extend(true, {}, scale.ticks, {
maxTicksLimit: Math.ceil(maxTicks / 2)
});
if (maxMinValue) {
scale.ticks.suggestedMax = maxMinValue.maxValue;
scale.ticks.suggestedMin = maxMinValue.minValue;
}
}
_adjustAxes(axes, maxTicks, maxMinValue) {
if (!axes || !Array.isArray(axes) || !axes.length) {
return;
}
for (let i = 0; i < axes.length; i++) {
axes[i] = $.extend(true, {}, axes[i], {
ticks: {
maxTicksLimit: maxTicks
}
});
if (maxMinValue) {
axes[i].ticks.suggestedMax = maxMinValue.maxValue;
axes[i].ticks.suggestedMin = maxMinValue.minValue;
}
}
}
_remove(afterRemoveFunc) {
if (this.rendered) {
this.$canvas.remove();
this.$canvas = null;
this.chartJs.destroy();
this.chartJs = null;
}
super._remove(afterRemoveFunc);
}
}