devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
615 lines (600 loc) • 24.2 kB
JavaScript
/**
* 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
}
}