UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

320 lines (316 loc) • 12.7 kB
/** * DevExtreme (viz/chart_components/crosshair.js) * Version: 18.2.18 * Build date: Tue Oct 18 2022 * * Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "use strict"; var math = Math, mathAbs = math.abs, mathMin = math.min, mathMax = math.max, mathFloor = math.floor, vizUtils = require("../core/utils"), extend = require("../../core/utils/extend").extend, HORIZONTAL = "horizontal", VERTICAL = "vertical", LABEL_BACKGROUND_PADDING_X = 8, LABEL_BACKGROUND_PADDING_Y = 4, CENTER = "center", RIGHT = "right", LEFT = "left", TOP = "top", BOTTOM = "bottom"; exports.getMargins = function() { 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 }, rectangleBBox = getRectangleBBox(bBox), 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 } } function Crosshair(renderer, options, params, group) { var that = this; that._renderer = renderer; that._crosshairGroup = group; that._options = {}; that.update(options, params) } Crosshair.prototype = { constructor: Crosshair, update: function(options, params) { var that = this, canvas = params.canvas; that._canvas = { top: canvas.top, bottom: canvas.height - canvas.bottom, left: canvas.left, right: canvas.width - canvas.right, width: canvas.width, height: canvas.height }; that._axes = params.axes; that._panes = params.panes; that._prepareOptions(options, HORIZONTAL); that._prepareOptions(options, VERTICAL) }, dispose: function() { var that = this; that._renderer = that._crosshairGroup = that._options = that._axes = that._canvas = that._horizontalGroup = that._verticalGroup = that._horizontal = that._vertical = that._circle = that._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 = [], canvas = this._canvas, 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 that = this, renderer = that._renderer, options = that._options, verticalOptions = options.vertical, horizontalOptions = options.horizontal, extraOptions = horizontalOptions.visible ? horizontalOptions.line : verticalOptions.line, circleOptions = { stroke: extraOptions.stroke, "stroke-width": extraOptions["stroke-width"], dashStyle: extraOptions.dashStyle, opacity: extraOptions.opacity }, canvas = that._canvas; that._horizontal = {}; that._vertical = {}; that._circle = renderer.circle(canvas.left, canvas.top, 0).attr(circleOptions).append(that._crosshairGroup); that._horizontalGroup = renderer.g().append(that._crosshairGroup); that._verticalGroup = renderer.g().append(that._crosshairGroup); if (verticalOptions.visible) { that._vertical.lines = that._createLines(verticalOptions.line, "h", that._verticalGroup); that._vertical.labels = that._createLabels(that._axes[0], verticalOptions, false, that._verticalGroup) } if (horizontalOptions.visible) { that._horizontal.lines = that._createLines(horizontalOptions.line, "v", that._horizontalGroup); that._horizontal.labels = that._createLabels(that._axes[1], horizontalOptions, true, that._horizontalGroup) } that.hide() }, _createLabels: function(axes, options, isHorizontal, group) { var x, y, text, background, currentLabelPos, that = this, canvas = that._canvas, renderer = that._renderer, labels = [], labelOptions = options.label; if (labelOptions.visible) { axes.forEach(function(axis) { var align, 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 } 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(vizUtils.patchFontOptions(options.label.font)).attr({ align: align }).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, coord = label.startXY, textElement = label.text, backgroundElement = label.background, 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, labelCoord = label.pos.coord, coords = this._linesCanvas, 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, canvas = this._canvas, 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] }) } }, _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 i, coords, panes = this._panes; 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 that = this, point = data.point, pointData = point.getCrosshairData(data.x, data.y), r = point.getPointRadius(), horizontal = that._horizontal, vertical = that._vertical, rad = !r ? 0 : r + 3, canvas = that._canvas, x = mathFloor(pointData.x), y = mathFloor(pointData.y); if (x >= canvas.left && x <= canvas.right && y >= canvas.top && y <= canvas.bottom) { that._crosshairGroup.attr({ visibility: "visible" }); that._resetLinesCanvas(); that._circle.attr({ cx: x, cy: y, r: rad, "clip-path": that._getClipRectForPane(x, y).id }); if (horizontal.lines) { that._updateText(pointData.yValue, pointData.axis, horizontal.labels, point, getLabelCheckerPosition(x, y, true, canvas)); that._updateLines(horizontal.lines, x, y, rad, true); that._horizontalGroup.attr({ translateY: y - canvas.top }) } if (vertical.lines) { that._updateText(pointData.xValue, pointData.axis, vertical.labels, point, getLabelCheckerPosition(x, y, false, canvas)); that._updateLines(vertical.lines, x, y, rad, false); that._verticalGroup.attr({ translateX: x - canvas.left }) } } else { that.hide() } } }; exports.Crosshair = Crosshair;