-
Notifications
You must be signed in to change notification settings - Fork 221
fix: update VennSeries to handle empty keys and filter valid legend keys #4000
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
6c11290
89835ef
af504a7
141af8a
d1e50cf
6bc32a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,8 @@ | ||
| import { array } from '@visactor/vutils'; | ||
|
|
||
| export const getVennSeriesDataKey = (sets: string | string[]) => { | ||
| export const getVennSeriesDataKey = (sets: string | string[], emptysetKey?: string) => { | ||
| if (!sets || (Array.isArray(sets) && sets.length === 0)) { | ||
| return emptysetKey || 'others'; | ||
| } | ||
| return array(sets).join(','); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,7 +36,6 @@ export class VennSeries<T extends IVennSeriesSpec = IVennSeriesSpec> extends Bas | |
|
|
||
| static readonly mark: SeriesMarkMap = vennSeriesMark; | ||
| static readonly builtInTheme = { venn }; | ||
|
|
||
| static readonly transformerConstructor = VennSeriesSpecTransformer; | ||
| readonly transformerConstructor = VennSeriesSpecTransformer; | ||
|
|
||
|
|
@@ -67,11 +66,21 @@ export class VennSeries<T extends IVennSeriesSpec = IVennSeriesSpec> extends Bas | |
| return this._valueField; | ||
| } | ||
|
|
||
| protected _emptySetKey!: string; | ||
| getEmptySetKey() { | ||
| return this._emptySetKey; | ||
| } | ||
| setEmptySetKey(f: string): string { | ||
| this._emptySetKey = f; | ||
| return this._emptySetKey; | ||
| } | ||
|
|
||
| setAttrFromSpec(): void { | ||
| super.setAttrFromSpec(); | ||
| this.setCategoryField(this._spec.categoryField ?? 'sets'); | ||
| this.setValueField(this._spec.valueField ?? 'size'); | ||
| this.setSeriesField(this._spec.seriesField ?? DEFAULT_DATA_KEY); | ||
| this.setEmptySetKey(this._spec.emptySetKey ?? 'others'); | ||
| } | ||
|
|
||
| initData() { | ||
|
|
@@ -210,7 +219,13 @@ export class VennSeries<T extends IVennSeriesSpec = IVennSeriesSpec> extends Bas | |
| { | ||
| x: datum => (datum as IVennCircleDatum).labelX, | ||
| y: datum => (datum as IVennCircleDatum).labelY, | ||
| text: datum => getVennSeriesDataKey((datum as IVennCircleDatum).sets), | ||
| text: datum => { | ||
| const sets = (datum as IVennOverlapDatum).sets; | ||
| if (!sets || (Array.isArray(sets) && sets.length === 0)) { | ||
| return ''; | ||
| } | ||
| return getVennSeriesDataKey(sets); | ||
| }, | ||
| maxLineWidth: (datum: any) => { | ||
| const { x, radius, labelX } = datum as IVennCircleDatum; | ||
| const circleX0 = x - radius; | ||
|
|
@@ -234,7 +249,10 @@ export class VennSeries<T extends IVennSeriesSpec = IVennSeriesSpec> extends Bas | |
| { | ||
| x: datum => (datum as IVennOverlapDatum).labelX, | ||
| y: datum => (datum as IVennOverlapDatum).labelY, | ||
| text: datum => getVennSeriesDataKey((datum as IVennOverlapDatum).sets) | ||
| text: datum => { | ||
| const sets = (datum as IVennOverlapDatum).sets; | ||
| return getVennSeriesDataKey(sets); | ||
| } | ||
| }, | ||
| STATE_VALUE_ENUM.STATE_NORMAL, | ||
| AttributeLevel.Series | ||
|
|
@@ -310,7 +328,7 @@ export class VennSeries<T extends IVennSeriesSpec = IVennSeriesSpec> extends Bas | |
| protected _getSeriesInfo(field: string, keys: string[]) { | ||
| const defaultShapeType = this.getDefaultShapeType(); | ||
| return keys.map(originalKey => { | ||
| const dataKey = getVennSeriesDataKey(originalKey); | ||
| const dataKey = getVennSeriesDataKey(originalKey, this._emptySetKey); | ||
| return { | ||
| key: dataKey, | ||
| originalKey, | ||
|
|
@@ -324,7 +342,7 @@ export class VennSeries<T extends IVennSeriesSpec = IVennSeriesSpec> extends Bas | |
|
|
||
| getSeriesFieldValue(datum: Datum, seriesField?: string) { | ||
| const value = super.getSeriesFieldValue(datum, seriesField); | ||
| return getVennSeriesDataKey(value); | ||
| return getVennSeriesDataKey(value, this._emptySetKey); | ||
| } | ||
|
|
||
| legendSelectedFilter(component: ILegend, selectedKeys: StringOrNumber[]) { | ||
|
|
@@ -339,27 +357,38 @@ export class VennSeries<T extends IVennSeriesSpec = IVennSeriesSpec> extends Bas | |
| if (selectedKeys.length === originalLegendKeys.length) { | ||
| return selectedKeys; | ||
| } | ||
|
|
||
| // 找到缺失的项 | ||
| const selectedFilter = {}; | ||
| selectedKeys.forEach(s => { | ||
| selectedFilter[s] = true; | ||
| }); | ||
| const disableKeys = originalLegendKeys.filter(key => !selectedFilter[getVennSeriesDataKey(key)]); | ||
|
|
||
| // 找到缺失的项的派生项(如 “A&B” 的派生项 “A&B&C”) | ||
| const derivedDisableKeys = originalLegendKeys.filter(key => { | ||
| if (disableKeys.includes(key)) { | ||
| return false; | ||
| } | ||
| return disableKeys.some(disableKey => array(disableKey).every(k => key.includes(k))); | ||
| }); | ||
|
|
||
| // 将派生项从 selectedKeys 中移除 | ||
| selectedKeys = selectedKeys.slice(); | ||
| derivedDisableKeys.forEach(key => { | ||
| selectedKeys.splice(selectedKeys.indexOf(getVennSeriesDataKey(key)), 1); | ||
| }); | ||
| const emptyKey = this._emptySetKey; | ||
|
|
||
| const hasEmpty = selectedKeys.includes(emptyKey); | ||
| const nonEmpty = selectedKeys.filter(key => key !== emptyKey); | ||
|
|
||
| if (nonEmpty.length > 0) { | ||
| // 过滤出非空的原始图例键 | ||
| const validKeys = originalLegendKeys.filter(key => getVennSeriesDataKey(key, this._emptySetKey) !== emptyKey); | ||
| // 找到缺失的项 | ||
| const selectedFilter: Record<StringOrNumber, boolean> = {}; | ||
| selectedKeys.forEach(s => { | ||
| selectedFilter[s] = true; | ||
| }); | ||
| const disableKeys = validKeys.filter(key => !selectedFilter[getVennSeriesDataKey(key, this._emptySetKey)]); | ||
|
|
||
| // 找到缺失的项的派生项(如 “A&B” 的派生项 “A&B&C”) | ||
| const derivedDisableKeys = validKeys.filter(key => { | ||
| if (disableKeys.includes(key)) { | ||
| return false; | ||
| } | ||
| return disableKeys.some(disableKey => array(disableKey).every(k => key.includes(k))); | ||
| }); | ||
|
|
||
| // 将派生项从 nonEmpty 中移除 | ||
| selectedKeys = nonEmpty.slice(); | ||
| derivedDisableKeys.forEach(key => { | ||
| selectedKeys.splice(selectedKeys.indexOf(getVennSeriesDataKey(key, this._emptySetKey)), 1); | ||
| }); | ||
| } | ||
| if (hasEmpty) { | ||
| selectedKeys.push(emptyKey); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] 请避免仅选空集合时重复追加选中项并修改入参 当 可在配置了 // data.values
[
{ sets: [], value: 6 },
{ sets: ['A'], value: 8 }
];
// 渲染后仅选中空集合
const selected = ['others'];
chart.setLegendSelectedDataByIndex(0, selected);预期仅保留空集合,且 麻烦在 Venn 筛选器中始终构造独立的结果数组,确保空集合项最多加入一次,并补充两项图例下的单选回归测试。根因是这里生成了重复的选中项,应在此处修正。 |
||
| } | ||
| } | ||
| return selectedKeys; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] 请依据原始集合结构识别空集合,避免误伤普通 others 集合
这里通过名称是否等于
emptyKey过滤原始图例键,会把合法的普通集合['others']也当作空集合处理,即使数据中根本没有sets: []。在配置了
categoryField: 'sets'、valueField: 'value'、seriesField: 'sets'和可见图例的普通 Venn 图中,使用以下数据:渲染后取消选中
others,对应公共 API 调用:预期隐藏
others及依赖它的交集,仅保留 A。当前实现将['others']从validKeys中排除,导致交集未被禁用。布局收到缺少组成集合的交集后,在greedyLayout抛出TypeError: Cannot read property 'size' of undefined。同一数据和操作在 develop4a51ea6a20d5对照中通过,属于本 PR 引入的回归。麻烦根据原始
sets是否为空来识别空集合,并区分空集合身份与显示名称,避免把普通集合名others隐式变为保留名称。请补充这一场景的回归测试。