Skip to content

Commit 210d484

Browse files
authored
Merge pull request #3879 from VisActor/feat/filter-elements-by-data
feat: add new api `filterGraphicsByDatum` to BaseChart
2 parents e2f95eb + b40b4f7 commit 210d484

File tree

3 files changed

+138
-98
lines changed

3 files changed

+138
-98
lines changed

packages/vchart/src/chart/base/base-chart.ts

+92-50
Original file line numberDiff line numberDiff line change
@@ -1226,65 +1226,107 @@ export class BaseChart<T extends IChartSpec> extends CompilableBase implements I
12261226
});
12271227
}
12281228

1229-
protected _setStateInDatum(
1230-
stateKey: string,
1231-
checkReverse: boolean,
1229+
filterGraphicsByDatum(
12321230
datum: MaybeArray<Datum> | null,
1233-
filter?: (series: ISeries, mark: IMark) => boolean,
1234-
region?: IRegionQuerier
1231+
opt: {
1232+
filter?: (series: ISeries, mark: IMark) => boolean;
1233+
region?: IRegionQuerier;
1234+
getDatum?: (el: IElement, mark: IMark, s: ISeries, r: IRegion) => Datum;
1235+
callback?: (el: IElement, mark: IMark, s: ISeries, r: IRegion) => void;
1236+
regionCallback?: (pickElements: IElement[], r: IRegion) => void;
1237+
} = {}
12351238
) {
12361239
datum = datum ? array(datum) : null;
1237-
const keys = !datum ? null : Object.keys(datum[0]);
1238-
this.getRegionsInQuerier(region).forEach(r => {
1239-
if (!datum) {
1240-
r.interaction.clearEventElement(stateKey, true);
1241-
return;
1242-
}
1243-
r.getSeries().forEach(s => {
1244-
s.getMarks().forEach(m => {
1245-
if (!m.getProduct()) {
1246-
return;
1247-
}
1248-
if (!filter || (isFunction(filter) && filter(s, m))) {
1249-
const isCollect = m.getProduct().isCollectionMark();
1250-
const elements = m.getProduct().elements;
1251-
let pickElements = [] as IElement[];
1252-
if (isCollect) {
1253-
pickElements = elements.filter(e => {
1254-
const elDatum = e.getDatum();
1255-
// eslint-disable-next-line max-nested-callbacks, eqeqeq
1256-
(datum as Datum[]).every((d, index) => keys.every(k => d[k] == elDatum[index][k]));
1257-
});
1258-
} else {
1259-
if (datum.length > 1) {
1260-
const datumTemp = (datum as Datum[]).slice();
1261-
pickElements = elements.filter(e => {
1262-
if (datumTemp.length === 0) {
1263-
return false;
1264-
}
1265-
const elDatum = e.getDatum();
1266-
// eslint-disable-next-line max-nested-callbacks, eqeqeq
1267-
const index = datumTemp.findIndex(d => keys.every(k => d[k] == elDatum[k]));
1268-
if (index >= 0) {
1269-
datumTemp.splice(index, 1);
1270-
return true;
1240+
const keys = !datum ? null : Object.keys((datum as Datum[])[0]);
1241+
const allElements = [] as IElement[];
1242+
const getDatumOfElement = opt.getDatum ?? ((el: IElement) => el.getDatum());
1243+
1244+
this.getRegionsInQuerier(opt.region).forEach(r => {
1245+
const pickElements = [] as IElement[];
1246+
datum &&
1247+
r.getSeries().forEach(s => {
1248+
s.getMarks().forEach(m => {
1249+
if (!m.getProduct()) {
1250+
return;
1251+
}
1252+
if (!opt.filter || (isFunction(opt.filter) && opt.filter(s, m))) {
1253+
const isCollect = m.getProduct().isCollectionMark();
1254+
const elements = m.getProduct().elements;
1255+
if (isCollect) {
1256+
elements.filter(e => {
1257+
const elDatum = getDatumOfElement(e, m, s, r);
1258+
const isPick =
1259+
// eslint-disable-next-line max-nested-callbacks, eqeqeq
1260+
elDatum && (datum as Datum[]).every((d, index) => keys.every(k => d[k] == elDatum[index][k]));
1261+
1262+
if (isPick) {
1263+
pickElements.push(e);
1264+
allElements.push(e);
1265+
opt.callback && opt.callback(e, m, s, r);
12711266
}
1272-
return false;
12731267
});
12741268
} else {
1275-
// eslint-disable-next-line eqeqeq
1276-
const el = elements.find(e => keys.every(k => datum[0][k] == e.getDatum()[k]));
1277-
el && (pickElements = [el]);
1269+
if (datum.length > 1) {
1270+
const datumTemp = (datum as Datum[]).slice();
1271+
1272+
elements.forEach(e => {
1273+
const elDatum = getDatumOfElement(e, m, s, r);
1274+
// eslint-disable-next-line max-nested-callbacks, eqeqeq
1275+
const index = elDatum && datumTemp.findIndex(d => keys.every(k => d[k] == elDatum[k]));
1276+
if (index >= 0) {
1277+
datumTemp.splice(index, 1);
1278+
1279+
pickElements.push(e);
1280+
allElements.push(e);
1281+
opt.callback && opt.callback(e, m, s, r);
1282+
}
1283+
});
1284+
} else {
1285+
const el = elements.find(e => {
1286+
const elDatum = getDatumOfElement(e, m, s, r);
1287+
// eslint-disable-next-line eqeqeq
1288+
return elDatum && keys.every(k => (datum as Datum[])[0][k] == elDatum[k]);
1289+
});
1290+
1291+
if (el) {
1292+
pickElements.push(el);
1293+
allElements.push(el);
1294+
opt.callback && opt.callback(el, m, s, r);
1295+
}
1296+
}
12781297
}
12791298
}
1280-
pickElements.forEach(element => {
1281-
r.interaction.startInteraction(stateKey, element);
1282-
});
1283-
}
1299+
});
12841300
});
1285-
});
1286-
if (checkReverse) {
1287-
r.interaction.reverseEventElement(stateKey);
1301+
1302+
opt.regionCallback && opt.regionCallback(pickElements, r);
1303+
});
1304+
1305+
return allElements;
1306+
}
1307+
1308+
protected _setStateInDatum(
1309+
stateKey: string,
1310+
checkReverse: boolean,
1311+
datum: MaybeArray<Datum> | null,
1312+
filter?: (series: ISeries, mark: IMark) => boolean,
1313+
region?: IRegionQuerier
1314+
) {
1315+
this.filterGraphicsByDatum(datum, {
1316+
filter,
1317+
region,
1318+
regionCallback: (elements, r) => {
1319+
if (!datum) {
1320+
r.interaction.clearEventElement(stateKey, true);
1321+
} else if (elements.length) {
1322+
elements.forEach(e => {
1323+
r.interaction.startInteraction(stateKey, e);
1324+
});
1325+
1326+
if (checkReverse) {
1327+
r.interaction.reverseEventElement(stateKey);
1328+
}
1329+
}
12881330
}
12891331
});
12901332
}

packages/vchart/src/chart/interface/chart.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { IEvent } from '../../event/interface';
22
import type { LayoutCallBack } from '../../layout/interface';
3-
import type { IView } from '@visactor/vgrammar-core';
3+
import type { IElement, IView } from '@visactor/vgrammar-core';
44
import type { IParserOptions } from '@visactor/vdataset';
55
import type { IComponent, IComponentConstructor } from '../../component/interface';
66
import type { IMark } from '../../mark/interface';
@@ -210,6 +210,20 @@ export interface IChart extends ICompilable {
210210
getSeriesData: (id: StringOrNumber | undefined, index: number | undefined) => DataView | undefined;
211211
// setDimensionIndex
212212
setDimensionIndex: (value: StringOrNumber, opt: DimensionIndexOption) => void;
213+
/**
214+
* 根据数据筛选图元
215+
* @since 1.13.9
216+
*/
217+
filterGraphicsByDatum: (
218+
datum: MaybeArray<Datum> | null,
219+
opt?: {
220+
filter?: (series: ISeries, mark: IMark) => boolean;
221+
region?: IRegionQuerier;
222+
getDatum?: (el: IElement, mark: IMark, s: ISeries, r: IRegion) => Datum;
223+
callback?: (el: IElement, mark: IMark, s: ISeries, r: IRegion) => void;
224+
regionCallback?: (pickElements: IElement[], r: IRegion) => void;
225+
}
226+
) => IElement[];
213227
}
214228

215229
export interface IChartSpecTransformer {

packages/vchart/src/chart/sankey/sankey.ts

+31-47
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { Datum, MaybeArray } from '../../typings/common';
99
import type { ISeries } from '../../series/interface';
1010
import type { IMark } from '../../mark/interface/common';
1111
import type { IRegionQuerier } from '../../typings/params';
12-
import { isArray, isFunction } from '@visactor/vutils';
12+
import { isArray } from '@visactor/vutils';
1313
import { loadScrollbar } from '@visactor/vrender-components';
1414

1515
export class SankeyChart<T extends ISankeyChartSpec = ISankeyChartSpec> extends BaseChart<T> {
@@ -29,57 +29,41 @@ export class SankeyChart<T extends ISankeyChartSpec = ISankeyChartSpec> extends
2929
) {
3030
// 桑基图暂时只支持单选
3131
const activeDatum = isArray(datum) ? datum[0] : datum;
32-
const keys = !activeDatum ? null : Object.keys(activeDatum);
33-
this.getRegionsInQuerier(region).forEach(r => {
34-
if (!activeDatum) {
35-
r.interaction.clearEventElement(stateKey, true);
36-
return;
37-
}
38-
let hasPick = false;
39-
r.getSeries().forEach(s => {
40-
let activeNodeOrLink = null;
41-
42-
s.getMarksWithoutRoot().forEach(m => {
43-
if (m.type === 'text') {
44-
return;
45-
}
46-
47-
let pickElement = null;
48-
const mark = m.getProduct();
49-
if (!mark) {
50-
return;
51-
}
52-
if (!filter || (isFunction(filter) && filter(s, m))) {
53-
pickElement = mark.elements.find((e: any) =>
54-
keys.every(k => {
55-
let datum = e.getDatum()?.datum;
32+
const markFilter = (series: ISeries, mark: IMark) => {
33+
return mark.type !== 'text' && mark.getProduct() && (!filter || filter(series, mark));
34+
};
5635

57-
if (isArray(datum)) {
58-
// data of link
59-
datum = datum[0];
60-
}
36+
this.filterGraphicsByDatum(activeDatum, {
37+
filter: markFilter,
38+
region,
39+
getDatum: e => {
40+
let d = e.getDatum()?.datum;
6141

62-
// eslint-disable-next-line eqeqeq
63-
return activeDatum[k] == datum?.[k];
64-
})
65-
);
66-
}
67-
if (pickElement) {
68-
hasPick = true;
69-
r.interaction.startInteraction(stateKey, pickElement);
42+
if (isArray(d)) {
43+
// data of link
44+
d = d[0];
45+
}
46+
return d;
47+
},
48+
callback: (element, mark, s, r) => {
49+
const id = mark.getProduct()?.id();
50+
if (id && (id.includes('node') || id.includes('link'))) {
51+
(s as any)._handleEmphasisElement?.({ item: element });
52+
}
53+
},
54+
regionCallback: (elements, r) => {
55+
if (!activeDatum) {
56+
r.interaction.clearEventElement(stateKey, true);
57+
return;
58+
} else if (elements.length) {
59+
elements.forEach(e => {
60+
r.interaction.startInteraction(stateKey, e);
61+
});
7062

71-
if (mark.id().includes('node') || mark.id().includes('link')) {
72-
activeNodeOrLink = pickElement;
73-
}
63+
if (checkReverse) {
64+
r.interaction.reverseEventElement(stateKey);
7465
}
75-
});
76-
77-
if (activeNodeOrLink) {
78-
(s as any)._handleEmphasisElement?.({ item: activeNodeOrLink });
7966
}
80-
});
81-
if (checkReverse && hasPick) {
82-
r.interaction.reverseEventElement(stateKey);
8367
}
8468
});
8569
}

0 commit comments

Comments
 (0)