import { __assign, __extends } from "tslib"; import { deepMix, find, flatten, get, isArray, isEqual, isFunction, isUndefined, mix } from '@antv/util'; import { Crosshair, HtmlTooltip } from '../../dependents'; import { getAngleByPoint, getDistanceToCenter, isPointInCoordinate } from '../../util/coordinate'; import { polarToCartesian } from '../../util/graphics'; import { findDataByPoint, getTooltipItems } from '../../util/tooltip'; import { Controller } from './base'; // Filter duplicates, use `name`, `color`, `value` and `title` property values as condition function uniq(items) { var uniqItems = []; var _loop_1 = function (index) { var item = items[index]; var result = find(uniqItems, function (subItem) { return (subItem.color === item.color && subItem.name === item.name && subItem.value === item.value && subItem.title === item.title); }); if (!result) { uniqItems.push(item); } }; for (var index = 0; index < items.length; index++) { _loop_1(index); } return uniqItems; } /** @ignore */ var Tooltip = /** @class */ (function (_super) { __extends(Tooltip, _super); function Tooltip() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.isLocked = false; _this.isVisible = true; return _this; } Object.defineProperty(Tooltip.prototype, "name", { get: function () { return 'tooltip'; }, enumerable: false, configurable: true }); Tooltip.prototype.init = function () { }; Tooltip.prototype.render = function () { var option = this.view.getOptions().tooltip; this.isVisible = option !== false; }; /** * Shows tooltip * @param point */ Tooltip.prototype.showTooltip = function (point) { this.point = point; if (!this.isVisible) { // 如果设置 tooltip(false) 则始终不显示 return; } var view = this.view; var items = this.getTooltipItems(point); if (!items.length) { // 无内容则不展示,同时 tooltip 需要隐藏 this.hideTooltip(); return; } var title = this.getTitle(items); var dataPoint = { x: items[0].x, y: items[0].y, }; // 数据点位置 view.emit('tooltip:show', __assign({ items: items, title: title }, point)); var cfg = this.getTooltipCfg(); var follow = cfg.follow, showMarkers = cfg.showMarkers, showCrosshairs = cfg.showCrosshairs, showContent = cfg.showContent, marker = cfg.marker; var lastItems = this.items; var lastTitle = this.title; if (!isEqual(lastTitle, title) || !isEqual(lastItems, items)) { // 内容发生变化了更新 tooltip view.emit('tooltip:change', __assign({ items: items, title: title }, point)); if (showContent) { // 展示 tooltip 内容框才渲染 tooltip if (!this.tooltip) { // 延迟生成 this.renderTooltip(); } this.tooltip.update(mix({}, cfg, { items: items, title: title, }, follow ? point : {})); this.tooltip.show(); } if (showMarkers) { // 展示 tooltipMarkers,tooltipMarkers 跟随数据 this.renderTooltipMarkers(items, marker); } } else { // 内容未发生变化,则更新位置 if (this.tooltip && follow) { this.tooltip.update(point); this.tooltip.show(); // tooltip 有可能被隐藏,需要保证显示状态 } if (this.tooltipMarkersGroup) { this.tooltipMarkersGroup.show(); } } this.items = items; this.title = title; if (showCrosshairs) { // 展示 tooltip 辅助线 var isCrosshairsFollowCursor = get(cfg, ['crosshairs', 'follow'], false); // 辅助线是否要跟随鼠标 this.renderCrosshairs(isCrosshairsFollowCursor ? point : dataPoint, cfg); } }; Tooltip.prototype.hideTooltip = function () { var follow = this.getTooltipCfg().follow; if (!follow) { this.point = null; return; } // hide the tooltipMarkers var tooltipMarkersGroup = this.tooltipMarkersGroup; if (tooltipMarkersGroup) { tooltipMarkersGroup.hide(); } // hide crosshairs var xCrosshair = this.xCrosshair; var yCrosshair = this.yCrosshair; if (xCrosshair) { xCrosshair.hide(); } if (yCrosshair) { yCrosshair.hide(); } var tooltip = this.tooltip; if (tooltip) { tooltip.hide(); } this.view.emit('tooltip:hide', {}); this.point = null; }; /** * lockTooltip */ Tooltip.prototype.lockTooltip = function () { this.isLocked = true; if (this.tooltip) { // tooltip contianer 可捕获事件 this.tooltip.setCapture(true); } }; /** * unlockTooltip */ Tooltip.prototype.unlockTooltip = function () { this.isLocked = false; var cfg = this.getTooltipCfg(); if (this.tooltip) { // 重置 capture 属性 this.tooltip.setCapture(cfg.capture); } }; /** * isTooltipLocked */ Tooltip.prototype.isTooltipLocked = function () { return this.isLocked; }; Tooltip.prototype.clear = function () { var _a = this, tooltip = _a.tooltip, xCrosshair = _a.xCrosshair, yCrosshair = _a.yCrosshair, tooltipMarkersGroup = _a.tooltipMarkersGroup; if (tooltip) { tooltip.hide(); tooltip.clear(); } if (xCrosshair) { xCrosshair.clear(); } if (yCrosshair) { yCrosshair.clear(); } if (tooltipMarkersGroup) { tooltipMarkersGroup.clear(); } }; Tooltip.prototype.destroy = function () { if (this.tooltip) { this.tooltip.destroy(); } if (this.xCrosshair) { this.xCrosshair.destroy(); } if (this.yCrosshair) { this.yCrosshair.destroy(); } if (this.guideGroup) { this.guideGroup.remove(true); } this.items = null; this.title = null; this.tooltipMarkersGroup = null; this.tooltipCrosshairsGroup = null; this.xCrosshair = null; this.yCrosshair = null; this.tooltip = null; this.guideGroup = null; this.isLocked = false; this.point = null; }; Tooltip.prototype.changeVisible = function (visible) { if (this.visible === visible) { return; } var _a = this, tooltip = _a.tooltip, tooltipMarkersGroup = _a.tooltipMarkersGroup, xCrosshair = _a.xCrosshair, yCrosshair = _a.yCrosshair; if (visible) { if (tooltip) { tooltip.show(); } if (tooltipMarkersGroup) { tooltipMarkersGroup.show(); } if (xCrosshair) { xCrosshair.show(); } if (yCrosshair) { yCrosshair.show(); } } else { if (tooltip) { tooltip.hide(); } if (tooltipMarkersGroup) { tooltipMarkersGroup.hide(); } if (xCrosshair) { xCrosshair.hide(); } if (yCrosshair) { yCrosshair.hide(); } } this.visible = visible; }; Tooltip.prototype.getTooltipItems = function (point) { var items = this.findItemsFromView(this.view, point); if (items.length) { // 三层 items = flatten(items); for (var _i = 0, items_1 = items; _i < items_1.length; _i++) { var itemArr = items_1[_i]; for (var _a = 0, itemArr_1 = itemArr; _a < itemArr_1.length; _a++) { var item = itemArr_1[_a]; var _b = item.mappingData, x = _b.x, y = _b.y; item.x = isArray(x) ? x[x.length - 1] : x; item.y = isArray(y) ? y[y.length - 1] : y; } } var shared = this.getTooltipCfg().shared; // shared: false 代表只显示当前拾取到的 shape 的数据,但是一个 view 会有多个 Geometry,所以有可能会拾取到多个 shape if (shared === false && items.length > 1) { var snapItem = items[0]; var min = Math.abs(point.y - snapItem[0].y); for (var _c = 0, items_2 = items; _c < items_2.length; _c++) { var aItem = items_2[_c]; var yDistance = Math.abs(point.y - aItem[0].y); if (yDistance <= min) { snapItem = aItem; min = yDistance; } } items = [snapItem]; } return uniq(flatten(items)); } return []; }; Tooltip.prototype.layout = function () { }; Tooltip.prototype.update = function () { if (this.point) { this.showTooltip(this.point); } }; // 获取 tooltip 配置,因为用户可能会通过 view.tooltip() 重新配置 tooltip,所以就不做缓存,每次直接读取 Tooltip.prototype.getTooltipCfg = function () { var view = this.view; var option = view.getOptions().tooltip; var theme = view.getTheme(); var defaultCfg = get(theme, ['components', 'tooltip'], {}); var enterable = isUndefined(get(option, 'enterable')) ? defaultCfg.enterable : get(option, 'enterable'); return deepMix({}, defaultCfg, option, { capture: enterable || this.isLocked ? true : false, }); }; Tooltip.prototype.getTitle = function (items) { var title = items[0].title || items[0].name; this.title = title; return title; }; Tooltip.prototype.renderTooltip = function () { var canvas = this.view.getCanvas(); var region = { start: { x: 0, y: 0 }, end: { x: canvas.get('width'), y: canvas.get('height') }, }; var cfg = this.getTooltipCfg(); var tooltip = new HtmlTooltip(__assign(__assign({ parent: canvas.get('el').parentNode, region: region }, cfg), { visible: false, crosshairs: null })); tooltip.init(); this.tooltip = tooltip; }; Tooltip.prototype.renderTooltipMarkers = function (items, marker) { var tooltipMarkersGroup = this.getTooltipMarkersGroup(); for (var _i = 0, items_3 = items; _i < items_3.length; _i++) { var item = items_3[_i]; var x = item.x, y = item.y; var attrs = __assign(__assign({ fill: item.color, symbol: 'circle', shadowColor: item.color }, marker), { x: x, y: y }); tooltipMarkersGroup.addShape('marker', { attrs: attrs, }); } }; Tooltip.prototype.renderCrosshairs = function (point, cfg) { var crosshairsType = get(cfg, ['crosshairs', 'type'], 'x'); // 默认展示 x 轴上的辅助线 if (crosshairsType === 'x') { if (this.yCrosshair) { this.yCrosshair.hide(); } this.renderXCrosshairs(point, cfg); } else if (crosshairsType === 'y') { if (this.xCrosshair) { this.xCrosshair.hide(); } this.renderYCrosshairs(point, cfg); } else if (crosshairsType === 'xy') { this.renderXCrosshairs(point, cfg); this.renderYCrosshairs(point, cfg); } }; // 渲染 x 轴上的 tooltip 辅助线 Tooltip.prototype.renderXCrosshairs = function (point, tooltipCfg) { var coordinate = this.getViewWithGeometry(this.view).getCoordinate(); if (!isPointInCoordinate(coordinate, point)) { return; } var start; var end; if (coordinate.isRect) { if (coordinate.isTransposed) { start = { x: coordinate.start.x, y: point.y, }; end = { x: coordinate.end.x, y: point.y, }; } else { start = { x: point.x, y: coordinate.end.y, }; end = { x: point.x, y: coordinate.start.y, }; } } else { // 极坐标下 x 轴上的 crosshairs 表现为半径 var angle = getAngleByPoint(coordinate, point); var center = coordinate.getCenter(); var radius = coordinate.getRadius(); end = polarToCartesian(center.x, center.y, radius, angle); start = center; } var cfg = deepMix({ start: start, end: end, container: this.getTooltipCrosshairsGroup(), }, get(tooltipCfg, 'crosshairs', {}), this.getCrosshairsText('x', point, tooltipCfg)); delete cfg.type; // 与 Crosshairs 组件的 type 冲突故删除 var xCrosshair = this.xCrosshair; if (xCrosshair) { xCrosshair.update(cfg); } else { xCrosshair = new Crosshair.Line(cfg); xCrosshair.init(); } xCrosshair.render(); xCrosshair.show(); this.xCrosshair = xCrosshair; }; // 渲染 y 轴上的辅助线 Tooltip.prototype.renderYCrosshairs = function (point, tooltipCfg) { var coordinate = this.getViewWithGeometry(this.view).getCoordinate(); if (!isPointInCoordinate(coordinate, point)) { return; } var cfg; var type; if (coordinate.isRect) { var start = void 0; var end = void 0; if (coordinate.isTransposed) { start = { x: point.x, y: coordinate.end.y, }; end = { x: point.x, y: coordinate.start.y, }; } else { start = { x: coordinate.start.x, y: point.y, }; end = { x: coordinate.end.x, y: point.y, }; } cfg = { start: start, end: end, }; type = 'Line'; } else { // 极坐标下 y 轴上的 crosshairs 表现为圆弧 cfg = { center: coordinate.getCenter(), // @ts-ignore radius: getDistanceToCenter(coordinate, point), startAngle: coordinate.startAngle, endAngle: coordinate.endAngle, }; type = 'Circle'; } cfg = deepMix({ container: this.getTooltipCrosshairsGroup() }, cfg, get(tooltipCfg, 'crosshairs', {}), this.getCrosshairsText('y', point, tooltipCfg)); delete cfg.type; // 与 Crosshairs 组件的 type 冲突故删除 var yCrosshair = this.yCrosshair; if (yCrosshair) { // 如果坐标系发生直角坐标系与极坐标的切换操作 if ((coordinate.isRect && yCrosshair.get('type') === 'circle') || (!coordinate.isRect && yCrosshair.get('type') === 'line')) { yCrosshair = new Crosshair[type](cfg); yCrosshair.init(); } else { yCrosshair.update(cfg); } } else { yCrosshair = new Crosshair[type](cfg); yCrosshair.init(); } yCrosshair.render(); yCrosshair.show(); this.yCrosshair = yCrosshair; }; Tooltip.prototype.getCrosshairsText = function (type, point, tooltipCfg) { var textCfg = get(tooltipCfg, ['crosshairs', 'text']); var follow = get(tooltipCfg, ['crosshairs', 'follow']); var items = this.items; if (textCfg) { var view = this.getViewWithGeometry(this.view); // 需要展示文本 var firstItem = items[0]; var xScale = view.getXScale(); var yScale = view.getYScales()[0]; var xValue = void 0; var yValue = void 0; if (follow) { // 如果需要跟随鼠标移动,就需要将当前鼠标坐标点转换为对应的数值 var invertPoint = this.view.getCoordinate().invert(point); xValue = xScale.invert(invertPoint.x); // 转换为原始值 yValue = yScale.invert(invertPoint.y); // 转换为原始值 } else { xValue = firstItem.data[xScale.field]; yValue = firstItem.data[yScale.field]; } var content = type === 'x' ? xValue : yValue; if (isFunction(textCfg)) { textCfg = textCfg(type, content, items, point); } else { textCfg.content = content; } return { text: textCfg, }; } }; // 获取存储 tooltipMarkers 和 crosshairs 的容器 Tooltip.prototype.getGuideGroup = function () { if (!this.guideGroup) { var foregroundGroup = this.view.foregroundGroup; this.guideGroup = foregroundGroup.addGroup({ name: 'tooltipGuide', capture: false, }); } return this.guideGroup; }; // 获取 tooltipMarkers 存储的容器 Tooltip.prototype.getTooltipMarkersGroup = function () { var tooltipMarkersGroup = this.tooltipMarkersGroup; if (tooltipMarkersGroup && !tooltipMarkersGroup.destroyed) { tooltipMarkersGroup.clear(); tooltipMarkersGroup.show(); } else { tooltipMarkersGroup = this.getGuideGroup().addGroup({ name: 'tooltipMarkersGroup', }); tooltipMarkersGroup.toFront(); this.tooltipMarkersGroup = tooltipMarkersGroup; } return tooltipMarkersGroup; }; // 获取 tooltip crosshairs 存储的容器 Tooltip.prototype.getTooltipCrosshairsGroup = function () { var tooltipCrosshairsGroup = this.tooltipCrosshairsGroup; if (!tooltipCrosshairsGroup) { tooltipCrosshairsGroup = this.getGuideGroup().addGroup({ name: 'tooltipCrosshairsGroup', capture: false, }); tooltipCrosshairsGroup.toBack(); this.tooltipCrosshairsGroup = tooltipCrosshairsGroup; } return tooltipCrosshairsGroup; }; Tooltip.prototype.getTooltipItemsByHitShape = function (geometry, point, title) { var result = []; var container = geometry.container; var shape = container.getShape(point.x, point.y); if (shape && shape.get('visible') && shape.get('origin')) { var mappingData = shape.get('origin').mappingData; var items = getTooltipItems(mappingData, geometry, title); if (items.length) { result.push(items); } } return result; }; Tooltip.prototype.getTooltipItemsByFindData = function (geometry, point, title) { var result = []; var dataArray = geometry.dataArray; geometry.sort(dataArray); // 先进行排序,便于 tooltip 查找 for (var _i = 0, dataArray_1 = dataArray; _i < dataArray_1.length; _i++) { var data = dataArray_1[_i]; var record = findDataByPoint(point, data, geometry); if (record) { var elementId = geometry.getElementId(record); var element = geometry.elementsMap[elementId]; if (geometry.type === 'heatmap' || element.visible) { // Heatmap 没有 Element // 如果图形元素隐藏了,怎不再 tooltip 上展示相关数据 var items = getTooltipItems(record, geometry, title); if (items.length) { result.push(items); } } } } return result; }; Tooltip.prototype.findItemsFromView = function (view, point) { if (view.getOptions().tooltip === false) { // 如果 view 关闭了 tooltip return []; } var result = []; // 先从 view 本身查找 var geometries = view.geometries; var _a = this.getTooltipCfg(), shared = _a.shared, title = _a.title; for (var _i = 0, geometries_1 = geometries; _i < geometries_1.length; _i++) { var geometry = geometries_1[_i]; if (geometry.visible && geometry.tooltipOption !== false) { // geometry 可见同时未关闭 tooltip var geometryType = geometry.type; var tooltipItems = void 0; if (['point', 'edge', 'polygon'].includes(geometryType)) { // 始终通过图形拾取 tooltipItems = this.getTooltipItemsByHitShape(geometry, point, title); } else if (['area', 'line', 'path', 'heatmap'].includes(geometryType)) { // 如果是 'area', 'line', 'path',始终通过数据查找方法查找 tooltip tooltipItems = this.getTooltipItemsByFindData(geometry, point, title); } else { if (shared !== false) { tooltipItems = this.getTooltipItemsByFindData(geometry, point, title); } else { tooltipItems = this.getTooltipItemsByHitShape(geometry, point, title); } } if (tooltipItems.length) { // geometry 有可能会有多个 item,因为用户可以设置 geometry.tooltip('x*y*z') result.push(tooltipItems); } } } // 递归查找,并合并结果 for (var _b = 0, _c = view.views; _b < _c.length; _b++) { var childView = _c[_b]; result = result.concat(this.findItemsFromView(childView, point)); } return result; }; // FIXME: hack 方法 // 因为 tooltip 的交互是挂载在 Chart 上,所以当chart 上没有绘制 Geometry 的时候,就查找不到数据,并且绘图区域同子 View 的区域不同 Tooltip.prototype.getViewWithGeometry = function (view) { var _this = this; if (view.geometries.length) { return view; } return find(view.views, function (childView) { return _this.getViewWithGeometry(childView); }); }; return Tooltip; }(Controller)); export default Tooltip; //# sourceMappingURL=tooltip.js.map