devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
319 lines (316 loc) • 12.7 kB
JavaScript
/**
* DevExtreme (esm/viz/chart_components/crosshair.js)
* Version: 21.1.4
* Build date: Mon Jun 21 2021
*
* Copyright (c) 2012 - 2021 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
import {
patchFontOptions
} from "../core/utils";
import {
extend
} from "../../core/utils/extend";
var math = Math;
var mathAbs = math.abs;
var mathMin = math.min;
var mathMax = math.max;
var mathFloor = math.floor;
var HORIZONTAL = "horizontal";
var VERTICAL = "vertical";
var LABEL_BACKGROUND_PADDING_X = 8;
var LABEL_BACKGROUND_PADDING_Y = 4;
var CENTER = "center";
var RIGHT = "right";
var LEFT = "left";
var TOP = "top";
var BOTTOM = "bottom";
export function getMargins() {
return {
x: LABEL_BACKGROUND_PADDING_X,
y: LABEL_BACKGROUND_PADDING_Y
}
}
function getRectangleBBox(bBox) {
return {
x: bBox.x - LABEL_BACKGROUND_PADDING_X,
y: bBox.y - LABEL_BACKGROUND_PADDING_Y,
width: bBox.width + 2 * LABEL_BACKGROUND_PADDING_X,
height: bBox.height + 2 * LABEL_BACKGROUND_PADDING_Y
}
}
function getLabelCheckerPosition(x, y, isHorizontal, canvas) {
var params = isHorizontal ? ["x", "width", "y", "height", y, 0] : ["y", "height", "x", "width", x, 1];
return function(bBox, position, coord) {
var labelCoord = {
x: coord.x,
y: coord.y
};
var rectangleBBox = getRectangleBBox(bBox);
var delta = isHorizontal ? coord.y - bBox.y - bBox.height / 2 : coord.y - bBox.y;
labelCoord.y = isHorizontal || !isHorizontal && position === BOTTOM ? coord.y + delta : coord.y;
if (rectangleBBox[params[0]] < 0) {
labelCoord[params[0]] -= rectangleBBox[params[0]]
} else if (rectangleBBox[params[0]] + rectangleBBox[params[1]] + delta * params[5] > canvas[params[1]]) {
labelCoord[params[0]] -= rectangleBBox[params[0]] + rectangleBBox[params[1]] + delta * params[5] - canvas[params[1]]
}
if (params[4] - rectangleBBox[params[3]] / 2 < 0) {
labelCoord[params[2]] -= params[4] - rectangleBBox[params[3]] / 2
} else if (params[4] + rectangleBBox[params[3]] / 2 > canvas[params[3]]) {
labelCoord[params[2]] -= params[4] + rectangleBBox[params[3]] / 2 - canvas[params[3]]
}
return labelCoord
}
}
export function Crosshair(renderer, options, params, group) {
this._renderer = renderer;
this._crosshairGroup = group;
this._options = {};
this.update(options, params)
}
Crosshair.prototype = {
constructor: Crosshair,
update: function(options, params) {
var canvas = params.canvas;
this._canvas = {
top: canvas.top,
bottom: canvas.height - canvas.bottom,
left: canvas.left,
right: canvas.width - canvas.right,
width: canvas.width,
height: canvas.height
};
this._axes = params.axes;
this._panes = params.panes;
this._prepareOptions(options, HORIZONTAL);
this._prepareOptions(options, VERTICAL)
},
dispose: function() {
this._renderer = this._crosshairGroup = this._options = this._axes = this._canvas = this._horizontalGroup = this._verticalGroup = this._horizontal = this._vertical = this._circle = this._panes = null
},
_prepareOptions: function(options, direction) {
var lineOptions = options[direction + "Line"];
this._options[direction] = {
visible: lineOptions.visible,
line: {
stroke: lineOptions.color || options.color,
"stroke-width": lineOptions.width || options.width,
dashStyle: lineOptions.dashStyle || options.dashStyle,
opacity: lineOptions.opacity || options.opacity,
"stroke-linecap": "butt"
},
label: extend(true, {}, options.label, lineOptions.label)
}
},
_createLines: function(options, sharpParam, group) {
var lines = [];
var canvas = this._canvas;
var points = [canvas.left, canvas.top, canvas.left, canvas.top];
for (var i = 0; i < 2; i++) {
lines.push(this._renderer.path(points, "line").attr(options).sharp(sharpParam).append(group))
}
return lines
},
render: function() {
var renderer = this._renderer;
var options = this._options;
var verticalOptions = options.vertical;
var horizontalOptions = options.horizontal;
var extraOptions = horizontalOptions.visible ? horizontalOptions.line : verticalOptions.line;
var circleOptions = {
stroke: extraOptions.stroke,
"stroke-width": extraOptions["stroke-width"],
dashStyle: extraOptions.dashStyle,
opacity: extraOptions.opacity
};
var canvas = this._canvas;
this._horizontal = {};
this._vertical = {};
this._circle = renderer.circle(canvas.left, canvas.top, 0).attr(circleOptions).append(this._crosshairGroup);
this._horizontalGroup = renderer.g().append(this._crosshairGroup);
this._verticalGroup = renderer.g().append(this._crosshairGroup);
if (verticalOptions.visible) {
this._vertical.lines = this._createLines(verticalOptions.line, "h", this._verticalGroup);
this._vertical.labels = this._createLabels(this._axes[0], verticalOptions, false, this._verticalGroup)
}
if (horizontalOptions.visible) {
this._horizontal.lines = this._createLines(horizontalOptions.line, "v", this._horizontalGroup);
this._horizontal.labels = this._createLabels(this._axes[1], horizontalOptions, true, this._horizontalGroup)
}
this.hide()
},
_createLabels: function(axes, options, isHorizontal, group) {
var canvas = this._canvas;
var renderer = this._renderer;
var x;
var y;
var text;
var labels = [];
var background;
var currentLabelPos;
var labelOptions = options.label;
if (labelOptions.visible) {
axes.forEach((function(axis) {
var position = axis.getOptions().position;
if (axis.getTranslator().getBusinessRange().isEmpty()) {
return
}
currentLabelPos = axis.getLabelsPosition();
if (isHorizontal) {
y = canvas.top;
x = currentLabelPos
} else {
x = canvas.left;
y = currentLabelPos
}
var align = position === TOP || position === BOTTOM ? CENTER : position === RIGHT ? LEFT : RIGHT;
background = renderer.rect(0, 0, 0, 0).attr({
fill: labelOptions.backgroundColor || options.line.stroke
}).append(group);
text = renderer.text("0", 0, 0).css(patchFontOptions(options.label.font)).attr({
align: align,
class: labelOptions.cssClass
}).append(group);
labels.push({
text: text,
background: background,
axis: axis,
options: labelOptions,
pos: {
coord: currentLabelPos,
side: position
},
startXY: {
x: x,
y: y
}
})
}))
}
return labels
},
_updateText: function(value, axisName, labels, point, func) {
var that = this;
labels.forEach((function(label) {
var axis = label.axis;
var coord = label.startXY;
var textElement = label.text;
var backgroundElement = label.background;
var text = "";
if (!axis.name || axis.name === axisName) {
text = axis.getFormattedValue(value, label.options, point)
}
if (text) {
textElement.attr({
text: text,
x: coord.x,
y: coord.y
});
textElement.attr(func(textElement.getBBox(), label.pos.side, coord));
that._updateLinesCanvas(label);
backgroundElement.attr(getRectangleBBox(textElement.getBBox()))
} else {
textElement.attr({
text: ""
});
backgroundElement.attr({
x: 0,
y: 0,
width: 0,
height: 0
})
}
}))
},
hide: function() {
this._crosshairGroup.attr({
visibility: "hidden"
})
},
_updateLinesCanvas: function(label) {
var position = label.pos.side;
var labelCoord = label.pos.coord;
var coords = this._linesCanvas;
var canvas = this._canvas;
coords[position] = coords[position] !== canvas[position] && mathAbs(coords[position] - canvas[position]) < mathAbs(labelCoord - canvas[position]) ? coords[position] : labelCoord
},
_updateLines: function(lines, x, y, r, isHorizontal) {
var coords = this._linesCanvas;
var canvas = this._canvas;
var points = isHorizontal ? [
[mathMin(x - r, coords.left), canvas.top, x - r, canvas.top],
[x + r, canvas.top, mathMax(coords.right, x + r), canvas.top]
] : [
[canvas.left, mathMin(coords.top, y - r), canvas.left, y - r],
[canvas.left, y + r, canvas.left, mathMax(coords.bottom, y + r)]
];
for (var i = 0; i < 2; i++) {
lines[i].attr({
points: points[i]
}).sharp(isHorizontal ? "v" : "h", isHorizontal ? y === canvas.bottom ? -1 : 1 : x === canvas.right ? -1 : 1)
}
},
_resetLinesCanvas: function() {
var canvas = this._canvas;
this._linesCanvas = {
left: canvas.left,
right: canvas.right,
top: canvas.top,
bottom: canvas.bottom
}
},
_getClipRectForPane: function(x, y) {
var panes = this._panes;
var i;
var coords;
for (i = 0; i < panes.length; i++) {
coords = panes[i].coords;
if (coords.left <= x && coords.right >= x && coords.top <= y && coords.bottom >= y) {
return panes[i].clipRect
}
}
return {
id: null
}
},
show: function(data) {
var point = data.point;
var pointData = point.getCrosshairData(data.x, data.y);
var r = point.getPointRadius();
var horizontal = this._horizontal;
var vertical = this._vertical;
var rad = !r ? 0 : r + 3;
var canvas = this._canvas;
var x = mathFloor(pointData.x);
var y = mathFloor(pointData.y);
if (x >= canvas.left && x <= canvas.right && y >= canvas.top && y <= canvas.bottom) {
this._crosshairGroup.attr({
visibility: "visible"
});
this._resetLinesCanvas();
this._circle.attr({
cx: x,
cy: y,
r: rad,
"clip-path": this._getClipRectForPane(x, y).id
});
if (horizontal.lines) {
this._updateText(pointData.yValue, pointData.axis, horizontal.labels, point, getLabelCheckerPosition(x, y, true, canvas));
this._updateLines(horizontal.lines, x, y, rad, true);
this._horizontalGroup.attr({
translateY: y - canvas.top
})
}
if (vertical.lines) {
this._updateText(pointData.xValue, pointData.axis, vertical.labels, point, getLabelCheckerPosition(x, y, false, canvas));
this._updateLines(vertical.lines, x, y, rad, false);
this._verticalGroup.attr({
translateX: x - canvas.left
})
}
} else {
this.hide()
}
}
};