-
Notifications
You must be signed in to change notification settings - Fork 605
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sankey): remove the circle data in sankey (#2178)
* feat(sankey): remove the circle data in sankey * test(sankey): add plot test of sankey with circle * chore: add warn log for remove data * fix(sankey): when node has multi parent node
- Loading branch information
Showing
4 changed files
with
181 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { cutoffCircle } from '../../../../src/plots/sankey/circle'; | ||
import { ENERGY_RELATIONS } from '../../../data/sankey-energy'; | ||
|
||
describe('sankey ', () => { | ||
it('cutoffCircle', () => { | ||
let data = [ | ||
{ source: 'a', target: 'b' }, | ||
{ source: 'b', target: 'c' }, | ||
{ source: 'a', target: 'c' }, | ||
{ source: 'c', target: 'd' }, | ||
]; | ||
|
||
// 不成环 | ||
expect(cutoffCircle(data, 'source', 'target')).toEqual([ | ||
{ source: 'a', target: 'b' }, | ||
{ source: 'b', target: 'c' }, | ||
{ source: 'a', target: 'c' }, | ||
{ source: 'c', target: 'd' }, | ||
]); | ||
|
||
// 两节点环 | ||
data = [ | ||
{ source: 'a', target: 'b' }, | ||
{ source: 'b', target: 'a' }, | ||
]; | ||
|
||
expect(cutoffCircle(data, 'source', 'target')).toEqual([{ source: 'a', target: 'b' }]); | ||
|
||
// 三节点环 | ||
data = [ | ||
{ source: 'a', target: 'b' }, | ||
{ source: 'b', target: 'c' }, | ||
{ source: 'c', target: 'a' }, | ||
]; | ||
|
||
expect(cutoffCircle(data, 'source', 'target')).toEqual([ | ||
{ source: 'a', target: 'b' }, | ||
{ source: 'b', target: 'c' }, | ||
]); | ||
|
||
// 多个环 | ||
data = [ | ||
{ source: 'a', target: 'b' }, | ||
{ source: 'b', target: 'c' }, | ||
{ source: 'c', target: 'a' }, | ||
{ source: 'a', target: 'd' }, | ||
{ source: 'd', target: 'e' }, | ||
{ source: 'e', target: 'a' }, | ||
]; | ||
|
||
expect(cutoffCircle(data, 'source', 'target')).toEqual([ | ||
{ source: 'a', target: 'b' }, | ||
{ source: 'b', target: 'c' }, | ||
{ source: 'a', target: 'd' }, | ||
{ source: 'd', target: 'e' }, | ||
]); | ||
|
||
// 一条边产生两个环 | ||
data = [ | ||
{ source: 'a', target: 'b' }, | ||
{ source: 'b', target: 'c' }, | ||
{ source: 'c', target: 'a' }, // 它带来两个环 | ||
{ source: 'a', target: 'd' }, | ||
{ source: 'd', target: 'c' }, | ||
]; | ||
|
||
expect(cutoffCircle(data, 'source', 'target')).toEqual([ | ||
{ source: 'a', target: 'b' }, | ||
{ source: 'b', target: 'c' }, | ||
{ source: 'a', target: 'd' }, | ||
{ source: 'd', target: 'c' }, | ||
]); | ||
|
||
// 节点多个父 | ||
data = [ | ||
{ source: 'a', target: 'c' }, | ||
{ source: 'b', target: 'c' }, | ||
{ source: 'c', target: 'a' }, | ||
]; | ||
|
||
expect(cutoffCircle(data, 'source', 'target')).toEqual([ | ||
{ source: 'a', target: 'c' }, | ||
{ source: 'b', target: 'c' }, | ||
]); | ||
|
||
// 稍微正式一点的数据 | ||
expect(cutoffCircle(ENERGY_RELATIONS, 'source', 'target')).toEqual(ENERGY_RELATIONS); | ||
expect(cutoffCircle(ENERGY_RELATIONS, 'source', 'target')).not.toBe(ENERGY_RELATIONS); | ||
|
||
// 空数据 | ||
expect(cutoffCircle(null, 'source', 'target')).toEqual([]); | ||
expect(cutoffCircle(undefined, 'source', 'target')).toEqual([]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { each, size } from '@antv/util'; | ||
import { Data, Datum } from '../../types'; | ||
|
||
/** | ||
* 是否有环的判断依据是,当前 source 对应的 target 是 source 的父节点 | ||
* @param circleCache | ||
* @param source | ||
* @param target | ||
*/ | ||
function hasCircle(circleCache: Map<string, string[]>, source: string[], target: string): boolean { | ||
// 父元素为空,则表示已经到头了! | ||
if (size(source) === 0) return false; | ||
// target 在父元素路径上,所以形成环 | ||
if (source.includes(target)) return true; | ||
|
||
// 递归 | ||
return source.some((s: string) => hasCircle(circleCache, circleCache.get(s), target)); | ||
} | ||
|
||
/** | ||
* 切断桑基图数据中的环(会丢失数据),保证顺序 | ||
* @param data | ||
* @param sourceField | ||
* @param targetField | ||
*/ | ||
export function cutoffCircle(data: Data, sourceField: string, targetField: string): Data { | ||
const dataWithoutCircle = []; | ||
const removedData = []; | ||
|
||
/** 存储父子关系的链表关系,具体是 子 -> 父 数组 */ | ||
const circleCache = new Map<string, string[]>(); | ||
|
||
each(data, (d: Datum) => { | ||
const source = d[sourceField] as string; | ||
const target = d[targetField] as string; | ||
|
||
// 当前数据,不成环 | ||
if (!hasCircle(circleCache, [source], target)) { | ||
// 保留数据 | ||
dataWithoutCircle.push(d); | ||
// 存储关系链表 | ||
if (!circleCache.has(target)) { | ||
circleCache.set(target, []); | ||
} | ||
circleCache.get(target).push(source); | ||
} else { | ||
// 保存起来用于打印 log | ||
removedData.push(d); | ||
} | ||
}); | ||
|
||
if (removedData.length !== 0) { | ||
console.warn(`sankey data contains circle, ${removedData.length} records removed.`, removedData); | ||
} | ||
|
||
return dataWithoutCircle; | ||
} |