UNPKG

@visactor/vchart

Version:

charts lib based @visactor/VGrammar

361 lines (345 loc) 23.4 kB
import { ChartEvent } from "../../constant/event"; import { AttributeLevel } from "../../constant/attribute"; import { LayoutZIndex } from "../../constant/layout"; import { BaseComponent } from "../base/base-component"; import { ComponentTypeEnum } from "../interface/type"; import { Brush as BrushComponent, IOperateType as BrushEvent } from "@visactor/vrender-components"; import { array, polygonIntersectPolygon, isValid, last } from "@visactor/vutils"; import { transformToGraphic } from "../../util/style"; import { isEqual } from "@visactor/vutils"; import { Factory } from "../../core/factory"; import { getSpecInfo } from "../util"; import { brush } from "../../theme/builtin/common/component/brush"; const IN_BRUSH_STATE = "inBrush", OUT_BRUSH_STATE = "outOfBrush"; export class Brush extends BaseComponent { constructor() { super(...arguments), this.layoutType = "none", this.type = ComponentTypeEnum.brush, this.name = ComponentTypeEnum.brush, this.specKey = "brush", this.layoutZIndex = LayoutZIndex.Brush, this._linkedSeries = [], this._itemMap = {}, this._linkedItemMap = {}, this._inBrushElementsMap = {}, this._outOfBrushElementsMap = {}, this._linkedInBrushElementsMap = {}, this._linkedOutOfBrushElementsMap = {}, this._cacheInteractiveRangeAttrs = [], this._releatedAxes = [], this._regionAxisMap = {}, this._axisDataZoomMap = {}, this._zoomRecord = []; } static getSpecInfo(chartSpec) { return getSpecInfo(chartSpec, this.specKey, this.type, (s => !1 !== s.visible)); } init() { const inBrushMarkAttr = this._transformBrushedMarkAttr(this._spec.inBrush), outOfBrushMarkAttr = this._transformBrushedMarkAttr(this._spec.outOfBrush); this._option.getAllSeries().forEach((s => { s.getActiveMarks().forEach((m => { m && (s.setMarkStyle(m, Object.assign({}, inBrushMarkAttr), "inBrush", AttributeLevel.Series), s.setMarkStyle(m, Object.assign({}, outOfBrushMarkAttr), "outOfBrush", AttributeLevel.Series)); })); })); } _initNeedOperatedItem() { const seriesUserId = this._spec.seriesId, seriesIndex = this._spec.seriesIndex; this._relativeRegions.forEach((r => { const allMarks = []; r.getSeries().forEach((s => { (seriesUserId && array(seriesUserId).includes(s.userId.toString()) || seriesIndex && array(seriesIndex).includes(s.getSpecIndex()) || !seriesIndex && !seriesUserId) && allMarks.push(...s.getMarksWithoutRoot()), this._itemMap[r.id] = allMarks; })); })), this._linkedSeries.forEach((s => { this._linkedItemMap[s.id] = s.getMarksWithoutRoot(); })); } created() { super.created(), this.initEvent(), this._bindRegions(), this._bindLinkedSeries(), this._initRegionAxisMap(), this._initAxisDataZoomMap(), this._initNeedOperatedItem(); } _bindRegions() { isValid(this._spec.regionId) || isValid(this._spec.regionIndex) ? this._relativeRegions = this._option.getRegionsInUserIdOrIndex(array(this._spec.regionId), array(this._spec.regionIndex)) : this._relativeRegions = this._option.getAllRegions(); } _bindLinkedSeries() { (isValid(this._spec.brushLinkSeriesId) || isValid(this._spec.brushLinkSeriesIndex)) && (this._linkedSeries = this._option.getSeriesInUserIdOrIndex(array(this._spec.brushLinkSeriesId), array(this._spec.brushLinkSeriesIndex))); } _initRegionAxisMap() { isValid(this._spec.axisId) ? array(this._spec.axisId).forEach((axisId => { this._releatedAxes.push(this._option.getComponentByUserId(axisId)); })) : isValid(this._spec.axisIndex) ? array(this._spec.axisIndex).forEach((axisIndex => { this._releatedAxes.push(this._option.getComponentByIndex("axes", axisIndex)); })) : this._releatedAxes = this._option.getComponentsByKey("axes"), this._releatedAxes.forEach((axis => { null == axis || axis.getRegions().forEach((region => { this._regionAxisMap["region_" + region.id] ? this._regionAxisMap["region_" + region.id].push(axis) : this._regionAxisMap["region_" + region.id] = [ axis ]; })); })); } _initAxisDataZoomMap() { this._option.getComponentsByKey("dataZoom").forEach((dz => { dz.relatedAxisComponent && (this._axisDataZoomMap[dz.relatedAxisComponent.id] = dz); })); } initEvent() {} _compareSpec(spec, prevSpec) { this._brushComponents && this._relativeRegions.forEach(((region, index) => { this._updateBrushComponent(region, index); })); const result = super._compareSpec(spec, prevSpec); return isEqual(prevSpec, spec) || (result.reRender = !0, result.reMake = !0), result; } onLayoutEnd() { var _a; if (super.onLayoutEnd(), this._option.disableTriggerEvent) return; (null === (_a = this._spec.visible) || void 0 === _a || _a) && (this._brushComponents ? this._relativeRegions.forEach(((region, index) => { this._updateBrushComponent(region, index); })) : (this._brushComponents = [], this._relativeRegions.forEach(((region, index) => { this._createBrushComponent(region, index); })))); } _updateBrushComponent(region, componentIndex) { const interactiveAttr = this._getBrushInteractiveAttr(region), brushComponent = this._brushComponents[componentIndex]; brushComponent.setAttributes(interactiveAttr), this._initMarkBrushState(componentIndex, ""), brushComponent.children[0].removeAllChild(); } _createBrushComponent(region, componentIndex) { var _a, _b; const interactiveAttr = this._getBrushInteractiveAttr(region), brush = new BrushComponent(Object.assign(Object.assign(Object.assign({ zIndex: this.layoutZIndex, brushStyle: transformToGraphic(null === (_a = this._spec) || void 0 === _a ? void 0 : _a.style) }, interactiveAttr), this._spec), { disableTriggerEvent: this._option.disableTriggerEvent })); brush.id = null !== (_b = this._spec.id) && void 0 !== _b ? _b : `brush-${this.id}`, this.getContainer().add(brush), this._brushComponents.push(brush), this._cacheInteractiveRangeAttrs.push(interactiveAttr), brush.addEventListener(BrushEvent.brushActive, (e => { this._initMarkBrushState(componentIndex, "outOfBrush"), this._emitEvent(ChartEvent.brushActive, region); })), brush.addEventListener(BrushEvent.drawStart, (e => { this._setRegionMarkPickable(region, !0), this._emitEvent(ChartEvent.brushStart, region); })), brush.addEventListener(BrushEvent.moveStart, (e => { this._setRegionMarkPickable(region, !0), this._emitEvent(ChartEvent.brushStart, region); })), brush.addEventListener(BrushEvent.drawing, (e => { this._setRegionMarkPickable(region, !1), this._handleBrushChange(region, e), this._emitEvent(ChartEvent.brushChange, region); })), brush.addEventListener(BrushEvent.moving, (e => { this._setRegionMarkPickable(region, !1), this._handleBrushChange(region, e), this._emitEvent(ChartEvent.brushChange, region); })), brush.addEventListener(BrushEvent.brushClear, (e => { this._setRegionMarkPickable(region, !0), this._initMarkBrushState(componentIndex, ""), this._emitEvent(ChartEvent.brushClear, region); })), brush.addEventListener(BrushEvent.drawEnd, (e => { var _a; this._setRegionMarkPickable(region, !0); const {operateMask: operateMask} = e.detail; if (null === (_a = this._spec) || void 0 === _a ? void 0 : _a.onBrushEnd) !0 === this._spec.onBrushEnd(e) ? (this.clearGraphic(), this._initMarkBrushState(componentIndex, ""), this._emitEvent(ChartEvent.brushClear, region)) : (this._spec.onBrushEnd(e), this._emitEvent(ChartEvent.brushEnd, region)); else { const inBrushData = this._extendDataInBrush(this._inBrushElementsMap); !this._spec.zoomWhenEmpty && inBrushData.length > 0 && this._setAxisAndDataZoom(operateMask, region), this._emitEvent(ChartEvent.brushEnd, region); } })), brush.addEventListener(BrushEvent.moveEnd, (e => { this._setRegionMarkPickable(region, !0); const {operateMask: operateMask} = e.detail, inBrushData = this._extendDataInBrush(this._inBrushElementsMap); !this._spec.zoomWhenEmpty && inBrushData.length > 0 && this._setAxisAndDataZoom(operateMask, region), this._emitEvent(ChartEvent.brushEnd, region); })); } _getBrushInteractiveAttr(region) { const regionLayoutPosition = region.getLayoutStartPoint(), regionLayoutRect = region.getLayoutRect(), seriesRegionStartX = regionLayoutPosition.x, seriesRegionEndX = seriesRegionStartX + regionLayoutRect.width, seriesRegionStartY = regionLayoutPosition.y, seriesRegionEndY = seriesRegionStartY + regionLayoutRect.height; return { interactiveRange: { minY: seriesRegionStartY, maxY: seriesRegionEndY, minX: seriesRegionStartX, maxX: seriesRegionEndX }, xRange: [ seriesRegionStartX, seriesRegionEndX ], yRange: [ seriesRegionStartY, seriesRegionEndY ] }; } _transformBrushedMarkAttr(brushedStyle) { const styleResult = {}; return (null == brushedStyle ? void 0 : brushedStyle.symbol) && (styleResult.symbolType = brushedStyle.symbol), (null == brushedStyle ? void 0 : brushedStyle.symbolSize) && (styleResult.size = brushedStyle.symbolSize), (null == brushedStyle ? void 0 : brushedStyle.color) && (styleResult.fill = brushedStyle.color), (null == brushedStyle ? void 0 : brushedStyle.colorAlpha) && (styleResult.fillOpacity = brushedStyle.colorAlpha), Object.assign(Object.assign({}, transformToGraphic(brushedStyle)), styleResult); } _handleBrushChange(region, e) { const {operateMask: operateMask} = e.detail; this._reconfigItem(operateMask, region), this._reconfigLinkedItem(operateMask, region); } _extendDataInBrush(elementsMap) { var _a, _b; const data = []; for (const brushName in elementsMap) for (const elementKey in elementsMap[brushName]) data.push(Object.assign({}, null === (_b = null === (_a = elementsMap[brushName][elementKey].context) || void 0 === _a ? void 0 : _a.data) || void 0 === _b ? void 0 : _b[0])); return data; } _extendDatumOutOfBrush(elementsMap) { var _a, _b; const data = []; for (const elementKey in elementsMap) data.push(null === (_b = null === (_a = elementsMap[elementKey].context) || void 0 === _a ? void 0 : _a.data) || void 0 === _b ? void 0 : _b[0]); return data; } _emitEvent(eventType, region) { var _a; this.event.emit(eventType, { model: this, value: { operateType: eventType, operateRegion: region, inBrushData: this._extendDataInBrush(this._inBrushElementsMap), outOfBrushData: this._extendDatumOutOfBrush(this._outOfBrushElementsMap), linkInBrushData: this._extendDataInBrush(this._linkedInBrushElementsMap), linkOutOfBrushData: this._extendDatumOutOfBrush(this._linkedOutOfBrushElementsMap), inBrushElementsMap: this._inBrushElementsMap, outOfBrushElementsMap: this._outOfBrushElementsMap, linkedInBrushElementsMap: this._linkedInBrushElementsMap, linkedOutOfBrushElementsMap: this._linkedOutOfBrushElementsMap, zoomRecord: this._zoomRecord }, vchart: null === (_a = this._option) || void 0 === _a ? void 0 : _a.globalInstance }); } _reconfigItem(operateMask, region) { var _a, _b, _c; if (!(null == operateMask ? void 0 : operateMask.globalTransMatrix) || !(null === (_a = null == operateMask ? void 0 : operateMask.attribute) || void 0 === _a ? void 0 : _a.points)) return; const points = null !== (_c = null === (_b = null == operateMask ? void 0 : operateMask.attribute) || void 0 === _b ? void 0 : _b.points) && void 0 !== _c ? _c : [], {a: a, b: b, c: c, d: d, e: e, f: f} = operateMask.globalTransMatrix, pointsCoord = points.map((p => ({ x: a * p.x + c * p.y + e, y: b * p.x + d * p.y + f }))), {markTypeFilter: markTypeFilter = []} = this._spec; this._itemMap[region.id].forEach((mark => { if (markTypeFilter.includes(mark.type)) return; const graphics = mark.getGraphics(); graphics && graphics.length && graphics.forEach((graphicItem => { var _a, _b, _c; const elementKey = mark.id + "_" + graphicItem.context.key, isBrushContainItem = this._isBrushContainItem(operateMask.globalAABBBounds, pointsCoord, graphicItem); (null === (_a = this._outOfBrushElementsMap) || void 0 === _a ? void 0 : _a[elementKey]) && isBrushContainItem ? (graphicItem.addState("inBrush", !0), this._inBrushElementsMap[null == operateMask ? void 0 : operateMask.name] || (this._inBrushElementsMap[null == operateMask ? void 0 : operateMask.name] = {}), this._inBrushElementsMap[null == operateMask ? void 0 : operateMask.name][elementKey] = graphicItem, delete this._outOfBrushElementsMap[elementKey]) : (null === (_c = null === (_b = this._inBrushElementsMap) || void 0 === _b ? void 0 : _b[null == operateMask ? void 0 : operateMask.name]) || void 0 === _c ? void 0 : _c[elementKey]) && !isBrushContainItem && (graphicItem.removeState("inBrush"), graphicItem.addState("outOfBrush", !0), this._outOfBrushElementsMap[elementKey] = graphicItem, delete this._inBrushElementsMap[operateMask.name][elementKey]); })); })); } _reconfigLinkedItem(operateMask, region) { var _a; if (!(null == operateMask ? void 0 : operateMask.globalTransMatrix) || !(null === (_a = null == operateMask ? void 0 : operateMask.attribute) || void 0 === _a ? void 0 : _a.points)) return; const regionLayoutPos = region.getLayoutStartPoint(), seriesId = region.getSeries().map((s => s.id)); this._linkedSeries.forEach((s => { var _a, _b; if (!seriesId.includes(s.id)) { const sRegionLayoutPos = s.getRegion().getLayoutStartPoint(), regionOffsetX = sRegionLayoutPos.x - regionLayoutPos.x, regionOffsetY = sRegionLayoutPos.y - regionLayoutPos.y, points = null !== (_b = null === (_a = null == operateMask ? void 0 : operateMask.attribute) || void 0 === _a ? void 0 : _a.points) && void 0 !== _b ? _b : [], {a: a, b: b, c: c, d: d, e: e, f: f} = operateMask.globalTransMatrix, dx = regionOffsetX || 0, dy = regionOffsetY || 0, pointsCoord = points.map((p => ({ x: a * p.x + c * p.y + e + dx, y: b * p.x + d * p.y + f + dy }))); operateMask.globalAABBBounds.clone().set(operateMask.globalAABBBounds.x1 + dx, operateMask.globalAABBBounds.y1 + dy, operateMask.globalAABBBounds.x2 + dx, operateMask.globalAABBBounds.y2 + dy); const {markTypeFilter: markTypeFilter = []} = this._spec; this._linkedItemMap[s.id].forEach((mark => { if (markTypeFilter.includes(mark.type)) return; const graphics = mark.getGraphics(); graphics && graphics.length && graphics.forEach((graphicItem => { var _a, _b, _c; const {key: key} = graphicItem.context, elementKey = mark.id + "_" + graphicItem.context.key; (null === (_a = this._linkedOutOfBrushElementsMap) || void 0 === _a ? void 0 : _a[elementKey]) && this._isBrushContainItem(operateMask.globalAABBBounds, pointsCoord, graphicItem) ? (graphicItem.addState("inBrush", !0), this._linkedInBrushElementsMap[null == operateMask ? void 0 : operateMask.name] || (this._linkedInBrushElementsMap[null == operateMask ? void 0 : operateMask.name] = {}), this._linkedInBrushElementsMap[null == operateMask ? void 0 : operateMask.name][elementKey] = graphicItem, delete this._linkedOutOfBrushElementsMap[elementKey]) : (null === (_c = null === (_b = this._linkedInBrushElementsMap) || void 0 === _b ? void 0 : _b[null == operateMask ? void 0 : operateMask.name]) || void 0 === _c ? void 0 : _c[elementKey]) && !this._isBrushContainItem(operateMask.globalAABBBounds, pointsCoord, graphicItem) && (graphicItem.removeState("inBrush"), graphicItem.addState("outOfBrush", !0), this._linkedOutOfBrushElementsMap[elementKey] = graphicItem); })); })); } })); } _isBrushContainItem(brushMaskAABBBounds, brushMaskPointsCoord, item) { let itemBounds = []; if ([ "symbol", "rect" ].includes(item.type)) { const {x1: x1, x2: x2, y1: y1, y2: y2} = null == item ? void 0 : item.globalAABBBounds; return itemBounds = [ { x: x1, y: y1 }, { x: x2, y: y1 }, { x: x2, y: y2 }, { x: x1, y: y2 } ], polygonIntersectPolygon(brushMaskPointsCoord, itemBounds); } return brushMaskAABBBounds.intersects(item.globalAABBBounds); } _initItemMap(itemMap, elementMap, stateName) { const {markTypeFilter: markTypeFilter = []} = this._spec; Object.entries(itemMap).forEach((([regionId, marks]) => { marks.forEach((mark => { if (markTypeFilter.includes(mark.type)) return; const graphics = mark.getGraphics(); graphics && graphics.length && graphics.forEach((el => { const elementKey = mark.id + "_" + el.context.key; el.removeState("inBrush"), el.removeState("outOfBrush"), stateName && el.addState(stateName, !0), elementMap[elementKey] = el; })); })); })); } _initMarkBrushState(componentIndex, stateName) { this._brushComponents.forEach(((brush, index) => { index !== componentIndex && brush.children[0].removeAllChild(); })), this._inBrushElementsMap = {}, this._outOfBrushElementsMap = {}, this._linkedInBrushElementsMap = {}, this._linkedOutOfBrushElementsMap = {}, this._initItemMap(this._itemMap, this._outOfBrushElementsMap, stateName), this._initItemMap(this._linkedItemMap, this._linkedOutOfBrushElementsMap, stateName); } _setRegionMarkPickable(region, pickable) { region.getGroupMark().getGraphics().forEach((g => g.setAttribute("childrenPickable", pickable))); } _stateClamp(state) { return Math.min(Math.max(0, state), 1); } _setAxisAndDataZoom(operateMask, region) { var _a; if (this._zoomRecord = [], this._spec.zoomAfterBrush) { const operateMaskBounds = operateMask.AABBBounds; null === (_a = this._regionAxisMap["region_" + region.id]) || void 0 === _a || _a.forEach((axis => { var _a, _b; const isHorizontal = "bottom" === axis.layoutOrient || "top" === axis.layoutOrient, axisRangeExpand = null !== (_a = this._spec.axisRangeExpand) && void 0 !== _a ? _a : 0, {x1: x1, x2: x2, y1: y1, y2: y2} = operateMaskBounds, regionStartAttr = isHorizontal ? "x" : "y", boundsStart = isHorizontal ? x1 : y1, boundsEnd = isHorizontal ? x2 : y2; if (this._axisDataZoomMap[axis.id]) { const dataZoom = this._axisDataZoomMap[axis.id], releatedAxis = dataZoom.relatedAxisComponent, startValue = releatedAxis.getScale().invert(boundsStart - region.getLayoutStartPoint()[regionStartAttr]), endValue = releatedAxis.getScale().invert(boundsEnd - region.getLayoutStartPoint()[regionStartAttr]), startPercent = dataZoom.dataToStatePoint(startValue), endPercent = dataZoom.dataToStatePoint(endValue), newStartPercent = this._stateClamp(startPercent - axisRangeExpand), newEndPercent = this._stateClamp(endPercent + axisRangeExpand); dataZoom.setStartAndEnd(Math.min(newStartPercent, newEndPercent), Math.max(newStartPercent, newEndPercent), [ "percent", "percent" ]), this._zoomRecord.push({ operateComponent: dataZoom, start: newStartPercent, end: newEndPercent, startValue: dataZoom.statePointToData(newStartPercent), endValue: dataZoom.statePointToData(newEndPercent) }); } else { const range = axis.getScale().range(), rangeFactor = null !== (_b = axis.scaleRangeFactor()) && void 0 !== _b ? _b : [ 0, 1 ], isAxisReverse = last(range) < range[0], startPosTemp = boundsStart - region.getLayoutStartPoint()[regionStartAttr], endPosTemp = boundsEnd - region.getLayoutStartPoint()[regionStartAttr], endPos = isAxisReverse ? Math.min(startPosTemp, endPosTemp) : Math.max(startPosTemp, endPosTemp), startPos = isAxisReverse ? Math.max(startPosTemp, endPosTemp) : Math.min(startPosTemp, endPosTemp), start = (startPos - range[0]) / (last(range) - range[0]) * (rangeFactor[1] - rangeFactor[0]) + rangeFactor[0], end = (endPos - range[0]) / (last(range) - range[0]) * (rangeFactor[1] - rangeFactor[0]) + rangeFactor[0], newStart = this._stateClamp(start - axisRangeExpand), newEnd = this._stateClamp(end + axisRangeExpand); axis.scaleRangeFactor([ newStart, newEnd ]), axis.effect.scaleUpdate(), this._zoomRecord.push({ operateComponent: axis, start: newStart, end: newEnd, startValue: axis.getScale().invert(startPos), endValue: axis.getScale().invert(endPos) }); } })); } } _getNeedClearVRenderComponents() { return this._brushComponents; } clearGraphic() { this._brushComponents && this._brushComponents.forEach((brush => { brush._container.incrementalClearChild(); })); } clear() { if (this._brushComponents) { const container = this.getContainer(); this._brushComponents.forEach(((brush, index) => { this._initMarkBrushState(index, ""), brush.removeAllChild(), brush.releaseBrushEvents(), container && container.removeChild(brush); })), this._brushComponents = null; } } } Brush.type = ComponentTypeEnum.brush, Brush.builtInTheme = { brush: brush }, Brush.specKey = "brush"; export const registerBrush = () => { Factory.registerComponent(Brush.type, Brush); }; //# sourceMappingURL=brush.js.map