devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
622 lines (616 loc) • 25.5 kB
JavaScript
/**
* DevExtreme (cjs/viz/series/points/symbol_point.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/
*/
"use strict";
exports.default = void 0;
var _extend2 = require("../../../core/utils/extend");
var _iterator = require("../../../core/utils/iterator");
var _common = require("../../../core/utils/common");
var _window = require("../../../core/utils/window");
var _label = require("./label");
var _type = require("../../../core/utils/type");
var _utils = require("../../core/utils");
const window = (0, _window.getWindow)();
const _extend = _extend2.extend;
const _math = Math;
const _round = _math.round;
const _floor = _math.floor;
const _ceil = _math.ceil;
const DEFAULT_IMAGE_WIDTH = 20;
const DEFAULT_IMAGE_HEIGHT = 20;
const LABEL_OFFSET = 10;
const CANVAS_POSITION_DEFAULT = "canvas_position_default";
function getSquareMarkerCoords(radius) {
return [-radius, -radius, radius, -radius, radius, radius, -radius, radius, -radius, -radius]
}
function getPolygonMarkerCoords(radius) {
const r = _ceil(radius);
return [-r, 0, 0, -r, r, 0, 0, r, -r, 0]
}
function getCrossMarkerCoords(radius) {
const r = _ceil(radius);
const floorHalfRadius = _floor(r / 2);
const ceilHalfRadius = _ceil(r / 2);
return [-r, -floorHalfRadius, -floorHalfRadius, -r, 0, -ceilHalfRadius, floorHalfRadius, -r, r, -floorHalfRadius, ceilHalfRadius, 0, r, floorHalfRadius, floorHalfRadius, r, 0, ceilHalfRadius, -floorHalfRadius, r, -r, floorHalfRadius, -ceilHalfRadius, 0]
}
function getTriangleDownMarkerCoords(radius) {
return [-radius, -radius, radius, -radius, 0, radius, -radius, -radius]
}
function getTriangleUpMarkerCoords(radius) {
return [-radius, radius, radius, radius, 0, -radius, -radius, radius]
}
var _default = exports.default = {
deleteLabel: function() {
this._label.dispose();
this._label = null
},
_hasGraphic: function() {
return this.graphic
},
clearVisibility: function() {
const graphic = this.graphic;
if (graphic && graphic.attr("visibility")) {
graphic.attr({
visibility: null
})
}
},
isVisible: function() {
return this.inVisibleArea && this.series.isVisible()
},
setInvisibility: function() {
const graphic = this.graphic;
if (graphic && "hidden" !== graphic.attr("visibility")) {
graphic.attr({
visibility: "hidden"
})
}
this._errorBar && this._errorBar.attr({
visibility: "hidden"
});
this._label.draw(false)
},
clearMarker: function() {
const graphic = this.graphic;
graphic && graphic.attr(this._emptySettings)
},
_createLabel: function() {
this._label = new _label.Label({
renderer: this.series._renderer,
labelsGroup: this.series._labelsGroup,
point: this
})
},
_calculateVisibility: function(x, y) {
const {
minX: minX,
maxX: maxX,
minY: minY,
maxY: maxY
} = this._getVisibleArea();
this.inVisibleArea = minX <= x && maxX >= x && minY <= y && maxY >= y
},
_updateLabelData: function() {
this._label.setData(this._getLabelFormatObject())
},
_updateLabelOptions: function() {
!this._label && this._createLabel();
this._label.setOptions(this._options.label)
},
_checkImage: function(image) {
return (0, _type.isDefined)(image) && ("string" === typeof image || (0, _type.isDefined)(image.url))
},
_fillStyle: function() {
this._styles = this._options.styles
},
_checkSymbol: function(oldOptions, newOptions) {
const oldSymbol = oldOptions.symbol;
const newSymbol = newOptions.symbol;
const symbolChanged = "circle" === oldSymbol && "circle" !== newSymbol || "circle" !== oldSymbol && "circle" === newSymbol;
const imageChanged = this._checkImage(oldOptions.image) !== this._checkImage(newOptions.image);
return !!(symbolChanged || imageChanged)
},
_populatePointShape: function(symbol, radius) {
switch (symbol) {
case "square":
return getSquareMarkerCoords(radius);
case "polygon":
return getPolygonMarkerCoords(radius);
case "triangle":
case "triangleDown":
return getTriangleDownMarkerCoords(radius);
case "triangleUp":
return getTriangleUpMarkerCoords(radius);
case "cross":
return getCrossMarkerCoords(radius)
}
},
hasCoords: function() {
return null !== this.x && null !== this.y
},
correctValue: function(correction) {
const that = this;
const axis = that.series.getValueAxis();
if (that.hasValue()) {
that.value = that.properValue = axis.validateUnit(that.initialValue.valueOf() + correction.valueOf());
that.minValue = axis.validateUnit(correction)
}
},
resetCorrection: function() {
this.value = this.properValue = this.initialValue;
this.minValue = CANVAS_POSITION_DEFAULT
},
resetValue: function() {
const that = this;
if (that.hasValue()) {
that.value = that.properValue = that.initialValue = 0;
that.minValue = 0;
that._label.setDataField("value", that.value)
}
},
_getTranslates: function(animationEnabled) {
let translateX = this.x;
let translateY = this.y;
if (animationEnabled) {
if (this._options.rotated) {
translateX = this.defaultX
} else {
translateY = this.defaultY
}
}
return {
x: translateX,
y: translateY
}
},
_createImageMarker: function(renderer, settings, options) {
const width = options.width || 20;
const height = options.height || 20;
return renderer.image(-_round(.5 * width), -_round(.5 * height), width, height, options.url ? options.url.toString() : options.toString(), "center").attr({
translateX: settings.translateX,
translateY: settings.translateY,
visibility: settings.visibility
})
},
_createSymbolMarker: function(renderer, pointSettings) {
let marker;
const symbol = this._options.symbol;
if ("circle" === symbol) {
delete pointSettings.points;
marker = renderer.circle().attr(pointSettings)
} else if ("square" === symbol || "polygon" === symbol || "triangle" === symbol || "triangleDown" === symbol || "triangleUp" === symbol || "cross" === symbol) {
marker = renderer.path([], "area").attr(pointSettings).sharp()
}
return marker
},
_createMarker: function(renderer, group, image, settings) {
const that = this;
const marker = that._checkImage(image) ? that._createImageMarker(renderer, settings, image) : that._createSymbolMarker(renderer, settings);
if (marker) {
marker.data({
"chart-data-point": that
}).append(group)
}
return marker
},
_getSymbolBBox: function(x, y, r) {
return {
x: x - r,
y: y - r,
width: 2 * r,
height: 2 * r
}
},
_getImageBBox: function(x, y) {
const image = this._options.image;
const width = image.width || 20;
const height = image.height || 20;
return {
x: x - _round(width / 2),
y: y - _round(height / 2),
width: width,
height: height
}
},
_getGraphicBBox: function() {
const that = this;
const options = that._options;
const x = that.x;
const y = that.y;
let bBox;
if (options.visible) {
bBox = that._checkImage(options.image) ? that._getImageBBox(x, y) : that._getSymbolBBox(x, y, options.styles.normal.r)
} else {
bBox = {
x: x,
y: y,
width: 0,
height: 0
}
}
return bBox
},
hideInsideLabel: _common.noop,
_getShiftLabelCoords: function(label) {
const coord = this._addLabelAlignmentAndOffset(label, this._getLabelCoords(label));
return this._checkLabelPosition(label, coord)
},
_drawLabel: function() {
const customVisibility = this._getCustomLabelVisibility();
const label = this._label;
const isVisible = this._showForZeroValues() && this.hasValue() && false !== customVisibility && (this.series.getLabelVisibility() || customVisibility);
label.draw(!!isVisible)
},
correctLabelPosition: function(label) {
const that = this;
const coord = that._getShiftLabelCoords(label);
if (!that.hideInsideLabel(label, coord)) {
label.setFigureToDrawConnector(that._getLabelConnector(label.pointPosition));
label.shift(_round(coord.x), _round(coord.y))
}
},
_showForZeroValues: function() {
return true
},
_getLabelConnector: function(pointPosition) {
const bBox = this._getGraphicBBox(pointPosition);
const w2 = bBox.width / 2;
const h2 = bBox.height / 2;
return {
x: bBox.x + w2,
y: bBox.y + h2,
r: this._options.visible ? Math.max(w2, h2) : 0
}
},
_getPositionFromLocation: function() {
return {
x: this.x,
y: this.y
}
},
_isPointInVisibleArea: function(visibleArea, graphicBBox) {
return visibleArea.minX <= graphicBBox.x + graphicBBox.width && visibleArea.maxX >= graphicBBox.x && visibleArea.minY <= graphicBBox.y + graphicBBox.height && visibleArea.maxY >= graphicBBox.y
},
_checkLabelPosition: function(label, coord) {
const that = this;
const visibleArea = that._getVisibleArea();
const labelBBox = label.getBoundingRect();
const graphicBBox = that._getGraphicBBox(label.pointPosition);
const fullGraphicBBox = that._getGraphicBBox();
const isInside = "inside" === label.getLayoutOptions().position;
if (that._isPointInVisibleArea(visibleArea, fullGraphicBBox)) {
if (!that._options.rotated) {
if (visibleArea.minX > coord.x) {
coord.x = visibleArea.minX
}
if (visibleArea.maxX < coord.x + labelBBox.width) {
coord.x = visibleArea.maxX - labelBBox.width
}
if (visibleArea.minY > coord.y) {
coord.y = isInside ? visibleArea.minY : graphicBBox.y + graphicBBox.height + 10
}
if (visibleArea.maxY < coord.y + labelBBox.height) {
coord.y = isInside ? visibleArea.maxY - labelBBox.height : graphicBBox.y - labelBBox.height - 10
}
} else {
if (visibleArea.minX > coord.x) {
coord.x = isInside ? visibleArea.minX : graphicBBox.x + graphicBBox.width + 10
}
if (visibleArea.maxX < coord.x + labelBBox.width) {
coord.x = isInside ? visibleArea.maxX - labelBBox.width : graphicBBox.x - 10 - labelBBox.width
}
if (visibleArea.minY > coord.y) {
coord.y = visibleArea.minY
}
if (visibleArea.maxY < coord.y + labelBBox.height) {
coord.y = visibleArea.maxY - labelBBox.height
}
}
}
return coord
},
_addLabelAlignmentAndOffset: function(label, coord) {
const labelBBox = label.getBoundingRect();
const labelOptions = label.getLayoutOptions();
if (!this._options.rotated) {
if ("left" === labelOptions.alignment) {
coord.x += labelBBox.width / 2
} else if ("right" === labelOptions.alignment) {
coord.x -= labelBBox.width / 2
}
}
coord.x += labelOptions.horizontalOffset;
coord.y += labelOptions.verticalOffset;
return coord
},
_getLabelCoords: function(label) {
return this._getLabelCoordOfPosition(label, this._getLabelPosition(label.pointPosition))
},
_getLabelCoordOfPosition: function(label, position) {
const labelBBox = label.getBoundingRect();
const graphicBBox = this._getGraphicBBox(label.pointPosition);
const centerY = graphicBBox.height / 2 - labelBBox.height / 2;
const centerX = graphicBBox.width / 2 - labelBBox.width / 2;
let x = graphicBBox.x;
let y = graphicBBox.y;
switch (position) {
case "left":
x -= labelBBox.width + 10;
y += centerY;
break;
case "right":
x += graphicBBox.width + 10;
y += centerY;
break;
case "top":
x += centerX;
y -= labelBBox.height + 10;
break;
case "bottom":
x += centerX;
y += graphicBBox.height + 10;
break;
case "inside":
x += centerX;
y += centerY
}
return {
x: x,
y: y
}
},
_drawMarker: function(renderer, group, animationEnabled) {
const options = this._options;
const translates = this._getTranslates(animationEnabled);
const style = this._getStyle();
this.graphic = this._createMarker(renderer, group, options.image, _extend({
translateX: translates.x,
translateY: translates.y,
points: this._populatePointShape(options.symbol, style.r)
}, style))
},
_getErrorBarSettings: function() {
return {
visibility: "visible"
}
},
_getErrorBarBaseEdgeLength() {
return 2 * this.getPointRadius()
},
_drawErrorBar: function(renderer, group) {
if (!this._options.errorBars) {
return
}
const that = this;
const options = that._options;
const errorBarOptions = options.errorBars;
const points = [];
let settings;
const pos = that._errorBarPos;
let high = that._highErrorCoord;
let low = that._lowErrorCoord;
const displayMode = (0, _utils.normalizeEnum)(errorBarOptions.displayMode);
const isHighDisplayMode = "high" === displayMode;
const isLowDisplayMode = "low" === displayMode;
const highErrorOnly = (isHighDisplayMode || !(0, _type.isDefined)(low)) && (0, _type.isDefined)(high) && !isLowDisplayMode;
const lowErrorOnly = (isLowDisplayMode || !(0, _type.isDefined)(high)) && (0, _type.isDefined)(low) && !isHighDisplayMode;
let edgeLength = errorBarOptions.edgeLength;
if (edgeLength <= 1 && edgeLength > 0) {
edgeLength = this._getErrorBarBaseEdgeLength() * errorBarOptions.edgeLength
}
edgeLength = _floor(parseInt(edgeLength) / 2);
highErrorOnly && (low = that._baseErrorBarPos);
lowErrorOnly && (high = that._baseErrorBarPos);
if ("none" !== displayMode && (0, _type.isDefined)(high) && (0, _type.isDefined)(low) && (0, _type.isDefined)(pos)) {
!lowErrorOnly && points.push([pos - edgeLength, high, pos + edgeLength, high]);
points.push([pos, high, pos, low]);
!highErrorOnly && points.push([pos + edgeLength, low, pos - edgeLength, low]);
options.rotated && (0, _iterator.each)(points, (function(_, p) {
p.reverse()
}));
settings = that._getErrorBarSettings(errorBarOptions);
if (!that._errorBar) {
that._errorBar = renderer.path(points, "line").attr(settings).append(group)
} else {
settings.points = points;
that._errorBar.attr(settings)
}
} else {
that._errorBar && that._errorBar.attr({
visibility: "hidden"
})
}
},
getTooltipParams: function() {
const graphic = this.graphic;
return {
x: this.x,
y: this.y,
offset: graphic ? graphic.getBBox().height / 2 : 0
}
},
setPercentValue: function(absTotal, total, leftHoleTotal, rightHoleTotal) {
const that = this;
const valuePercent = that.value / absTotal || 0;
const minValuePercent = that.minValue / absTotal || 0;
const percent = valuePercent - minValuePercent;
that._label.setDataField("percent", percent);
that._label.setDataField("total", total);
if (that.series.isFullStackedSeries() && that.hasValue()) {
if (that.leftHole) {
that.leftHole /= absTotal - leftHoleTotal;
that.minLeftHole /= absTotal - leftHoleTotal
}
if (that.rightHole) {
that.rightHole /= absTotal - rightHoleTotal;
that.minRightHole /= absTotal - rightHoleTotal
}
that.value = that.properValue = valuePercent;
that.minValue = !minValuePercent ? that.minValue : minValuePercent
}
},
_storeTrackerR: function() {
let navigator = window.navigator;
const r = this._options.styles.normal.r;
const minTrackerSize = (0, _window.hasProperty)("ontouchstart") || navigator.msPointerEnabled && navigator.msMaxTouchPoints || navigator.pointerEnabled && navigator.maxTouchPoints ? 20 : 6;
this._options.trackerR = r < minTrackerSize ? minTrackerSize : r;
return this._options.trackerR
},
_translateErrorBars: function() {
const options = this._options;
const rotated = options.rotated;
const errorBars = options.errorBars;
const translator = this._getValTranslator();
if (!errorBars) {
return
}(0, _type.isDefined)(this.lowError) && (this._lowErrorCoord = translator.translate(this.lowError));
(0, _type.isDefined)(this.highError) && (this._highErrorCoord = translator.translate(this.highError));
this._errorBarPos = _floor(rotated ? this.vy : this.vx);
this._baseErrorBarPos = "stdDeviation" === errorBars.type ? this._lowErrorCoord + (this._highErrorCoord - this._lowErrorCoord) / 2 : rotated ? this.vx : this.vy
},
_translate: function() {
const that = this;
const valTranslator = that._getValTranslator();
const argTranslator = that._getArgTranslator();
if (that._options.rotated) {
that.vx = that.x = valTranslator.translate(that.value, void 0, true);
that.vy = that.y = argTranslator.translate(that.argument, void 0, true);
that.minX = valTranslator.translate(that.minValue, void 0, true);
that.defaultX = valTranslator.translate(CANVAS_POSITION_DEFAULT)
} else {
that.vy = that.y = valTranslator.translate(that.value, void 0, true);
that.vx = that.x = argTranslator.translate(that.argument, void 0, true);
that.minY = valTranslator.translate(that.minValue, void 0, true);
that.defaultY = valTranslator.translate(CANVAS_POSITION_DEFAULT)
}
that._translateErrorBars();
that._calculateVisibility(that.x, that.y)
},
_updateData: function(data) {
this.value = this.properValue = this.initialValue = this.originalValue = data.value;
this.minValue = this.initialMinValue = this.originalMinValue = (0, _type.isDefined)(data.minValue) ? data.minValue : CANVAS_POSITION_DEFAULT
},
_getImageSettings: function(image) {
return {
href: image.url || image.toString(),
width: image.width || 20,
height: image.height || 20
}
},
getCrosshairData: function() {
const r = this._options.rotated;
const value = this.properValue;
const argument = this.argument;
return {
x: this.vx,
y: this.vy,
xValue: r ? value : argument,
yValue: r ? argument : value,
axis: this.series.axis
}
},
getPointRadius: function() {
const style = this._getStyle();
const options = this._options;
const r = style.r;
let extraSpace;
const symbol = options.symbol;
const isSquare = "square" === symbol;
const isTriangle = "triangle" === symbol || "triangleDown" === symbol || "triangleUp" === symbol;
if (options.visible && !options.image && r) {
extraSpace = style["stroke-width"] / 2;
return (isSquare || isTriangle ? 1.4 * r : r) + extraSpace
}
return 0
},
_updateMarker: function(animationEnabled, style) {
const that = this;
const options = that._options;
let settings;
const image = options.image;
const visibility = !that.isVisible() ? {
visibility: "hidden"
} : {};
if (that._checkImage(image)) {
settings = _extend({}, {
visibility: style.visibility
}, visibility, that._getImageSettings(image))
} else {
settings = _extend({}, style, visibility, {
points: that._populatePointShape(options.symbol, style.r)
})
}
if (!animationEnabled) {
settings.translateX = that.x;
settings.translateY = that.y
}
that.graphic.attr(settings).sharp()
},
_getLabelFormatObject: function() {
return {
argument: this.initialArgument,
value: this.initialValue,
originalArgument: this.originalArgument,
originalValue: this.originalValue,
seriesName: this.series.name,
lowErrorValue: this.lowError,
highErrorValue: this.highError,
point: this
}
},
_getLabelPosition: function() {
const rotated = this._options.rotated;
if (this.initialValue > 0) {
return rotated ? "right" : "top"
} else {
return rotated ? "left" : "bottom"
}
},
_getFormatObject: function(tooltip) {
const labelFormatObject = this._label.getData();
return _extend({}, labelFormatObject, {
argumentText: tooltip.formatValue(this.initialArgument, "argument"),
valueText: tooltip.formatValue(this.initialValue)
}, (0, _type.isDefined)(labelFormatObject.percent) ? {
percentText: tooltip.formatValue(labelFormatObject.percent, "percent")
} : {}, (0, _type.isDefined)(labelFormatObject.total) ? {
totalText: tooltip.formatValue(labelFormatObject.total)
} : {})
},
getMarkerVisibility: function() {
return this._options.visible
},
coordsIn: function(x, y) {
const trackerRadius = this._storeTrackerR();
return x >= this.x - trackerRadius && x <= this.x + trackerRadius && y >= this.y - trackerRadius && y <= this.y + trackerRadius
},
getMinValue: function(noErrorBar) {
const errorBarOptions = this._options.errorBars;
if (errorBarOptions && !noErrorBar) {
const displayMode = errorBarOptions.displayMode;
const lowValue = "high" !== displayMode && (0, _type.isDefined)(this.lowError) ? this.lowError : this.value;
const highValue = "low" !== displayMode && (0, _type.isDefined)(this.highError) ? this.highError : this.value;
return lowValue < highValue ? lowValue : highValue
} else {
return this.value
}
},
getMaxValue: function(noErrorBar) {
const errorBarOptions = this._options.errorBars;
if (errorBarOptions && !noErrorBar) {
const displayMode = errorBarOptions.displayMode;
const lowValue = "high" !== displayMode && (0, _type.isDefined)(this.lowError) ? this.lowError : this.value;
const highValue = "low" !== displayMode && (0, _type.isDefined)(this.highError) ? this.highError : this.value;
return lowValue > highValue ? lowValue : highValue
} else {
return this.value
}
}
};
module.exports = exports.default;
module.exports.default = exports.default;