UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

615 lines (600 loc) • 24.2 kB
/** * DevExtreme (esm/viz/gauges/common.js) * Version: 24.2.6 * Build date: Mon Mar 17 2025 * * Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ import { BaseGauge, compareArrays as _compareArrays } from "./base_gauge"; import { isDefined as _isDefined, isNumeric as _isNumber } from "../../core/utils/type"; import { extend } from "../../core/utils/extend"; const _isArray = Array.isArray; import { Axis } from "../axes/base_axis"; import { map as _map, normalizeEnum as _normalizeEnum } from "../core/utils"; const _isFinite = isFinite; const _Number = Number; const _min = Math.min; const _max = Math.max; const _extend = extend; import { noop as _noop } from "../../core/utils/common"; const SHIFT_ANGLE = 90; const OPTION_VALUE = "value"; const OPTION_SUBVALUES = "subvalues"; const DEFAULT_MINOR_AXIS_DIVISION_FACTOR = 5; const DEFAULT_NUMBER_MULTIPLIERS = [1, 2, 5]; function processValue(value, fallbackValue) { if (null === value) { return value } return _isFinite(value) ? _Number(value) : fallbackValue } function parseArrayOfNumbers(arg) { return _isArray(arg) ? arg : _isNumber(arg) ? [arg] : null } export const dxGauge = BaseGauge.inherit({ _initCore: function() { const renderer = this._renderer; this._setupValue(this.option("value")); this.__subvalues = parseArrayOfNumbers(this.option("subvalues")); this._setupSubvalues(this.__subvalues); selectMode(this); this.callBase.apply(this, arguments); this._rangeContainer = new this._factory.RangeContainer({ renderer: renderer, container: renderer.root, translator: this._translator, themeManager: this._themeManager }); this._initScale(); this._subvalueIndicatorContainer = this._renderer.g().attr({ class: "dxg-subvalue-indicators" }).linkOn(this._renderer.root, "valueIndicator").enableLinks() }, _fontFields: ["scale.label.font", "valueIndicators.rangebar.text.font", "valueIndicators.textcloud.text.font", "indicator.text.font"], _initScale: function() { this._scaleGroup = this._renderer.g().attr({ class: "dxg-scale" }).linkOn(this._renderer.root, "scale"); this._labelsAxesGroup = this._renderer.g().attr({ class: "dxg-scale-elements" }).linkOn(this._renderer.root, "scale-elements"); this._scale = new Axis({ incidentOccurred: this._incidentOccurred, renderer: this._renderer, axesContainerGroup: this._scaleGroup, labelsAxesGroup: this._labelsAxesGroup, axisType: this._scaleTypes.type, drawingType: this._scaleTypes.drawingType, widgetClass: "dxg", getTemplate() {} }) }, _disposeCore: function() { this.callBase.apply(this, arguments); this._scale.dispose(); this._scaleGroup.linkOff(); this._labelsAxesGroup.linkOff(); this._rangeContainer.dispose(); this._disposeValueIndicators(); this._subvalueIndicatorContainer.linkOff(); this._scale = this._scaleGroup = this._labelsAxesGroup = this._rangeContainer = null }, _disposeValueIndicators: function() { this._valueIndicator && this._valueIndicator.dispose(); this._subvalueIndicatorsSet && this._subvalueIndicatorsSet.dispose(); this._valueIndicator = this._subvalueIndicatorsSet = null }, _setupDomainCore: function() { const scaleOption = this.option("scale") || {}; let startValue = this.option("startValue"); let endValue = this.option("endValue"); startValue = _isNumber(startValue) ? _Number(startValue) : _isNumber(scaleOption.startValue) ? _Number(scaleOption.startValue) : 0; endValue = _isNumber(endValue) ? _Number(endValue) : _isNumber(scaleOption.endValue) ? _Number(scaleOption.endValue) : 100; this._baseValue = startValue < endValue ? startValue : endValue; this._translator.setDomain(startValue, endValue) }, _cleanContent: function() { this._rangeContainer.clean(); this._cleanValueIndicators() }, _measureScale: function(scaleOptions) { const majorTick = scaleOptions.tick; const majorTickEnabled = majorTick.visible && majorTick.length > 0 && majorTick.width > 0; const minorTick = scaleOptions.minorTick; const minorTickEnabled = minorTick.visible && minorTick.length > 0 && minorTick.width > 0; const label = scaleOptions.label; const indentFromTick = Number(label.indentFromTick); if (!majorTickEnabled && !minorTickEnabled && !label.visible) { return {} } const textParams = this._scale.measureLabels(extend({}, this._canvas)); const layoutValue = this._getScaleLayoutValue(); const result = { min: layoutValue, max: layoutValue }; const coefs = this._getTicksCoefficients(scaleOptions); const innerCoef = coefs.inner; const outerCoef = coefs.outer; if (majorTickEnabled) { result.min = _min(result.min, layoutValue - innerCoef * majorTick.length); result.max = _max(result.max, layoutValue + outerCoef * majorTick.length) } if (minorTickEnabled) { result.min = _min(result.min, layoutValue - innerCoef * minorTick.length); result.max = _max(result.max, layoutValue + outerCoef * minorTick.length) } label.visible && this._correctScaleIndents(result, indentFromTick, textParams); return result }, _renderContent: function() { const that = this; const scaleOptions = that._prepareScaleSettings(); that._rangeContainer.render(_extend(that._getOption("rangeContainer"), { vertical: that._area.vertical })); that._renderScale(scaleOptions); that._subvalueIndicatorContainer.linkAppend(); const elements = _map([that._rangeContainer].concat(that._prepareValueIndicators()), (function(element) { return element && element.enabled ? element : null })); that._applyMainLayout(elements, that._measureScale(scaleOptions)); elements.forEach((element => element.resize(that._getElementLayout(element.getOffset())))); that._shiftScale(that._getElementLayout(0), scaleOptions); that._beginValueChanging(); that._updateActiveElements(); that._endValueChanging() }, _prepareScaleSettings: function() { const that = this; const userOptions = that.option("scale"); const scaleOptions = extend(true, {}, that._themeManager.theme("scale"), userOptions); scaleOptions.label.indentFromAxis = 0; scaleOptions.isHorizontal = !that._area.vertical; scaleOptions.forceUserTickInterval |= _isDefined(userOptions) && _isDefined(userOptions.tickInterval) && !_isDefined(userOptions.scaleDivisionFactor); scaleOptions.axisDivisionFactor = scaleOptions.scaleDivisionFactor || that._gridSpacingFactor; scaleOptions.minorAxisDivisionFactor = scaleOptions.minorScaleDivisionFactor || 5; scaleOptions.numberMultipliers = DEFAULT_NUMBER_MULTIPLIERS; scaleOptions.tickOrientation = that._getTicksOrientation(scaleOptions); if (scaleOptions.label.useRangeColors) { scaleOptions.label.customizeColor = function() { return that._rangeContainer.getColorForValue(this.value) } } return scaleOptions }, _renderScale: function(scaleOptions) { const bounds = this._translator.getDomain(); const startValue = bounds[0]; const endValue = bounds[1]; const angles = this._translator.getCodomain(); const invert = !!(startValue > endValue ^ scaleOptions.inverted); const min = _min(startValue, endValue); const max = _max(startValue, endValue); scaleOptions.min = min; scaleOptions.max = max; scaleOptions.startAngle = 90 - angles[0]; scaleOptions.endAngle = 90 - angles[1]; scaleOptions.skipViewportExtending = true; scaleOptions.inverted = invert; this._scale.updateOptions(scaleOptions); this._scale.setBusinessRange({ axisType: "continuous", dataType: "numeric", min: min, max: max, invert: invert }); this._updateScaleTickIndent(scaleOptions); this._scaleGroup.linkAppend(); this._labelsAxesGroup.linkAppend(); this._scale.draw(extend({}, this._canvas)) }, _updateIndicatorSettings: function(settings) { const that = this; settings.currentValue = settings.baseValue = _isFinite(that._translator.translate(settings.baseValue)) ? _Number(settings.baseValue) : that._baseValue; settings.vertical = that._area.vertical; if (settings.text && !settings.text.format) { settings.text.format = that._defaultFormatOptions } }, _prepareIndicatorSettings: function(options, defaultTypeField) { const theme = this._themeManager.theme("valueIndicators"); const type = _normalizeEnum(options.type || this._themeManager.theme(defaultTypeField)); const settings = _extend(true, {}, theme._default, theme[type], options); settings.type = type; settings.animation = this._animationSettings; settings.containerBackgroundColor = this._containerBackgroundColor; this._updateIndicatorSettings(settings); return settings }, _cleanValueIndicators: function() { this._valueIndicator && this._valueIndicator.clean(); this._subvalueIndicatorsSet && this._subvalueIndicatorsSet.clean() }, _prepareValueIndicators: function() { this._prepareValueIndicator(); null !== this.__subvalues && this._prepareSubvalueIndicators(); return [this._valueIndicator, this._subvalueIndicatorsSet] }, _updateActiveElements: function() { this._updateValueIndicator(); this._updateSubvalueIndicators() }, _prepareValueIndicator: function() { const that = this; let target = that._valueIndicator; const settings = that._prepareIndicatorSettings(that.option("valueIndicator") || {}, "valueIndicatorType"); if (target && target.type !== settings.type) { target.dispose(); target = null } if (!target) { target = that._valueIndicator = that._createIndicator(settings.type, that._renderer.root, "dxg-value-indicator", "value-indicator") } target.render(settings) }, _createSubvalueIndicatorsSet: function() { const that = this; const root = that._subvalueIndicatorContainer; return new ValueIndicatorsSet({ createIndicator: function(type, i) { return that._createIndicator(type, root, "dxg-subvalue-indicator", "subvalue-indicator", i) }, createPalette: function(palette) { return that._themeManager.createPalette(palette) } }) }, _prepareSubvalueIndicators: function() { const that = this; let target = that._subvalueIndicatorsSet; const settings = that._prepareIndicatorSettings(that.option("subvalueIndicator") || {}, "subvalueIndicatorType"); if (!target) { target = that._subvalueIndicatorsSet = that._createSubvalueIndicatorsSet() } const isRecreate = settings.type !== target.type; target.type = settings.type; const dummy = that._createIndicator(settings.type, that._renderer.root); if (dummy) { dummy.dispose(); target.render(settings, isRecreate) } }, _setupValue: function(value) { this.__value = processValue(value, this.__value) }, _setupSubvalues: function(subvalues) { const vals = void 0 === subvalues ? this.__subvalues : parseArrayOfNumbers(subvalues); let i; let ii; let list; if (null === vals) { return } for (i = 0, ii = vals.length, list = []; i < ii; ++i) { list.push(processValue(vals[i], this.__subvalues[i])) } this.__subvalues = list }, _updateValueIndicator: function() { this._valueIndicator && this._valueIndicator.value(this.__value, this._noAnimation) }, _updateSubvalueIndicators: function() { this._subvalueIndicatorsSet && this._subvalueIndicatorsSet.values(this.__subvalues, this._noAnimation) }, value: function(arg) { if (void 0 !== arg) { this._changeValue(arg); return this } return this.__value }, subvalues: function(arg) { if (void 0 !== arg) { this._changeSubvalues(arg); return this } return null !== this.__subvalues ? this.__subvalues.slice() : void 0 }, _changeValue: function(value) { this._setupValue(value); this._beginValueChanging(); this._updateValueIndicator(); this._updateExtraElements(); if (this.__value !== this.option("value")) { this.option("value", this.__value) } this._endValueChanging() }, _changeSubvalues: function(subvalues) { if (null !== this.__subvalues) { this._setupSubvalues(subvalues); this._beginValueChanging(); this._updateSubvalueIndicators(); this._updateExtraElements(); this._endValueChanging() } else { this.__subvalues = parseArrayOfNumbers(subvalues); this._setContentSize(); this._renderContent() } if (!_compareArrays(this.__subvalues, this.option("subvalues"))) { this.option("subvalues", this.__subvalues) } }, _optionChangesMap: { scale: "DOMAIN", rangeContainer: "MOSTLY_TOTAL", valueIndicator: "MOSTLY_TOTAL", subvalueIndicator: "MOSTLY_TOTAL", containerBackgroundColor: "MOSTLY_TOTAL", value: "VALUE", subvalues: "SUBVALUES", valueIndicators: "MOSTLY_TOTAL" }, _customChangesOrder: ["VALUE", "SUBVALUES"], _change_VALUE: function() { this._changeValue(this.option("value")) }, _change_SUBVALUES: function() { this._changeSubvalues(this.option("subvalues")) }, _applyMainLayout: null, _getElementLayout: null, _createIndicator: function(type, owner, className, trackerType, trackerIndex, _strict) { const indicator = this._factory.createIndicator({ renderer: this._renderer, translator: this._translator, owner: owner, tracker: this._tracker, className: className }, type, _strict); if (indicator) { indicator.type = type; indicator._trackerInfo = { type: trackerType, index: trackerIndex } } return indicator }, _getApproximateScreenRange: null }); function valueGetter(arg) { return arg ? arg.value : null } function setupValues(that, fieldName, optionItems) { const currentValues = that[fieldName]; const newValues = _isArray(optionItems) ? _map(optionItems, valueGetter) : []; let i = 0; const ii = newValues.length; const list = []; for (; i < ii; ++i) { list.push(processValue(newValues[i], currentValues[i])) } that[fieldName] = list } function selectMode(gauge) { if (void 0 === gauge.option("value") && void 0 === gauge.option("subvalues")) { if (void 0 !== gauge.option("valueIndicators")) { disableDefaultMode(gauge); selectHardMode(gauge) } } } function disableDefaultMode(that) { that.value = that.subvalues = _noop; that._setupValue = that._setupSubvalues = that._updateValueIndicator = that._updateSubvalueIndicators = null } function selectHardMode(that) { that._indicatorValues = []; setupValues(that, "_indicatorValues", that.option("valueIndicators")); that._valueIndicators = []; const _applyMostlyTotalChange = that._applyMostlyTotalChange; that._applyMostlyTotalChange = function() { setupValues(this, "_indicatorValues", this.option("valueIndicators")); _applyMostlyTotalChange.call(this) }; that._updateActiveElements = updateActiveElements_hardMode; that._prepareValueIndicators = prepareValueIndicators_hardMode; that._disposeValueIndicators = disposeValueIndicators_hardMode; that._cleanValueIndicators = cleanValueIndicators_hardMode; that.indicatorValue = indicatorValue_hardMode } function updateActiveElements_hardMode() { const that = this; that._valueIndicators.forEach((valueIndicator => { valueIndicator.value(that._indicatorValues[valueIndicator.index], that._noAnimation) })) } function prepareValueIndicators_hardMode() { const that = this; const valueIndicators = that._valueIndicators || []; const userOptions = that.option("valueIndicators"); const optionList = []; let i = 0; let ii; for (ii = _isArray(userOptions) ? userOptions.length : 0; i < ii; ++i) { optionList.push(userOptions[i]) } for (ii = valueIndicators.length; i < ii; ++i) { optionList.push(null) } const newValueIndicators = []; optionList.forEach(((userSettings, i) => { let valueIndicator = valueIndicators[i]; if (!userSettings) { valueIndicator && valueIndicator.dispose(); return } const settings = that._prepareIndicatorSettings(userSettings, "valueIndicatorType"); if (valueIndicator && valueIndicator.type !== settings.type) { valueIndicator.dispose(); valueIndicator = null } if (!valueIndicator) { valueIndicator = that._createIndicator(settings.type, that._renderer.root, "dxg-value-indicator", "value-indicator", i, true) } if (valueIndicator) { valueIndicator.index = i; valueIndicator.render(settings); newValueIndicators.push(valueIndicator) } })); that._valueIndicators = newValueIndicators; return that._valueIndicators } function disposeValueIndicators_hardMode() { this._valueIndicators.forEach((valueIndicator => valueIndicator.dispose())); this._valueIndicators = null } function cleanValueIndicators_hardMode() { this._valueIndicators.forEach((valueIndicator => valueIndicator.clean())) } function indicatorValue_hardMode(index, value) { return accessPointerValue(this, this._valueIndicators, this._indicatorValues, index, value) } function accessPointerValue(that, pointers, values, index, value) { if (void 0 !== value) { if (void 0 !== values[index]) { values[index] = processValue(value, values[index]); pointers[index] && pointers[index].value(values[index]) } return that } else { return values[index] } } function ValueIndicatorsSet(parameters) { this._parameters = parameters; this._indicators = [] } ValueIndicatorsSet.prototype = { constructor: ValueIndicatorsSet, dispose: function() { this._indicators.forEach((indicator => indicator.dispose())); this._parameters = this._options = this._indicators = this._colorPalette = this._palette = null; return this }, clean: function() { this._sample && this._sample.clean().dispose(); this._indicators.forEach((indicator => indicator.clean())); this._sample = this._options = this._palette = null; return this }, render: function(options, isRecreate) { const that = this; that._options = options; that._sample = that._parameters.createIndicator(that.type); that._sample.render(options); that.enabled = that._sample.enabled; that._palette = _isDefined(options.palette) ? that._parameters.createPalette(options.palette) : null; if (that.enabled) { that._generatePalette(that._indicators.length); that._indicators = _map(that._indicators, (function(indicator, i) { if (isRecreate) { indicator.dispose(); indicator = that._parameters.createIndicator(that.type, i) } indicator.render(that._getIndicatorOptions(i)); return indicator })) } return that }, getOffset: function() { return this._sample.getOffset() }, resize: function(layout) { this._layout = layout; this._indicators.forEach((indicator => indicator.resize(layout))); return this }, measure: function(layout) { return this._sample.measure(layout) }, _getIndicatorOptions: function(index) { let result = this._options; if (this._colorPalette) { result = _extend({}, result, { color: this._colorPalette[index] }) } return result }, _generatePalette: function(count) { const that = this; let colors = null; if (that._palette) { that._palette.reset(); colors = that._palette.generateColors(count, { repeat: true }) } that._colorPalette = colors }, _adjustIndicatorsCount: function(count) { const that = this; const indicators = that._indicators; let i; let ii; let indicator; const indicatorsLen = indicators.length; if (indicatorsLen > count) { for (i = count, ii = indicatorsLen; i < ii; ++i) { indicators[i].clean().dispose() } that._indicators = indicators.slice(0, count); that._generatePalette(indicators.length) } else if (indicatorsLen < count) { that._generatePalette(count); for (i = indicatorsLen, ii = count; i < ii; ++i) { indicator = that._parameters.createIndicator(that.type, i); indicator.render(that._getIndicatorOptions(i)).resize(that._layout); indicators.push(indicator) } } }, values: function(arg, _noAnimation) { const that = this; if (!that.enabled) { return } if (void 0 !== arg) { if (!_isArray(arg)) { arg = _isFinite(arg) ? [Number(arg)] : null } if (arg) { that._adjustIndicatorsCount(arg.length); that._indicators.forEach(((indicator, i) => indicator.value(arg[i], _noAnimation))) } return that } return _map(that._indicators, (function(indicator) { return indicator.value() })) } }; export function createIndicatorCreator(indicators) { return function(parameters, type, _strict) { const indicatorType = indicators[_normalizeEnum(type)] || !_strict && indicators._default; return indicatorType ? new indicatorType(parameters) : null } }