import { each, head, isEqual, last, get, flatten, isArray, uniq, isNil } from '@antv/util'; import View from '../../chart/view'; import { findItemsFromViewRecurisive } from '../../util/tooltip'; import { IShape, Point, ShapeAttrs } from '../../dependents'; import Element from '../../geometry/element'; import { LooseObject, TooltipCfg } from '../../interface'; import { getAngle, getSectorPath } from '../../util/graphics'; import Action from './base'; const DEFAULT_REGION_PATH_STYLE = { fill: '#CCD6EC', opacity: 0.3, }; export function getItemsOfView(view: View, point: Point, tooltipCfg: TooltipCfg) { let items = findItemsFromViewRecurisive(view, point, tooltipCfg); if (items.length) { // 三层 items = flatten(items); for (const itemArr of items) { for (const item of itemArr) { const { x, y } = item.mappingData; item.x = isArray(x) ? x[x.length - 1] : x; item.y = isArray(y) ? y[y.length - 1] : y; } } const { shared } = tooltipCfg; // shared: false 代表只显示当前拾取到的 shape 的数据,但是一个 view 会有多个 Geometry,所以有可能会拾取到多个 shape if (shared === false && items.length > 1) { let snapItem = items[0]; let min = Math.abs(point.y - snapItem[0].y); for (const aItem of items) { const yDistance = Math.abs(point.y - aItem[0].y); if (yDistance <= min) { snapItem = aItem; min = yDistance; } } items = [snapItem]; } return uniq(flatten(items)); } return []; } /** * 背景框的 Action. 只作用于 interval 和 schema geometry * @ignore */ class ActiveRegion extends Action { private items: any[]; private regionPath: IShape; /** * 显示 * @param {ShapeAttrs} style region-path 的样式 * @param {number} appendRatio 适用于笛卡尔坐标系. 对于 x 轴非 linear 类型: 默认:0.25, x 轴 linear 类型: 默认 0 * @param {number} appendWidth 适用于笛卡尔坐标系. 像素级别,优先级 > appendRatio */ public show(args?: { style: ShapeAttrs; appendRatio?: number; appendWidth?: number }) { const view = this.context.view; const ev = this.context.event; const tooltipCfg = view.getController('tooltip').getTooltipCfg(); const tooltipItems = getItemsOfView( view, { x: ev.x, y: ev.y, }, tooltipCfg ); if (isEqual(tooltipItems, this.items)) { // 如果拾取数据同上次相同,则不重复绘制 return; } this.items = tooltipItems; if (tooltipItems.length) { const xField = view.getXScale().field; const xValue = tooltipItems[0].data[xField]; // 根据 x 对应的值查找 elements let elements: Element[] = []; const geometries = view.geometries; each(geometries, (geometry) => { if (geometry.type === 'interval' || geometry.type === 'schema') { const result = geometry.getElementsBy((ele) => { const eleData = ele.getData(); return eleData[xField] === xValue; }); elements = elements.concat(result); } }); // 根据 bbox 计算背景框的面积区域 if (elements.length) { const coordinate = view.getCoordinate(); let firstBBox = elements[0].shape.getCanvasBBox(); let lastBBox = elements[0].shape.getCanvasBBox(); const groupBBox: LooseObject = firstBBox; each(elements, (ele: Element) => { const bbox = ele.shape.getCanvasBBox(); if (coordinate.isTransposed) { if (bbox.minY < firstBBox.minY) { firstBBox = bbox; } if (bbox.maxY > lastBBox.maxY) { lastBBox = bbox; } } else { if (bbox.minX < firstBBox.minX) { firstBBox = bbox; } if (bbox.maxX > lastBBox.maxX) { lastBBox = bbox; } } groupBBox.x = Math.min(bbox.minX, groupBBox.minX); groupBBox.y = Math.min(bbox.minY, groupBBox.minY); groupBBox.width = Math.max(bbox.maxX, groupBBox.maxX) - groupBBox.x; groupBBox.height = Math.max(bbox.maxY, groupBBox.maxY) - groupBBox.y; }); const { backgroundGroup, coordinateBBox } = view; let path; if (coordinate.isRect) { const xScale = view.getXScale(); let { appendRatio, appendWidth } = args || {}; if (isNil(appendWidth)) { appendRatio = isNil(appendRatio) ? (xScale.isLinear ? 0 : 0.25) : appendRatio; // 如果 x 轴是数值类型,如直方图,默认不需要加额外的宽度 appendWidth = coordinate.isTransposed ? appendRatio * lastBBox.height : appendRatio * firstBBox.width; } let minX: number; let minY: number; let width: number; let height: number; if (coordinate.isTransposed) { minX = coordinateBBox.minX; minY = Math.min(lastBBox.minY, firstBBox.minY) - appendWidth; width = coordinateBBox.width; height = groupBBox.height + appendWidth * 2; } else { minX = Math.min(firstBBox.minX, lastBBox.minX) - appendWidth; // 直角坐标系 非转置:最小值直接取 坐标系 minY minY = coordinateBBox.minY; width = groupBBox.width + appendWidth * 2; height = coordinateBBox.height; } path = [ ['M', minX, minY], ['L', minX + width, minY], ['L', minX + width, minY + height], ['L', minX, minY + height], ['Z'], ]; } else { const firstElement = head(elements); const lastElement = last(elements); const { startAngle } = getAngle(firstElement.getModel(), coordinate); const { endAngle } = getAngle(lastElement.getModel(), coordinate); const center = coordinate.getCenter(); const radius = coordinate.getRadius(); const innterRadius = coordinate.innerRadius * radius; path = getSectorPath(center.x, center.y, radius, startAngle, endAngle, innterRadius); } if (this.regionPath) { this.regionPath.attr('path', path); this.regionPath.show(); } else { const style = get(args, 'style', DEFAULT_REGION_PATH_STYLE); this.regionPath = backgroundGroup.addShape({ type: 'path', name: 'active-region', capture: false, attrs: { ...style, path, }, }); } } } } /** * 隐藏 */ public hide() { if (this.regionPath) { this.regionPath.hide(); } // this.regionPath = null; this.items = null; } /** * 销毁 */ public destroy() { this.hide(); if (this.regionPath) { this.regionPath.remove(true); } super.destroy(); } } export default ActiveRegion;