UNPKG

@eclipse-scout/chart

Version:
1,358 lines (1,322 loc) 329 kB
import * as __WEBPACK_EXTERNAL_MODULE__eclipse_scout_core_ec9759ad__ from "@eclipse-scout/core"; import * as __WEBPACK_EXTERNAL_MODULE_chart_js_1c0615aa__ from "chart.js"; import * as __WEBPACK_EXTERNAL_MODULE_chart_js_auto_c730fedb__ from "chart.js/auto"; import * as __WEBPACK_EXTERNAL_MODULE_chartjs_plugin_datalabels_94b90fda__ from "chartjs-plugin-datalabels"; /******/ var __webpack_modules__ = ({ /***/ "./src/bookmark/ChartTableControlConfigHelperImpl.ts": /*!***********************************************************!*\ !*** ./src/bookmark/ChartTableControlConfigHelperImpl.ts ***! \***********************************************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ ChartTableControlConfigHelperImpl: () => (/* binding */ ChartTableControlConfigHelperImpl) /* harmony export */ }); /* harmony import */ var _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @eclipse-scout/core */ "@eclipse-scout/core"); /* harmony import */ var _index__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../index */ "./src/index.ts"); /* * Copyright (c) 2010, 2025 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ class ChartTableControlConfigHelperImpl extends _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__.ChartTableControlConfigHelper { async _exportConfig(page) { if (!page?.detailTable) { return null; } let chartTableControl = page.detailTable.findTableControl(_index__WEBPACK_IMPORTED_MODULE_1__.ChartTableControl); if (chartTableControl && chartTableControl.selected) { return _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__.scout.create(_index__WEBPACK_IMPORTED_MODULE_1__.ChartTableControlConfigDo, { chartTypeId: chartTableControl.chartType, chartGroup1ColumnId: chartTableControl.chartGroup1?.id, chartGroup1Modifier: chartTableControl.chartGroup1?.modifier, chartGroup2ColumnId: chartTableControl.chartGroup2?.id, chartGroup2Modifier: chartTableControl.chartGroup2?.modifier, chartAggregationColumnId: chartTableControl.chartAggregation?.id, chartAggregationModifier: chartTableControl.chartAggregation?.modifier }); } return null; } async _importConfig(page, config) { if (!page?.detailTable || !config) { return; } let chartTableControl = page.detailTable.findTableControl(_index__WEBPACK_IMPORTED_MODULE_1__.ChartTableControl); if (chartTableControl) { chartTableControl.setSelected(true); // necessary, because otherwise internal data structures might not be properly initialized chartTableControl.setChartType(config.chartTypeId); chartTableControl.setChartGroup1({ id: config.chartGroup1ColumnId, modifier: config.chartGroup1Modifier }); chartTableControl.setChartGroup2({ id: config.chartGroup2ColumnId, modifier: config.chartGroup2Modifier }); chartTableControl.setChartAggregation({ id: config.chartAggregationColumnId, modifier: config.chartAggregationModifier }); } } } /***/ }), /***/ "./src/chart/AbstractChartRenderer.ts": /*!********************************************!*\ !*** ./src/chart/AbstractChartRenderer.ts ***! \********************************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ AbstractChartRenderer: () => (/* binding */ AbstractChartRenderer) /* harmony export */ }); /* harmony import */ var _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @eclipse-scout/core */ "@eclipse-scout/core"); /* * Copyright (c) 2010, 2025 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ class AbstractChartRenderer extends _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__.PropertyEventEmitter { chart; session; /** true while this.render() is executing */ rendering; rendered; /** set by render() and remove(), makes it unnecessary to carry an argument through all method calls */ animationDuration; firstOpaqueBackgroundColor; constructor(chart) { super(); this.chart = chart; this.session = chart.session; this.rendering = false; this.rendered = false; this.animationDuration = 0; this.firstOpaqueBackgroundColor = ''; } validate() { if (!this._validateChartData()) { return false; } return this._validate(); } _validateChartData() { let chartData = this.chart && this.chart.data; if (!chartData || !chartData.chartValueGroups || chartData.chartValueGroups.length === 0) { return false; } // check lengths let i, length = 0; for (i = 0; i < chartData.chartValueGroups.length; i++) { let chartValueGroup = chartData.chartValueGroups[i]; if (!chartValueGroup.values) { return false; } // Length of all "values" arrays have to be equal if (i === 0) { length = chartValueGroup.values.length; } else { if (chartValueGroup.values.length !== length) { return false; } } // color should have been set. if (!this.chart.config.options.autoColor && !_eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__.arrays.ensure(chartValueGroup.colorHexValue).length && !chartValueGroup.cssClass) { return false; } } for (i = 0; i < chartData.axes.length; i++) { if (chartData.axes[i].length !== length) { return false; } } return true; } _validate() { // Override in subclasses return true; } /** * @param requestAnimation * Whether animations should be used while rendering the chart. Note that his * property is ignored when chart.config.options.animation.duration is <code>0</code>! */ render(requestAnimation) { if (!this.validate() || !this.chart.rendered) { return; } const configAnimationDuration = this.chart.config.options.animation.duration; this.setAnimationDuration(requestAnimation ? configAnimationDuration : 0); this.rendering = true; this._render(); this.rendering = false; this.rendered = true; this.setAnimationDuration(configAnimationDuration); } _render() { // Override in subclasses } renderCheckedItems() { if (this.rendered) { this._renderCheckedItems(); } } _renderCheckedItems() { // nop } /** * @param requestAnimation * Whether animations should be used while updating the chart. Note that his * property is ignored when chart.config.options.animation.duration is <code>0</code>! */ updateData(requestAnimation) { if (!this.rendered) { this.render(requestAnimation); return; } if (!this.validate() || !this.isDataUpdatable()) { return; } const configAnimationDuration = this.chart.config.options.animation.duration; this.setAnimationDuration(requestAnimation ? configAnimationDuration : 0); this._updateData(); this.setAnimationDuration(configAnimationDuration); } _updateData() { // Override in subclasses } isDataUpdatable() { return false; } isDetachSupported() { return true; } refresh() { if (this.rendering) { return; } if (this.rendered) { this.remove(false); } this.render(false); } setAnimationDuration(animationDuration) { if (!this.setProperty('animationDuration', animationDuration)) { return; } if (this.rendered) { this._renderAnimationDuration(); } } _setAnimationDuration(animationDuration) { this._setProperty('animationDuration', animationDuration); } _renderAnimationDuration() { // nop } /** * @param requestAnimation * Whether animations should be used while removing the chart. Note that his * property is ignored when chart.config.options.animation.duration is <code>0</code>! */ remove() { let requestAnimation = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; let afterRemoveFunc = arguments.length > 1 ? arguments[1] : undefined; const configAnimationDuration = this.chart.config.options.animation.duration; this.setAnimationDuration(requestAnimation && configAnimationDuration); if (this.animationDuration && this.rendered) { this._removeAnimated(afterRemoveFunc); } else { this._remove(afterRemoveFunc); } this.setAnimationDuration(configAnimationDuration); } _remove(afterRemoveFunc) { this.rendered = false; afterRemoveFunc && afterRemoveFunc(); } _removeAnimated(afterRemoveFunc) { // Override in subclasses this._remove(afterRemoveFunc); } /** * Controls if the animation of the chart is shown when chart data has been updated. */ shouldAnimateRemoveOnUpdate(opts) { return opts.requestAnimation; } } /***/ }), /***/ "./src/chart/AbstractSvgChartRenderer.ts": /*!***********************************************!*\ !*** ./src/chart/AbstractSvgChartRenderer.ts ***! \***********************************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ AbstractSvgChartRenderer: () => (/* binding */ AbstractSvgChartRenderer) /* harmony export */ }); /* harmony import */ var _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @eclipse-scout/core */ "@eclipse-scout/core"); /* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! jquery */ "jquery"); /* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(jquery__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var _index__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../index */ "./src/index.ts"); /* * Copyright (c) 2010, 2025 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ class AbstractSvgChartRenderer extends _index__WEBPACK_IMPORTED_MODULE_2__.AbstractChartRenderer { chartBox; /** Clipping and masking */ clipId; maskId; suppressLegendBox; height; width; chartAnimationStopping; $svg; _nonValueClickHandler; constructor(chart) { super(chart); this.chartBox = null; this.clipId = 'Clip-' + _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__.ObjectIdProvider.get().createUiSeqId(); this.maskId = 'Mask-' + _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__.ObjectIdProvider.get().createUiSeqId(); this.suppressLegendBox = false; this._nonValueClickHandler = this._onNonValueClick.bind(this); } static FONT_SIZE_SMALLEST = 'smallestFont'; static FONT_SIZE_SMALL = 'smallFont'; static FONT_SIZE_MIDDLE = 'middleFont'; static FONT_SIZE_BIG = 'bigFont'; _render() { if (!this.$svg) { this.$svg = this.chart.$container.appendSVG('svg', 'chart-svg'); _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__.aria.role(this.$svg, 'img'); // labeling has to be done here because otherwise the svg is ignored this.linkChartWithFieldLabel(this.$svg); this.$svg.on('click', this._nonValueClickHandler); } this.firstOpaqueBackgroundColor = _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__.styles.getFirstOpaqueBackgroundColor(this.$svg); // This works, because CSS specifies 100% width/height this.height = this.$svg.height(); this.width = this.$svg.width(); this._initChartBox(); if (this._useFontSizeBig()) { this.$svg.addClass(AbstractSvgChartRenderer.FONT_SIZE_BIG); } else if (this._useFontSizeMiddle()) { this.$svg.addClass(AbstractSvgChartRenderer.FONT_SIZE_MIDDLE); } else if (this._useFontSizeSmall()) { this.$svg.addClass(AbstractSvgChartRenderer.FONT_SIZE_SMALL); } else if (this._useFontSizeSmallest()) { this.$svg.addClass(AbstractSvgChartRenderer.FONT_SIZE_SMALLEST); } if (!this.$svg.isAttached()) { // user navigated away. do not try to render->error return; } this._renderInternal(); } /** * Links chart svg with its field label so the field name is read when entering the chart * * @see <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-labelledby">ARIA: aria-labelledby</a> */ linkChartWithFieldLabel($chartSvg) { if (!$chartSvg) { return; } let $field = $chartSvg.parents('.chart-field'); if ($field.length > 0) { let $fieldLabel = $field.eq(0).children('label'); if ($fieldLabel.length > 0) { _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__.aria.linkElementWithLabel($chartSvg, $fieldLabel.eq(0)); } } } _renderInternal() { // Override in subclasses } _useFontSizeBig() { return false; } _useFontSizeMiddle() { return false; } _useFontSizeSmall() { return false; } _useFontSizeSmallest() { return false; } remove() { let requestAnimation = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; let afterRemoveFunc = arguments.length > 1 ? arguments[1] : undefined; if (this.rendered && !this.chartAnimationStopping) { this.chartAnimationStopping = true; this.$svg.children().stop(true, false); this.chartAnimationStopping = false; } super.remove(requestAnimation, afterRemoveFunc); } _remove(afterRemoveFunc) { // this function is called directly from renderers after all removal animations are done // however, other animations may have been queued in the meantime (e.g. in case the chart was removed, then added (+animation queued), and then removed again) if (this.rendered) { this.$svg.children().stop(true, false); // need to check again, as stop() may have triggered a chart removal and this may not be rendered anymore if (this.rendered) { this.$svg.remove(); this.$svg = null; } } this.rendered = false; afterRemoveFunc && afterRemoveFunc(this.chartAnimationStopping); } /** * For all parameters: use null when parameter is not used or set by a chart type. */ _createClickObject(xIndex, datasetIndex) { return { xIndex: xIndex, dataIndex: xIndex, datasetIndex: datasetIndex }; } _onChartValueClick(event) { this.chart.handleValueClick(event.data, event.originalEvent); event.stopPropagation(); } _onNonValueClick(event) { this.chart.handleNonValueClick(event.originalEvent); event.stopPropagation(); } _measureText(text, legendLabelClass) { let $label = this.$svg.appendSVG('text', legendLabelClass).attr('x', 0).attr('y', 0).attr('visibility', 'hidden').text(text); let textBounds; try { // Firefox throws error when node is not in dom(already removed by navigating away). all other browser returns a bounding box with 0 textBounds = $label[0].getBBox(); } catch (e) { return { height: 0, width: 0 }; } $label.remove(); return textBounds; } _renderLine(x1, y1, x2, y2, lineClass) { let $line = this.$svg.appendSVG('line', lineClass).attr('x1', x1).attr('y1', y1).attr('x2', x2).attr('y2', y2); if (this.animationDuration) { $line.attr('opacity', 0).animateSVG('opacity', 1, this.animationDuration, null, true); } return $line; } _renderLineLabel(x, y, label, labelClass, drawBackground) { let $label = this.$svg.appendSVG('text', labelClass ? labelClass : 'line-label').attr('x', x).attr('y', y).text(label); if (drawBackground) { $label.attr('mask', 'url(#' + this.maskId + ')'); let $background = this.$svg.appendSVG('text', labelClass ? labelClass + ' background' : 'line-label-background').attr('x', x).attr('y', y).attr('clip-path', 'url(#' + this.clipId + ')').text(label); $label.data('$background', $background); if (this.animationDuration) { $background.attr('opacity', 0).animateSVG('opacity', 1, this.animationDuration, null, true); } } if (this.animationDuration) { $label.attr('opacity', 0).animateSVG('opacity', 1, this.animationDuration, null, true); } return $label; } _initChartBox() { this.chartBox = { width: this.width, height: this.height, xOffset: 0, yOffset: 0, mX: function () { return this.xOffset + this.width / 2; }, mY: function () { return this.yOffset + this.height / 2; } }; } _createAnimationObjectWithTabIndexRemoval(animationFunc, duration) { return { step: function (now, fx) { try { animationFunc.bind(this)(now, fx); } catch (e) { // prevent logging thousands of exceptions (1 per animation step) by stopping and clearing the queue jquery__WEBPACK_IMPORTED_MODULE_1___default()(this).stop(true, false); throw e; } }, duration: duration ? duration : _index__WEBPACK_IMPORTED_MODULE_2__.Chart.DEFAULT_ANIMATION_DURATION, complete: function () { jquery__WEBPACK_IMPORTED_MODULE_1___default()(this).removeAttr('tabindex'); } }; } _addClipping(cssClass) { // add clip and mask paths for all relevant objects let $clip = this.$svg.appendSVG('clipPath'); $clip[0].id = this.clipId; let $mask = this.$svg.appendSVG('mask'); $mask.appendSVG('rect').attr('x', 0).attr('y', 0).attr('width', '100%').attr('height', '100%').attr('fill', 'white'); $mask[0].id = this.maskId; this.chart.$container.find('.' + cssClass).each(function (i) { this.id = 'ClipMask-' + _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__.ObjectIdProvider.get().createUiSeqId(); $clip.appendSVG('use').attrXLINK('href', '#' + this.id); $mask.appendSVG('use').attrXLINK('href', '#' + this.id); }); } _renderWireLegend(text, legendPositions, className, drawBackgroundBox) { if (!this.chart.config.options.plugins.tooltip.enabled) { return { detachFunc: () => { // nop }, attachFunc: () => { // nop }, removeFunc: () => { // nop } }; } let legend = {}, padding = 5, $background, backgroundWidth = 0, lineHeight = 17, backgroundHeight = lineHeight; if (drawBackgroundBox) { $background = this.$svg.appendSVG('rect', 'wire-legend-background-box').attr('opacity', '1'); } let positions = legendPositions; // draw and measure label let $legend, lengthLegend = 0, horizontalSpace = 0; if (positions.h === -1) { horizontalSpace = positions.x2 - 2 * padding; } else { horizontalSpace = this.width - positions.x2 - 2 * padding; } if (Array.isArray(text)) { for (let i = 0; i < text.length; i++) { let posIndex = text.length - i - 1; let yPos = positions.y2 + positions.v * padding - lineHeight * posIndex - padding * posIndex; let $line = this._renderLineLabel(positions.x2 + padding, yPos, _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__.strings.truncateText(text[i], horizontalSpace, this._measureText.bind(this)), '', drawBackgroundBox); $line.addClass(className); lengthLegend = Math.max(lengthLegend, $line[0].getComputedTextLength()); if (i === 0) { $legend = $line; } else { if ($legend.data('lines')) { $legend.data('lines').push($line); } else { $legend.data('lines', [$line]); } } } } else { $legend = this._renderLineLabel(positions.x2 + padding, positions.y2 + positions.v * padding, _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__.strings.truncateText(text, horizontalSpace, this._measureText.bind(this)), '', drawBackgroundBox); $legend.addClass(className); lengthLegend = $legend[0].getComputedTextLength(); } backgroundWidth = lengthLegend + 2 * padding; if (legendPositions.autoPosition) { positions = legendPositions.posFunc.call(this, backgroundWidth, backgroundHeight); // adjust legend $legend.attr('x', positions.x2 + padding); $legend.attr('y', positions.y2 + positions.v * padding); } // fix layout depending on orientation of legend if (positions.h === -1) { $legend.attr('x', positions.x2 - padding - lengthLegend); $legend.css('text-anchor', 'left'); if ($legend.data('lines')) { $legend.data('lines').forEach($line => { $line.attr('x', positions.x2 - padding - lengthLegend); $line.css('text-anchor', 'left'); }); } } else { $legend.attr('x', positions.x2 + padding); $legend.css('text-anchor', 'right'); if ($legend.data('lines')) { $legend.data('lines').forEach($line => { $line.attr('x', positions.x2 + padding); $line.css('text-anchor', 'right'); }); } } if (positions.v === 1) { if ($legend.data('lines')) { $legend.data('lines').forEach(($line, i) => { $line.attr('y', positions.x2 - padding - lengthLegend); let index = 1 + i; $line.attr('y', positions.y2 + positions.v * padding + lineHeight * index + padding * (index + 1)); }); } $legend.attr('dy', '0.7em'); } else { if ($legend.data('lines')) { let index = $legend.data('lines').length; $legend.attr('y', positions.y2 + positions.v * padding - lineHeight * index - padding * index); $legend.data('lines').forEach(($line, i) => { index = $legend.data('lines').length - 1 - i; $line.attr('y', positions.y2 + positions.v * padding - lineHeight * index - padding * index); }); } } // align background text $legend.add($legend.data('lines')).each((i, line) => { let $line = jquery__WEBPACK_IMPORTED_MODULE_1___default()(line), $background = $line.data('$background'); if ($background) { $background.attr('x', $line.attr('x')); $background.attr('y', $line.attr('y')); $background.css('text-anchor', $line.css('text-anchor')); $background.attr('dy', $line.attr('dy')); } }); // draw lines, if wished let wires = []; if (positions.x1 > 0 && positions.y1 > 0) { wires.push(this._renderLine(positions.x1, positions.y1, positions.x2, positions.y2, 'label-line')); wires.push(this._renderLine(positions.x2, positions.y2, positions.x2 + positions.h * (lengthLegend + 2 * padding), positions.y2, 'label-line')); } $legend.data('wires', wires); let $svg = this.$svg; legend.detachFunc = () => { $legend.data('wires').forEach($wire => { $wire.detach(); }); if ($legend.data('lines')) { $legend.data('lines').forEach($line => { if ($line.data('$background')) { $svg.append($line.data('$background')); } $line.detach(); }); } if ($legend.data('$background')) { $legend.data('$background').remove(); } $legend.detach(); }; legend.attachFunc = () => { $svg.append($legend); if ($legend.data('$background')) { $svg.append($legend.data('$background')); } $svg.append($legend.data('wires')); if ($legend.data('lines')) { $legend.data('lines').forEach($line => { $svg.append($line); if ($line.data('$background')) { $svg.append($line.data('$background')); } }); } }; legend.removeFunc = () => { $legend.data('wires').forEach($wire => { $wire.remove(); }); if ($legend.data('lines')) { $legend.data('lines').forEach($line => { if ($line.data('$background')) { $line.data('$background').remove(); } $line().remove(); }); } if ($legend.data('$background')) { $legend.data('$background').remove(); } $legend.remove(); }; legend.$field = $legend; return legend; } } /***/ }), /***/ "./src/chart/Chart.ts": /*!****************************!*\ !*** ./src/chart/Chart.ts ***! \****************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ Chart: () => (/* binding */ Chart), /* harmony export */ ChartColorMode: () => (/* binding */ ChartColorMode) /* harmony export */ }); /* harmony import */ var _index__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../index */ "./src/index.ts"); /* harmony import */ var _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @eclipse-scout/core */ "@eclipse-scout/core"); /* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! jquery */ "jquery"); /* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(jquery__WEBPACK_IMPORTED_MODULE_2__); /* * Copyright (c) 2010, 2025 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ class Chart extends _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_1__.Widget { data; config; checkedItems; chartRenderer; /** @internal */ _updatedOnce; _updateChartTimeoutId; _updateChartOpts; _updateChartOptsWhileNotAttached; constructor() { super(); this.$container = null; this.data = null; this.config = null; this.checkedItems = []; this.chartRenderer = null; this._updateChartTimeoutId = null; this._updateChartOpts = null; this._updateChartOptsWhileNotAttached = []; this._updatedOnce = false; } static Type = { PIE: 'pie', LINE: 'line', BAR: 'bar', BAR_HORIZONTAL: 'horizontalBar', COMBO_BAR_LINE: 'comboBarLine', FULFILLMENT: 'fulfillment', SPEEDO: 'speedo', SALESFUNNEL: 'salesfunnel', VENN: 'venn', DOUGHNUT: 'doughnut', POLAR_AREA: 'polarArea', RADAR: 'radar', BUBBLE: 'bubble', SCATTER: 'scatter' }; static Position = { TOP: 'top', BOTTOM: 'bottom', LEFT: 'left', RIGHT: 'right', CENTER: 'center' }; static DEFAULT_ANIMATION_DURATION = 600; // ms static DEFAULT_DEBOUNCE_TIMEOUT = 100; // ms _init(model) { super._init(model); this.setConfig(this.config); this._setData(this.data); } _render() { this.$container = this.$parent.appendDiv('chart'); _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_1__.aria.role(this.$container, 'none'); // ignore this container for screen readers, they care about the chart inside this.htmlComp = _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_1__.HtmlComponent.install(this.$container, this.session); this.htmlComp.setLayout(new _index__WEBPACK_IMPORTED_MODULE_0__.ChartLayout(this)); // !!! Do _not_ update the chart here, because usually the container size // !!! is not correct anyway during the render phase. The ChartLayout // !!! will eventually call updateChart() when the layout is validated. } _renderProperties() { super._renderProperties(); this._renderClickable(); this._renderCheckable(); this._renderChartType(); this._renderColorScheme(); this.updateChart({ requestAnimation: true, debounce: Chart.DEFAULT_DEBOUNCE_TIMEOUT }); } _renderOnAttach() { super._renderOnAttach(); const updateChartOptsWhileNotAttached = this._updateChartOptsWhileNotAttached.splice(0); if (!this.chartRenderer?.isDetachSupported()) { // the chartRenderer does not support detach => recreate it this._updateChartRenderer(); updateChartOptsWhileNotAttached.forEach(opts => delete opts.requestAnimation); updateChartOptsWhileNotAttached.push({ requestAnimation: false }); } updateChartOptsWhileNotAttached.forEach(opts => this.updateChart(jquery__WEBPACK_IMPORTED_MODULE_2___default().extend(true, {}, opts, { debounce: true }))); } _remove() { if (this.chartRenderer) { this.chartRenderer.remove(false); } this.$container.remove(); this.$container = null; } setData(data) { this.setProperty('data', data); this.setCheckedItems(this.checkedItems); } _setData(data) { if (data) { data = jquery__WEBPACK_IMPORTED_MODULE_2___default().extend({ axes: [] }, data); } this._setProperty('data', data); } _renderData() { this.updateChart({ requestAnimation: true, debounce: Chart.DEFAULT_DEBOUNCE_TIMEOUT, onlyUpdateData: true }); } setConfig(config) { let defaultConfig = { type: Chart.Type.PIE, options: { autoColor: true, colorMode: ChartColorMode.AUTO, colorScheme: _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_1__.colorSchemes.ColorSchemeId.DEFAULT, transparent: false, maxSegments: 5, adjustGridMaxMin: true, clickable: false, checkable: false, animation: { duration: Chart.DEFAULT_ANIMATION_DURATION }, plugins: { datalabels: { display: false }, tooltip: { enabled: true }, legend: { display: true, clickable: false, position: Chart.Position.RIGHT, pointsVisible: true } } } }; config = jquery__WEBPACK_IMPORTED_MODULE_2___default().extend(true, {}, defaultConfig, config); config.options.colorScheme = _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_1__.colorSchemes.ensureColorScheme(config.options.colorScheme); if (_eclipse_scout_core__WEBPACK_IMPORTED_MODULE_1__.objects.equalsRecursive(this.config, config)) { return; } // check if only data has changed let oldConfigWithNewData = jquery__WEBPACK_IMPORTED_MODULE_2___default().extend(true, {}, this.config); if (config.data) { oldConfigWithNewData.data = config.data; } else { delete oldConfigWithNewData.data; } // the label map is technically part of the config, but it is handled as data. Therefore, it is excluded from this check. let transferLabelMap = (source, target, identifier) => { if (!source || !target || !identifier) { return; } // Property not set on source -> remove from target if (!source.options || !source.options[identifier]) { if (target.options) { delete target.options[identifier]; } if (target.options && _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_1__.objects.isEmpty(target.options.scales) && !(source.options && source.options.scales)) { delete target.options.scales; } if (_eclipse_scout_core__WEBPACK_IMPORTED_MODULE_1__.objects.isEmpty(target.options) && !source.options) { delete target.options; } return; } target.options[identifier] = source.options[identifier]; }; transferLabelMap(config, oldConfigWithNewData, 'xLabelMap'); transferLabelMap(config, oldConfigWithNewData, 'yLabelMap'); if (_eclipse_scout_core__WEBPACK_IMPORTED_MODULE_1__.objects.equalsRecursive(oldConfigWithNewData, config)) { this._setProperty('config', config); if (this.rendered) { this._renderConfig(true); } this.setCheckedItems(this.checkedItems); return; } if (this.rendered && this.config && this.config.type) { this.$container.removeClass(this.config.type + '-chart'); } this.setProperty('config', config); this.setCheckedItems(this.checkedItems); this._updateChartRenderer(); } _renderConfig(onlyUpdateData) { this._renderClickable(); this._renderCheckable(); this._renderChartType(); this._renderColorScheme(); this.updateChart({ requestAnimation: true, debounce: Chart.DEFAULT_DEBOUNCE_TIMEOUT, onlyUpdateData: onlyUpdateData }); } setCheckedItems(checkedItems) { this.setProperty('checkedItems', _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_1__.arrays.ensure(this._filterCheckedItems(checkedItems))); } _filterCheckedItems(checkedItems) { if (!Array.isArray(checkedItems)) { return checkedItems; } let datasetLengths = []; if (this.data && this.data.chartValueGroups) { this.data.chartValueGroups.forEach(chartValueGroup => datasetLengths.push(chartValueGroup.values.length)); } else if (this.config && this.config.data) { this.config.data.datasets.forEach(dataset => datasetLengths.push(dataset.data.length)); } let filteredCheckedItems = checkedItems.filter(item => datasetLengths[item.datasetIndex] && item.dataIndex < datasetLengths[item.datasetIndex]); if (filteredCheckedItems.length < checkedItems.length) { return filteredCheckedItems; } return checkedItems; } _renderCheckedItems() { if (this.chartRenderer) { this.chartRenderer.renderCheckedItems(); } } _renderEnabled() { this.updateChart(); } _renderClickable() { this.$container.toggleClass('clickable', this.config.options.clickable); } _renderCheckable() { this.$container.toggleClass('checkable', this.config.options.checkable); } _renderChartType() { this.$container.addClass(this.config.type + '-chart'); } _renderColorScheme() { _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_1__.colorSchemes.toggleColorSchemeClasses(this.$container, this.config.options.colorScheme); } updateChart(opts) { opts = opts || {}; opts.onlyUpdateData = opts.onlyUpdateData && this.chartRenderer && this.chartRenderer.isDataUpdatable(); opts.enforceRerender = !opts.onlyUpdateData && !opts.onlyRefresh; // Cancel previously scheduled update and merge opts if (this._updateChartTimeoutId) { clearTimeout(this._updateChartTimeoutId); if (this._updateChartOpts) { // Inherit 'true' values from previously scheduled updates opts.requestAnimation = opts.requestAnimation || this._updateChartOpts.requestAnimation; opts.onlyUpdateData = opts.onlyUpdateData || this._updateChartOpts.onlyUpdateData; opts.onlyRefresh = opts.onlyRefresh || this._updateChartOpts.onlyRefresh; opts.enforceRerender = opts.enforceRerender || this._updateChartOpts.enforceRerender; } this._updateChartTimeoutId = null; this._updateChartOpts = null; } let updateChartImplFn = updateChartImpl.bind(this); let doDebounce = opts.debounce === true || typeof opts.debounce === 'number'; if (doDebounce) { this._updateChartOpts = opts; if (typeof opts.debounce === 'number') { this._updateChartTimeoutId = setTimeout(updateChartImplFn, opts.debounce); } else { this._updateChartTimeoutId = setTimeout(updateChartImplFn); } } else { updateChartImplFn(); } // ---- Helper functions ----- function updateChartImpl() { this._updateChartTimeoutId = null; this._updateChartOpts = null; if (!this.$container || !this.$container.isAttached()) { this._updateChartOptsWhileNotAttached.push(opts); return; } this._updatedOnce = true; if (!this.chartRenderer) { return; // nothing to render when there is no renderer. } if (opts.enforceRerender) { this.chartRenderer.remove(this.chartRenderer.shouldAnimateRemoveOnUpdate(opts), chartAnimationStopping => { if (this.removing || chartAnimationStopping) { // prevent exceptions trying to render after navigated away, and do not update/render while a running animation is being stopped return; } this.chartRenderer.render(opts.requestAnimation); this.trigger('chartRender'); }); } else if (opts.onlyUpdateData) { this.chartRenderer.updateData(opts.requestAnimation); } else if (opts.onlyRefresh) { this.chartRenderer.refresh(); } } } _resolveChartRenderer() { switch (this.config.type) { case Chart.Type.FULFILLMENT: return new _index__WEBPACK_IMPORTED_MODULE_0__.FulfillmentChartRenderer(this); case Chart.Type.SPEEDO: return new _index__WEBPACK_IMPORTED_MODULE_0__.SpeedoChartRenderer(this); case Chart.Type.SALESFUNNEL: return new _index__WEBPACK_IMPORTED_MODULE_0__.SalesfunnelChartRenderer(this); case Chart.Type.VENN: return new _index__WEBPACK_IMPORTED_MODULE_0__.VennChartRenderer(this); case Chart.Type.BAR: case Chart.Type.BAR_HORIZONTAL: case Chart.Type.LINE: case Chart.Type.COMBO_BAR_LINE: case Chart.Type.PIE: case Chart.Type.DOUGHNUT: case Chart.Type.POLAR_AREA: case Chart.Type.RADAR: case Chart.Type.BUBBLE: case Chart.Type.SCATTER: return new _index__WEBPACK_IMPORTED_MODULE_0__.ChartJsRenderer(this); } return null; } _updateChartRenderer() { this.chartRenderer && this.chartRenderer.remove(); this.setProperty('chartRenderer', this._resolveChartRenderer()); } handleValueClick(clickedItem, originalEvent) { if (this.config.options.checkable) { let checkedItems = [...this.checkedItems], checkedItem = checkedItems.filter(item => item.datasetIndex === clickedItem.datasetIndex && item.dataIndex === clickedItem.dataIndex)[0]; if (checkedItem) { _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_1__.arrays.remove(checkedItems, checkedItem); } else { checkedItems.push(clickedItem); } this.setCheckedItems(checkedItems); } this.trigger('valueClick', { data: clickedItem, originalEvent }); } handleNonValueClick(originalEvent) { this.trigger('nonValueClick', { originalEvent }); } handleLegendClick(legentItemIndex, originalEvent) { this.trigger('legendItemClick', { legendItemIndex: legentItemIndex, originalEvent: originalEvent }); } } /** * Determines what parts of the chart data is colored with the same colors. */ var ChartColorMode; (function (ChartColorMode) { /** * Uses one of the other options depending on the chart type. */ ChartColorMode["AUTO"] = "auto"; /** * Uses a different color for each dataset. */ ChartColorMode["DATASET"] = "dataset"; /** * Uses a different color for each datapoint in a dataset but the n-th datapoint in each dataset will be colored using the same color. */ ChartColorMode["DATA"] = "data"; })(ChartColorMode || (ChartColorMode = {})); /***/ }), /***/ "./src/chart/ChartAdapter.ts": /*!***********************************!*\ !*** ./src/chart/ChartAdapter.ts ***! \***********************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ ChartAdapter: () => (/* binding */ ChartAdapter) /* harmony export */ }); /* harmony import */ var _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @eclipse-scout/core */ "@eclipse-scout/core"); /* * Copyright (c) 2010, 2023 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ class ChartAdapter extends _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_0__.ModelAdapter { _onWidgetValueClick(event) { this._send('valueClick', event.data); } _onWidgetEvent(event) { if (event.type === 'valueClick') { this._onWidgetValueClick(event); } else { super._onWidgetEvent(event); } } } /***/ }), /***/ "./src/chart/ChartEventMap.ts": /*!************************************!*\ !*** ./src/chart/ChartEventMap.ts ***! \************************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /***/ }), /***/ "./src/chart/ChartJsRenderer.ts": /*!**************************************!*\ !*** ./src/chart/ChartJsRenderer.ts ***! \**************************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ ChartJsRenderer: () => (/* binding */ ChartJsRenderer) /* harmony export */ }); /* harmony import */ var _index__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../index */ "./src/index.ts"); /* harmony import */ var chart_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! chart.js */ "chart.js"); /* harmony import */ var chart_js_auto__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! chart.js/auto */ "chart.js/auto"); /* harmony import */ var _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @eclipse-scout/core */ "@eclipse-scout/core"); /* harmony import */ var chartjs_plugin_datalabels__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! chartjs-plugin-datalabels */ "chartjs-plugin-datalabels"); /* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! jquery */ "jquery"); /* harmony import */ var jquery__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(jquery__WEBPACK_IMPORTED_MODULE_5__); /* * Copyright (c) 2010, 2025 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ // Import from auto to register charts chart_js__WEBPACK_IMPORTED_MODULE_1__.Chart.register(chartjs_plugin_datalabels__WEBPACK_IMPORTED_MODULE_4__["default"]); jquery__WEBPACK_IMPORTED_MODULE_5___default().extend(true, chart_js__WEBPACK_IMPORTED_MODULE_1__.Chart.defaults, { maintainAspectRatio: false, elements: { line: { borderWidth: 2 }, point: { radius: 0, hitRadius: 10, hoverRadius: 7, hoverBorderWidth: 2 }, arc: { borderWidth: 1 }, bar: { borderWidth: 1, borderSkipped: '' } }, plugins: { legend: { labels: { usePointStyle: true, boxHeight: 7 } } } }); jquery__WEBPACK_IMPORTED_MODULE_5___default().extend(true, chart_js__WEBPACK_IMPORTED_MODULE_1__.Chart.overrides, { line: { elements: { point: { borderWidth: 2 } } }, scatter: { elements: { point: { radius: 3 } } }, bubble: { layout: { // Enabled auto padding would cause Chart.js to automatically add padding, in the size of the largest bubble // (defined by 'sizeOfLargestBubble'), around the chart. This wastes spaces and is not necessary, as we adjust // the axes already with the bubble size in mind. autoPadding: false } } }); let chartJsGlobalsInitialized = false; const PHI = (1 + Math.sqrt(5)) / 2; // golden ratio class ChartJsRenderer extends _index__WEBPACK_IMPORTED_MODULE_0__.AbstractChartRenderer { static ARROW_LEFT_RIGHT = '\u2194'; static ARROW_UP_DOWN = '\u2195'; chartJs; onlyIntegers; maxXAxesTicksHeight; numSupportedColors; colorSchemeCssClass; minRadialChartDatalabelSpace; resetDatasetAfterHover; legendHoverDatasets; removing; $canvas; _labelFormatter; _xLabelFormatter; _yLabelFormatter; _xAxisFitter; _yAxisFitter; _radialChartDatalabelsDisplayHandler; _radialChartDatalabelsFormatter; _datalabelsFormatter; _datalabelBackgroundColorHandler; _legendLabelGenerator; _clickHandler; _hoverHandler; _pointerHoverHandler; _legendClickHandler; _legendHoverHandler; _legendPointerHoverHandler; _legendLeaveHandler; _legendPointerLeaveHandler; _resizeHandler; _tooltipTitleGenerator; _tooltipItemsGenerator; _tooltipLabelGenerator; _tooltipLabelValueGenerator; _tooltipLabelColorGenerator; _tooltipRenderer; _tooltip; _tooltipTimeoutId; _updatingDatalabels; _hoveringClickableElement; constructor(chart) { super(chart); this.chartJs = null; this.onlyIntegers = true; this.maxXAxesTicksHeight = 75; this.numSupportedColors = 6; this.colorSchemeCssClass = ''; this.minRadialChartDatalabelSpace = 25; this._labelFormatter = this._createLabelFormatter(this._formatLabel); this._xLabelFormatter = this._createLabelFormatter(this._formatXLabel); this._yLabelFormatter = this._createLabelFormatter(this._formatYLabel); 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); this._tooltipTitleGenerator = this._generateTooltipTitle.bind(this); this._tooltipItemsGenerator = this._generateTooltipItems.bind(this); this._tooltipLabelGenerator = this._generateTooltipLabel.bind(this); this._tooltipLabelValueGenerator = this._generateTooltipLabelValue.bind(this); this._tooltipLabelColorGenerator = this._generateTooltipLabelColor.bind(this); this._tooltipRenderer = this._renderTooltip.bind(this); this._tooltip = null; this._tooltipTimeoutId = null; } _validateChartData() { let chartDataValid = true; let chartData = this.chart && this.chart.data; if (!chartData || !chartData.chartValueGroups || chartData.chartValueGroups.length === 0 || !chartData.axes) { chartDataValid = false; } if (chartDataValid && _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_3__.scout.isOneOf(this.chart.config.type, _index__WEBPACK_IMPORTED_MODULE_0__.Chart.Type.POLAR_AREA, _index__WEBPACK_IMPORTED_MODULE_0__.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 && _eclipse_scout_core__WEBPACK_IMPORTED_MODULE_3__.scout.isOneOf(config.type, _index__WEBPACK_IMPORTED_MODULE_0__.Chart.Type.POLAR_AREA, _index__WEBPACK_IMPORTED_MODULE_0__.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.a