@visactor/vchart
Version:
charts lib based @visactor/VGrammar
364 lines (349 loc) • 22.9 kB
JavaScript
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";
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._needInitOutState = !0, this._cacheInteractiveRangeAttrs = [], this._needDisablePickable = !1,
this._releatedAxes = [], this._regionAxisMap = {}, this._axisDataZoomMap = {}, this._zoomRecord = [];
}
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));
}));
}));
}
static getSpecInfo(chartSpec) {
return getSpecInfo(chartSpec, this.specKey, this.type, (s => !1 !== s.visible));
}
created() {
super.created(), this.initEvent(), this._bindRegions(), this._bindLinkedSeries(),
this._initRegionAxisMap(), this._initAxisDataZoomMap(), this._initNeedOperatedItem();
}
_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]) || void 0 === _a ? void 0 : _a.data) || void 0 === _b ? void 0 : _b[0]));
return data;
}
_extendDatumOutOfBrush(elementsMap) {
var _a;
const data = [];
for (const elementKey in elementsMap) data.push(null === (_a = elementsMap[elementKey].data) || void 0 === _a ? void 0 : _a[0]);
return data;
}
_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 ]
};
}
_updateBrushComponent(region, componentIndex) {
const interactiveAttr = this._getBrushInteractiveAttr(region), brushComponent = this._brushComponents[componentIndex];
brushComponent.setAttributes(interactiveAttr), this._initMarkBrushState(componentIndex, ""),
brushComponent.children[0].removeAllChild(), this._needInitOutState = !0;
}
_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);
const {brushMode: brushMode = "single"} = this._spec;
this._brushComponents.push(brush), this._cacheInteractiveRangeAttrs.push(interactiveAttr),
brush.addEventListener(BrushEvent.drawStart, (e => {
this._emitEvent(ChartEvent.brushStart, region);
})), brush.addEventListener(BrushEvent.moveStart, (e => {
this._emitEvent(ChartEvent.brushStart, region);
})), brush.addEventListener(BrushEvent.drawing, (e => {
this._needInitOutState && "single" === brushMode && this._initMarkBrushState(componentIndex, "outOfBrush"),
this._needInitOutState = !1, this._needDisablePickable = !0, this._handleBrushChange(ChartEvent.brushChange, region, e),
this._emitEvent(ChartEvent.brushChange, region);
})), brush.addEventListener(BrushEvent.moving, (e => {
this._handleBrushChange(ChartEvent.brushChange, region, e), this._emitEvent(ChartEvent.brushChange, region);
})), brush.addEventListener(BrushEvent.brushClear, (e => {
this._initMarkBrushState(componentIndex, ""), this._needInitOutState = !0, this._needDisablePickable = !1,
this._handleBrushChange(ChartEvent.brushChange, region, e), this._handleBrushChange(ChartEvent.brushClear, region, e),
this._emitEvent(ChartEvent.brushChange, region), this._emitEvent(ChartEvent.brushClear, region);
})), brush.addEventListener(BrushEvent.drawEnd, (e => {
this._needInitOutState = !0, this._needDisablePickable = !1;
const {operateMask: operateMask} = e.detail;
this._handleBrushChange(ChartEvent.brushEnd, region, e);
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 => {
const {operateMask: operateMask} = e.detail;
this._handleBrushChange(ChartEvent.brushEnd, region, e);
const inBrushData = this._extendDataInBrush(this._inBrushElementsMap);
!this._spec.zoomWhenEmpty && inBrushData.length > 0 && this._setAxisAndDataZoom(operateMask, region),
this._emitEvent(ChartEvent.brushEnd, region);
}));
}
_handleBrushChange(eventType, region, e) {
const {operateMask: operateMask} = e.detail;
this._reconfigItem(operateMask, region), this._reconfigLinkedItem(operateMask, region);
}
_emitEvent(eventType, region) {
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
}
});
}
_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);
}
_reconfigItem(operateMask, region) {
this._itemMap[region.id].forEach((mark => {
const grammarMark = mark.getProduct();
if (!grammarMark || !grammarMark.elements || !grammarMark.elements.length) return;
grammarMark.elements.forEach((el => {
var _a, _b, _c;
const graphicItem = el.getGraphicItem(), elementKey = mark.id + "_" + el.key;
(null === (_a = this._outOfBrushElementsMap) || void 0 === _a ? void 0 : _a[elementKey]) && this._isBrushContainItem(operateMask, graphicItem) ? (el.addState("inBrush"),
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] = el,
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]) && !this._isBrushContainItem(operateMask, graphicItem) && (el.removeState("inBrush"),
el.addState("outOfBrush"), this._outOfBrushElementsMap[elementKey] = el, delete this._inBrushElementsMap[operateMask.name][elementKey]),
graphicItem.setAttribute("pickable", !this._needDisablePickable);
}));
}));
}
_reconfigLinkedItem(operateMask, region) {
const regionLayoutPos = region.getLayoutStartPoint(), seriesId = region.getSeries().map((s => s.id));
this._linkedSeries.forEach((s => {
if (!seriesId.includes(s.id)) {
const sRegionLayoutPos = s.getRegion().getLayoutStartPoint(), regionOffsetX = sRegionLayoutPos.x - regionLayoutPos.x, regionOffsetY = sRegionLayoutPos.y - regionLayoutPos.y;
this._linkedItemMap[s.id].forEach((mark => {
const grammarMark = mark.getProduct();
if (!grammarMark || !grammarMark.elements || !grammarMark.elements.length) return;
grammarMark.elements.forEach((el => {
var _a, _b, _c;
const graphicItem = el.getGraphicItem(), elementKey = mark.id + "_" + el.key;
(null === (_a = this._linkedOutOfBrushElementsMap) || void 0 === _a ? void 0 : _a[elementKey]) && this._isBrushContainItem(operateMask, graphicItem, {
dx: regionOffsetX,
dy: regionOffsetY
}) ? (el.addState("inBrush"), 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] = el,
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, graphicItem, {
dx: regionOffsetX,
dy: regionOffsetY
}) && (el.removeState("inBrush"), el.addState("outOfBrush"), this._linkedOutOfBrushElementsMap[elementKey] = el),
graphicItem.setAttribute("pickable", !this._needDisablePickable);
}));
}));
}
}));
}
_isBrushContainItem(brushMask, item, linkedOffset) {
var _a, _b, _c;
if (!(null == brushMask ? void 0 : brushMask.globalTransMatrix) || !(null === (_a = null == brushMask ? void 0 : brushMask.attribute) || void 0 === _a ? void 0 : _a.points)) return !1;
const points = null !== (_c = null === (_b = null == brushMask ? void 0 : brushMask.attribute) || void 0 === _b ? void 0 : _b.points) && void 0 !== _c ? _c : [], {a: a, b: b, c: c, d: d, e: e, f: f} = brushMask.globalTransMatrix, dx = (null == linkedOffset ? void 0 : linkedOffset.dx) || 0, dy = (null == linkedOffset ? void 0 : linkedOffset.dy) || 0, pointsCoord = points.map((p => ({
x: a * p.x + c * p.y + e + dx,
y: b * p.x + d * p.y + f + dy
})));
brushMask.globalAABBBounds.clone().set(brushMask.globalAABBBounds.x1 + dx, brushMask.globalAABBBounds.y1 + dy, brushMask.globalAABBBounds.x2 + dx, brushMask.globalAABBBounds.y2 + dy);
const x = item.globalTransMatrix.e, y = item.globalTransMatrix.f;
let itemBounds = [];
if ("symbol" === item.type) {
const {size: itemSize = 0} = null == item ? void 0 : item.attribute, size = array(itemSize)[0] / 2;
return itemBounds = [ {
x: x - size,
y: y - size
}, {
x: x + size,
y: y - size
}, {
x: x + size,
y: y + size
}, {
x: x - size,
y: y + size
} ], polygonIntersectPolygon(pointsCoord, itemBounds);
}
if ("rect" === item.type) {
const {x1: x1, x2: x2, y1: y1, y2: y2} = null == item ? void 0 : item.AABBBounds, width = Math.abs(x1 - x2), height = Math.abs(y1 - y2);
return itemBounds = [ {
x: x,
y: y
}, {
x: x + width,
y: y
}, {
x: x + width,
y: y + height
}, {
x: x,
y: y + height
} ], polygonIntersectPolygon(pointsCoord, itemBounds);
}
return brushMask.globalAABBBounds.intersects(item.globalAABBBounds);
}
_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.getScale().rangeFactor()) && 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.getScale().rangeFactor([ newStart, newEnd ]), axis.effect.scaleUpdate(), this._zoomRecord.push({
operateComponent: axis,
start: newStart,
end: newEnd,
startValue: axis.getScale().invert(startPos),
endValue: axis.getScale().invert(endPos)
});
}
}));
}
}
_bindRegions() {
isValid(this._spec.regionId) && isValid(this._spec.regionIndex) && (this._relativeRegions = this._option.getAllRegions()),
this._relativeRegions = this._option.getRegionsInUserIdOrIndex(array(this._spec.regionId), array(this._spec.regionIndex));
}
_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);
}));
}
_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();
}));
}
_initMarkBrushState(componentIndex, stateName) {
this._brushComponents.forEach(((brush, index) => {
index !== componentIndex && brush.children[0].removeAllChild();
})), this._inBrushElementsMap = {}, this._outOfBrushElementsMap = {}, this._linkedInBrushElementsMap = {},
this._linkedOutOfBrushElementsMap = {}, this._option.getAllSeries().forEach((s => {
s.getMarksWithoutRoot().forEach((mark => {
const grammarMark = mark.getProduct();
if (!grammarMark || !grammarMark.elements || !grammarMark.elements.length) return;
grammarMark.elements.forEach((el => {
const elementKey = mark.id + "_" + el.key;
el.removeState("inBrush"), el.removeState("outOfBrush"), el.addState(stateName),
this._outOfBrushElementsMap[elementKey] = el, this._linkedOutOfBrushElementsMap[elementKey] = el;
}));
}));
}));
}
initEvent() {}
onRender(ctx) {}
changeRegions(regions) {}
_getNeedClearVRenderComponents() {
return this._brushComponents;
}
_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(ctx) {
var _a;
if (super.onLayoutEnd(ctx), 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);
}))));
}
clearGraphic() {
this._brushComponents && this._brushComponents.forEach((brush => {
brush._container.incrementalClearChild();
}));
}
clear() {
if (this._brushComponents) {
const container = this.getContainer();
this._brushComponents.forEach((brush => {
brush.removeAllChild(), brush.releaseBrushEvents(), container && container.removeChild(brush);
})), this._brushComponents = null;
}
}
}
Brush.type = ComponentTypeEnum.brush, Brush.specKey = "brush";
export const registerBrush = () => {
Factory.registerComponent(Brush.type, Brush);
};
//# sourceMappingURL=brush.js.map