diff --git a/packages/vchart/src/chart/base/base-chart.ts b/packages/vchart/src/chart/base/base-chart.ts index 6f2c01e021..a41a884182 100644 --- a/packages/vchart/src/chart/base/base-chart.ts +++ b/packages/vchart/src/chart/base/base-chart.ts @@ -1226,65 +1226,107 @@ export class BaseChart extends CompilableBase implements I }); } - protected _setStateInDatum( - stateKey: string, - checkReverse: boolean, + filterGraphicsByDatum( datum: MaybeArray | null, - filter?: (series: ISeries, mark: IMark) => boolean, - region?: IRegionQuerier + opt: { + filter?: (series: ISeries, mark: IMark) => boolean; + region?: IRegionQuerier; + getDatum?: (el: IElement, mark: IMark, s: ISeries, r: IRegion) => Datum; + callback?: (el: IElement, mark: IMark, s: ISeries, r: IRegion) => void; + regionCallback?: (pickElements: IElement[], r: IRegion) => void; + } = {} ) { datum = datum ? array(datum) : null; - const keys = !datum ? null : Object.keys(datum[0]); - this.getRegionsInQuerier(region).forEach(r => { - if (!datum) { - r.interaction.clearEventElement(stateKey, true); - return; - } - r.getSeries().forEach(s => { - s.getMarks().forEach(m => { - if (!m.getProduct()) { - return; - } - if (!filter || (isFunction(filter) && filter(s, m))) { - const isCollect = m.getProduct().isCollectionMark(); - const elements = m.getProduct().elements; - let pickElements = [] as IElement[]; - if (isCollect) { - pickElements = elements.filter(e => { - const elDatum = e.getDatum(); - // eslint-disable-next-line max-nested-callbacks, eqeqeq - (datum as Datum[]).every((d, index) => keys.every(k => d[k] == elDatum[index][k])); - }); - } else { - if (datum.length > 1) { - const datumTemp = (datum as Datum[]).slice(); - pickElements = elements.filter(e => { - if (datumTemp.length === 0) { - return false; - } - const elDatum = e.getDatum(); - // eslint-disable-next-line max-nested-callbacks, eqeqeq - const index = datumTemp.findIndex(d => keys.every(k => d[k] == elDatum[k])); - if (index >= 0) { - datumTemp.splice(index, 1); - return true; + const keys = !datum ? null : Object.keys((datum as Datum[])[0]); + const allElements = [] as IElement[]; + const getDatumOfElement = opt.getDatum ?? ((el: IElement) => el.getDatum()); + + this.getRegionsInQuerier(opt.region).forEach(r => { + const pickElements = [] as IElement[]; + datum && + r.getSeries().forEach(s => { + s.getMarks().forEach(m => { + if (!m.getProduct()) { + return; + } + if (!opt.filter || (isFunction(opt.filter) && opt.filter(s, m))) { + const isCollect = m.getProduct().isCollectionMark(); + const elements = m.getProduct().elements; + if (isCollect) { + elements.filter(e => { + const elDatum = getDatumOfElement(e, m, s, r); + const isPick = + // eslint-disable-next-line max-nested-callbacks, eqeqeq + elDatum && (datum as Datum[]).every((d, index) => keys.every(k => d[k] == elDatum[index][k])); + + if (isPick) { + pickElements.push(e); + allElements.push(e); + opt.callback && opt.callback(e, m, s, r); } - return false; }); } else { - // eslint-disable-next-line eqeqeq - const el = elements.find(e => keys.every(k => datum[0][k] == e.getDatum()[k])); - el && (pickElements = [el]); + if (datum.length > 1) { + const datumTemp = (datum as Datum[]).slice(); + + elements.forEach(e => { + const elDatum = getDatumOfElement(e, m, s, r); + // eslint-disable-next-line max-nested-callbacks, eqeqeq + const index = elDatum && datumTemp.findIndex(d => keys.every(k => d[k] == elDatum[k])); + if (index >= 0) { + datumTemp.splice(index, 1); + + pickElements.push(e); + allElements.push(e); + opt.callback && opt.callback(e, m, s, r); + } + }); + } else { + const el = elements.find(e => { + const elDatum = getDatumOfElement(e, m, s, r); + // eslint-disable-next-line eqeqeq + return elDatum && keys.every(k => (datum as Datum[])[0][k] == elDatum[k]); + }); + + if (el) { + pickElements.push(el); + allElements.push(el); + opt.callback && opt.callback(el, m, s, r); + } + } } } - pickElements.forEach(element => { - r.interaction.startInteraction(stateKey, element); - }); - } + }); }); - }); - if (checkReverse) { - r.interaction.reverseEventElement(stateKey); + + opt.regionCallback && opt.regionCallback(pickElements, r); + }); + + return allElements; + } + + protected _setStateInDatum( + stateKey: string, + checkReverse: boolean, + datum: MaybeArray | null, + filter?: (series: ISeries, mark: IMark) => boolean, + region?: IRegionQuerier + ) { + this.filterGraphicsByDatum(datum, { + filter, + region, + regionCallback: (elements, r) => { + if (!datum) { + r.interaction.clearEventElement(stateKey, true); + } else if (elements.length) { + elements.forEach(e => { + r.interaction.startInteraction(stateKey, e); + }); + + if (checkReverse) { + r.interaction.reverseEventElement(stateKey); + } + } } }); } diff --git a/packages/vchart/src/chart/interface/chart.ts b/packages/vchart/src/chart/interface/chart.ts index 718fed8b45..cd66a63d2b 100644 --- a/packages/vchart/src/chart/interface/chart.ts +++ b/packages/vchart/src/chart/interface/chart.ts @@ -1,6 +1,6 @@ import type { IEvent } from '../../event/interface'; import type { LayoutCallBack } from '../../layout/interface'; -import type { IView } from '@visactor/vgrammar-core'; +import type { IElement, IView } from '@visactor/vgrammar-core'; import type { IParserOptions } from '@visactor/vdataset'; import type { IComponent, IComponentConstructor } from '../../component/interface'; import type { IMark } from '../../mark/interface'; @@ -210,6 +210,20 @@ export interface IChart extends ICompilable { getSeriesData: (id: StringOrNumber | undefined, index: number | undefined) => DataView | undefined; // setDimensionIndex setDimensionIndex: (value: StringOrNumber, opt: DimensionIndexOption) => void; + /** + * 根据数据筛选图元 + * @since 1.13.9 + */ + filterGraphicsByDatum: ( + datum: MaybeArray | null, + opt?: { + filter?: (series: ISeries, mark: IMark) => boolean; + region?: IRegionQuerier; + getDatum?: (el: IElement, mark: IMark, s: ISeries, r: IRegion) => Datum; + callback?: (el: IElement, mark: IMark, s: ISeries, r: IRegion) => void; + regionCallback?: (pickElements: IElement[], r: IRegion) => void; + } + ) => IElement[]; } export interface IChartSpecTransformer { diff --git a/packages/vchart/src/chart/sankey/sankey.ts b/packages/vchart/src/chart/sankey/sankey.ts index 80f8e3f5c0..1827b2ecf9 100644 --- a/packages/vchart/src/chart/sankey/sankey.ts +++ b/packages/vchart/src/chart/sankey/sankey.ts @@ -9,7 +9,7 @@ import type { Datum, MaybeArray } from '../../typings/common'; import type { ISeries } from '../../series/interface'; import type { IMark } from '../../mark/interface/common'; import type { IRegionQuerier } from '../../typings/params'; -import { isArray, isFunction } from '@visactor/vutils'; +import { isArray } from '@visactor/vutils'; import { loadScrollbar } from '@visactor/vrender-components'; export class SankeyChart extends BaseChart { @@ -29,57 +29,41 @@ export class SankeyChart extends ) { // 桑基图暂时只支持单选 const activeDatum = isArray(datum) ? datum[0] : datum; - const keys = !activeDatum ? null : Object.keys(activeDatum); - this.getRegionsInQuerier(region).forEach(r => { - if (!activeDatum) { - r.interaction.clearEventElement(stateKey, true); - return; - } - let hasPick = false; - r.getSeries().forEach(s => { - let activeNodeOrLink = null; - - s.getMarksWithoutRoot().forEach(m => { - if (m.type === 'text') { - return; - } - - let pickElement = null; - const mark = m.getProduct(); - if (!mark) { - return; - } - if (!filter || (isFunction(filter) && filter(s, m))) { - pickElement = mark.elements.find((e: any) => - keys.every(k => { - let datum = e.getDatum()?.datum; + const markFilter = (series: ISeries, mark: IMark) => { + return mark.type !== 'text' && mark.getProduct() && (!filter || filter(series, mark)); + }; - if (isArray(datum)) { - // data of link - datum = datum[0]; - } + this.filterGraphicsByDatum(activeDatum, { + filter: markFilter, + region, + getDatum: e => { + let d = e.getDatum()?.datum; - // eslint-disable-next-line eqeqeq - return activeDatum[k] == datum?.[k]; - }) - ); - } - if (pickElement) { - hasPick = true; - r.interaction.startInteraction(stateKey, pickElement); + if (isArray(d)) { + // data of link + d = d[0]; + } + return d; + }, + callback: (element, mark, s, r) => { + const id = mark.getProduct()?.id(); + if (id && (id.includes('node') || id.includes('link'))) { + (s as any)._handleEmphasisElement?.({ item: element }); + } + }, + regionCallback: (elements, r) => { + if (!activeDatum) { + r.interaction.clearEventElement(stateKey, true); + return; + } else if (elements.length) { + elements.forEach(e => { + r.interaction.startInteraction(stateKey, e); + }); - if (mark.id().includes('node') || mark.id().includes('link')) { - activeNodeOrLink = pickElement; - } + if (checkReverse) { + r.interaction.reverseEventElement(stateKey); } - }); - - if (activeNodeOrLink) { - (s as any)._handleEmphasisElement?.({ item: activeNodeOrLink }); } - }); - if (checkReverse && hasPick) { - r.interaction.reverseEventElement(stateKey); } }); }