@visactor/vchart
Version:
charts lib based @visactor/VGrammar
361 lines (345 loc) • 23.4 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";
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