diff --git a/__tests__/unit/plots/sunburst/field-spec.ts b/__tests__/unit/plots/sunburst/field-spec.ts new file mode 100644 index 0000000000..dfcc4c6dec --- /dev/null +++ b/__tests__/unit/plots/sunburst/field-spec.ts @@ -0,0 +1,39 @@ +import { Sunburst } from '../../../../src'; +import { createDiv } from '../../../utils/dom'; +import { SIMPLE_SUNBURST_DATA } from '../../../data/sunburst'; + +describe('sunburst: 字段信', () => { + const div = createDiv(); + const plot = new Sunburst(div, { + data: SIMPLE_SUNBURST_DATA, + }); + plot.render(); + + it('节点位置索引:nodeIndex', () => { + const data = plot.chart.getData(); + + expect(data[0].nodeIndex).toBe(0); + expect(data[1].nodeIndex).toBe(1); + + expect(data[3].nodeIndex).toBe(0); + }); + + it('儿子节点数量:childNodeCount', () => { + const data = plot.chart.getData(); + + expect(data[0].childNodeCount).toBe( + SIMPLE_SUNBURST_DATA.children.find((c) => c.name === data[0].name).children.length + ); + }); + + it('组件节点: ancestors', () => { + const data = plot.chart.getData(); + + expect(data[0][Sunburst.NODE_ANCESTORS_FIELD].length).toBe(0); + expect(data[3][Sunburst.NODE_ANCESTORS_FIELD].length).toBe(1); + }); + + afterAll(() => { + plot.destroy(); + }); +}); diff --git a/__tests__/unit/utils/hierarchy/util-spec.ts b/__tests__/unit/utils/hierarchy/util-spec.ts index 549d42bf0e..6b80720f9b 100644 --- a/__tests__/unit/utils/hierarchy/util-spec.ts +++ b/__tests__/unit/utils/hierarchy/util-spec.ts @@ -67,7 +67,7 @@ describe('hierarchy/util', () => { expect(getAllNodes({ a: 1 })).toEqual([]); - const nodes = ['a', 'b', 'c']; + const nodes = [{ name: 'a' }, { name: 'b' }, { name: 'c' }]; expect( getAllNodes({ each: (loop) => nodes.forEach(loop), diff --git a/docs/api/plots/sunburst.en.md b/docs/api/plots/sunburst.en.md index b318fa09be..f7fb15bc83 100644 --- a/docs/api/plots/sunburst.en.md +++ b/docs/api/plots/sunburst.en.md @@ -35,7 +35,19 @@ type Node = { name: string; value?: number; children: Node[]; } `markdown:docs/common/meta.en.md` -旭日图内含的数据字段有:Sunburst.SUNBURST_PATH_FIELD, Sunburst.SUNBURST_ANCESTOR_FIELD, depth, height,这些字段可以在元数据中获取(tooltip、style 回调中使用). +旭日图内含的数据字段有: + +| Field key | Description of field | Type of value | +| --- | --- | --- | +|`Sunburst.SUNBURST_PATH_FIELD`| Path of current node, up the tree to the least common ancestor, and back down to the given node |_string_ | +|`Sunburst.SUNBURST_ANCESTOR_FIELD`| Ancestor node of current node | _string_ | +|`Sunburst.NODE_ANCESTORS_FIELD`| Ancestor nodes of current node |_object[]_ | +|`nodeIndex`| Index of nodes at the same level |_number_ | +| `childNodeCount` | Counts of current node's childNodes |_number_ | +|`depth`| |_number_ | +|`height`| | _number_ | + +这些字段可以在元数据中获取(tooltip、style 回调中使用). 可以通过下面的方式来设置字段的元信息: @@ -78,14 +90,14 @@ Hierarchy configuration, such as' size ', 'padding', etc., refer to [D3-Hierarch 支持配置属性: -| Properties | Type | Description | -| ---------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | -| field | _string_ | 数据节点权重映射字段,默认为:`value`. 当你的节点数据格式不是:`{ name: 'xx', value: 'xx' }`, 可以通过该字段来指定,详细见:图表示例 | -| padding | _number\|number[]_ | 默认:`0`。参考:[d3-hierarchy#partition_padding](https://github.com/d3/d3-hierarchy#partition_padding) | -| size | _number[]_ | 默认:`[1, 1]`。参考:[d3-hierarchy#partition_size](https://github.com/d3/d3-hierarchy#partition_size) | -| round | _boolean_ | 默认:`false`。参考:[d3-hierarchy#partition_round](https://github.com/d3/d3-hierarchy#partition_round) | -| sort | _Function_ | 数据节点排序方式,默认:降序。参考: [d3-hierarchy#node_sort](https://github.com/d3/d3-hierarchy#node_sort) | -| ignoreParentValue | _boolean_ | 是否忽略 parentValue, 默认:true。 当设置为 true 时,父节点的权重由子元素决定 | +| Properties | Type | Description | +| ----------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | +| field | _string_ | 数据节点权重映射字段,默认为:`value`. 当你的节点数据格式不是:`{ name: 'xx', value: 'xx' }`, 可以通过该字段来指定,详细见:图表示例 | +| padding | _number\|number[]_ | 默认:`0`。参考:[d3-hierarchy#partition_padding](https://github.com/d3/d3-hierarchy#partition_padding) | +| size | _number[]_ | 默认:`[1, 1]`。参考:[d3-hierarchy#partition_size](https://github.com/d3/d3-hierarchy#partition_size) | +| round | _boolean_ | 默认:`false`。参考:[d3-hierarchy#partition_round](https://github.com/d3/d3-hierarchy#partition_round) | +| sort | _Function_ | 数据节点排序方式,默认:降序。参考: [d3-hierarchy#node_sort](https://github.com/d3/d3-hierarchy#node_sort) | +| ignoreParentValue | _boolean_ | 是否忽略 parentValue, 默认:true。 当设置为 true 时,父节点的权重由子元素决定 | #### radius diff --git a/docs/api/plots/sunburst.zh.md b/docs/api/plots/sunburst.zh.md index b1ad981c33..43921e076e 100644 --- a/docs/api/plots/sunburst.zh.md +++ b/docs/api/plots/sunburst.zh.md @@ -35,7 +35,17 @@ type Node = { name: string; value?: number; children: Node[]; } `markdown:docs/common/meta.zh.md` -旭日图内含的数据字段有:Sunburst.SUNBURST_PATH_FIELD, Sunburst.SUNBURST_ANCESTOR_FIELD, depth, height,这些字段可以在元数据中获取(tooltip、style 回调中使用). +旭日图内含的数据字段有: + +| 字段 | 字段描述 | 字段值类型 | +| --- | --- | --- | +|`Sunburst.SUNBURST_PATH_FIELD`| 节点的路径信息 |_string_ | +|`Sunburst.SUNBURST_ANCESTOR_FIELD`| 当前节点的祖先节点 | _string_ | +|`Sunburst.NODE_ANCESTORS_FIELD`| 当前节点的祖先节点列表 |_object[]_ | +|`nodeIndex`| 当前节点在同一父节点下的所有节点中的索引顺序 |_number_ | +| `childNodeCount` | 当前节点的儿子节点数 |_number_ | +|`depth`| |_number_ | +|`height`| | _number_ | 可以通过下面的方式来设置字段的元信息: diff --git a/examples/more-plots/sunburst/demo/meta.json b/examples/more-plots/sunburst/demo/meta.json index 6df4db30f9..a1e61ed77b 100644 --- a/examples/more-plots/sunburst/demo/meta.json +++ b/examples/more-plots/sunburst/demo/meta.json @@ -19,7 +19,6 @@ "en": "Custom hierarchy field of value" }, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/yBxQkbPBXF/2a71c7de-1971-4651-bcba-dd18ddd8732e.png" - }, { "filename": "label.ts", @@ -28,7 +27,6 @@ "en": "Set label layout" }, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/nUWhQ3lWeT/ab0dd09f-f735-41ab-bb13-f38e3354ed62.png" - }, { "filename": "color-field.ts", @@ -37,7 +35,6 @@ "en": "Color of sunburst plot" }, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/DCwOWeZD5T/cbbbe84d-a0ff-4602-ab3e-22429e6a1719.png" - }, { "filename": "color.ts", @@ -46,7 +43,6 @@ "en": "Color of sunburst plot" }, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/IvLI4di5ww/7faf7005-44f2-4832-bbca-227b039f4260.png" - }, { "filename": "style.ts", @@ -54,17 +50,16 @@ "zh": "自定义旭日图样式", "en": "Style of sunburst plot" }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/aAO3F8JuLP/83d5b6fe-e789-4395-a48b-6992259eb872.png" - + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/hWIcBgVYMI/d265c8b0-2184-48aa-8610-173cb0b065a8.png", + "new": true }, { "filename": "tooltip-fields.ts", "title": { - "zh": "自定义 Tooltip fields", - "en": "Custom tooltip fields" + "zh": "指定父节点权重", + "en": "Specify weight of parentNode" }, - "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/IGdW4zjTce/e515a753-d3a2-49d7-90e0-89786501db82.png" - + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/D6hdl%24%26RiT/72411396-daf0-4d9e-bb54-d117f159cb44.png" }, { "filename": "custom-tooltip-items.ts", @@ -73,7 +68,6 @@ "en": "Custom tooltip items" }, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/yBxQkbPBXF/2a71c7de-1971-4651-bcba-dd18ddd8732e.png" - } ] } diff --git a/examples/more-plots/sunburst/demo/style.ts b/examples/more-plots/sunburst/demo/style.ts index 80adc2950d..0812ad2fdc 100644 --- a/examples/more-plots/sunburst/demo/style.ts +++ b/examples/more-plots/sunburst/demo/style.ts @@ -1,8 +1,35 @@ import { Sunburst } from '@antv/g2plot'; +import { last } from '@antv/util'; +import chromaJs from 'chroma-js'; fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/sunburst.json') .then((res) => res.json()) .then((data) => { + const colors = [ + '#5B8FF9', + '#61DDAA', + '#65789B', + '#F6BD16', + '#7262fd', + '#78D3F8', + '#9661BC', + '#F6903D', + '#008685', + '#F08BB4', + ]; + function getPaletteByColor(color, count) { + const origin = chromaJs(color); + const range = [origin.brighten(0.5), origin, origin.darken(0.5)]; + return ( + chromaJs + // @ts-ignore + .scale(range) + .mode('lab') + .cache(false) + .colors(count) + ); + } + const plot = new Sunburst('container', { data, innerRadius: 0.3, @@ -11,10 +38,23 @@ fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/sunburst.json') field: 'sum', }, sunburstStyle: (datum) => { + const depth = datum.depth; + const nodeIndex = datum.nodeIndex; + + const ancestorIndex = last(datum[Sunburst.NODE_ANCESTORS_FIELD])?.nodeIndex || 0; + + const colorIndex = depth === 1 ? nodeIndex : ancestorIndex; + let color = colors[colorIndex % colors.length]; + + if (depth > 1) { + const newColors = getPaletteByColor(color, last(datum[Sunburst.NODE_ANCESTORS_FIELD])?.childNodeCount); + color = newColors[nodeIndex % colors.length]; + } + return { - // 节点层级不大于 10 - fillOpacity: 0.75 - datum.depth / 10, - strokeOpacity: 1 - datum.depth / 10, + fill: color, + stroke: '#fff', + lineWidth: 0.5, }; }, }); diff --git a/gatsby-browser.js b/gatsby-browser.js index 7a3b0ba1fc..fd2b8c916d 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -5,5 +5,6 @@ window.G = require('@antv/g-canvas'); window.react = require('react'); window.reactDom = require('react-dom'); window.antd = require('antd'); +window.chromaJs = require('chroma-js'); require('antd/lib/alert/style/index.css'); diff --git a/package.json b/package.json index 17f692dd6b..396f342cbd 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ }, "devDependencies": { "@antv/data-set": "^0.11.5", - "@antv/gatsby-theme-antv": "1.1.6-beta.5", + "@antv/gatsby-theme-antv": "1.1.6-beta.6", "@babel/core": "^7.10.4", "@babel/plugin-transform-runtime": "^7.11.5", "@babel/preset-env": "^7.10.4", @@ -78,6 +78,7 @@ "all-contributors-cli": "^6.20.0", "antd": "^4.8.4", "babel-loader": "^8.1.0", + "chroma-js": "^2.1.2", "conventional-changelog-cli": "^2.0.34", "cross-env": "^7.0.2", "eslint": "^6.1.0", diff --git a/src/plots/sunburst/constant.ts b/src/plots/sunburst/constant.ts index 42a3f056e2..a8718c0e01 100644 --- a/src/plots/sunburst/constant.ts +++ b/src/plots/sunburst/constant.ts @@ -1,5 +1,6 @@ import { Plot } from '../../core/plot'; import { deepAssign } from '../../utils'; +import { CHILD_NODE_COUNT, NODE_ANCESTORS_FIELD, NODE_INDEX_FIELD } from '../../utils/hierarchy/util'; import { SunburstOptions } from './types'; /** @@ -10,7 +11,15 @@ export const SUNBURST_Y_FIELD = 'value'; export const SUNBURST_PATH_FIELD = 'path'; /** 默认的源字段 */ -export const RAW_FIELDS = [SUNBURST_PATH_FIELD, 'name', 'depth', 'height']; +export const RAW_FIELDS = [ + SUNBURST_PATH_FIELD, + NODE_INDEX_FIELD, + NODE_ANCESTORS_FIELD, + CHILD_NODE_COUNT, + 'name', + 'depth', + 'height', +]; /** * 旭日图 默认配置项 diff --git a/src/plots/sunburst/index.ts b/src/plots/sunburst/index.ts index 76a6f3b066..5ac2942a47 100644 --- a/src/plots/sunburst/index.ts +++ b/src/plots/sunburst/index.ts @@ -1,5 +1,6 @@ import { Plot } from '../../core/plot'; import { Adaptor } from '../../core/adaptor'; +import { NODE_ANCESTORS_FIELD } from '../../utils/hierarchy/util'; import { SunburstOptions } from './types'; import { adaptor } from './adaptor'; import { DEFAULT_OPTIONS, SUNBURST_ANCESTOR_FIELD, SUNBURST_PATH_FIELD } from './constant'; @@ -20,6 +21,8 @@ export class Sunburst extends Plot { static SUNBURST_ANCESTOR_FIELD = SUNBURST_ANCESTOR_FIELD; /** 旭日图 节点的路径 */ static SUNBURST_PATH_FIELD = SUNBURST_PATH_FIELD; + /** 节点的祖先节点 */ + static NODE_ANCESTORS_FIELD = NODE_ANCESTORS_FIELD; /** 图表类型 */ public type: string = 'sunburst'; diff --git a/src/utils/hierarchy/util.ts b/src/utils/hierarchy/util.ts index a85385d2d6..79586c5d32 100644 --- a/src/utils/hierarchy/util.ts +++ b/src/utils/hierarchy/util.ts @@ -1,4 +1,12 @@ -import { isArray, isString } from '@antv/util'; +import { isArray, isString, filter } from '@antv/util'; + +/** export 一些字段常量 */ +/** 在同层级,同一父节点下的节点索引顺序 */ +export const NODE_INDEX_FIELD = 'nodeIndex'; +/** child 节点数量 */ +export const CHILD_NODE_COUNT = 'childNodeCount'; +/** 节点的祖先节点 */ +export const NODE_ANCESTORS_FIELD = 'nodeAncestor'; const INVALID_FIELD_ERR_MSG = 'Invalid field: it must be a string!'; @@ -32,8 +40,25 @@ export function getField(options: Options, defaultField?: string): string { export function getAllNodes(root: any) { const nodes: any[] = []; if (root && root.each) { - // d3-hierarchy + let parent; + let index; + // d3-hierarchy: Invokes the specified function for node and each descendant in **breadth-first order** root.each((node: any) => { + if (node.parent !== parent) { + parent = node.parent; + index = 0; + } else { + index += 1; + } + const ancestors = filter( + (node.ancestors?.() || []).map((d: any) => nodes.find((n) => n.name === d.name) || d), + ({ depth }) => depth > 0 && depth < node.depth + ); + + node[NODE_ANCESTORS_FIELD] = ancestors; + node[CHILD_NODE_COUNT] = node.children?.length || 0; + node[NODE_INDEX_FIELD] = index; + nodes.push(node); }); } else if (root && root.eachNode) {