diff --git a/__tests__/data/antv-star.ts b/__tests__/data/antv-star.ts new file mode 100644 index 0000000000..d059e3740c --- /dev/null +++ b/__tests__/data/antv-star.ts @@ -0,0 +1,10 @@ +export const antvStar = [ + { name: 'X6', star: 297 }, + { name: 'G', star: 506 }, + { name: 'AVA', star: 805 }, + { name: 'G2Plot', star: 1478 }, + { name: 'L7', star: 2029 }, + { name: 'F2', star: 7346 }, + { name: 'G6', star: 7100 }, + { name: 'G2', star: 10178 }, +]; diff --git a/__tests__/unit/plots/radial-bar/axis-spec.ts b/__tests__/unit/plots/radial-bar/axis-spec.ts new file mode 100644 index 0000000000..bbbf8bc320 --- /dev/null +++ b/__tests__/unit/plots/radial-bar/axis-spec.ts @@ -0,0 +1,34 @@ +import { RadialBar } from '../../../../src'; +import { createDiv } from '../../../utils/dom'; +import { antvStar } from '../../../data/antv-star'; + +const xField = 'name'; +const yField = 'star'; + +describe('radial-bar axis', () => { + it('xAxis*yAxis', () => { + const bar = new RadialBar(createDiv(), { + width: 400, + height: 300, + data: antvStar, + xField, + yField, + yAxis: { + line: { + style: { + lineWidth: 1, + fill: 'red', + }, + }, + }, + }); + bar.render(); + const axisOptions = bar.chart.getOptions().axes; + expect(axisOptions[yField]).toBeUndefined(); + expect(axisOptions[xField]).toMatchObject({ + grid: null, + line: null, + tickLine: null, + }); + }); +}); diff --git a/__tests__/unit/plots/radial-bar/index-spec.ts b/__tests__/unit/plots/radial-bar/index-spec.ts new file mode 100644 index 0000000000..ad1074daca --- /dev/null +++ b/__tests__/unit/plots/radial-bar/index-spec.ts @@ -0,0 +1,51 @@ +import { RadialBar } from '../../../../src'; +import { createDiv } from '../../../utils/dom'; +import { antvStar } from '../../../data/antv-star'; + +const xField = 'name'; +const yField = 'star'; + +describe('radial-bar', () => { + it('xField*yField', () => { + const bar = new RadialBar(createDiv(), { + width: 400, + height: 300, + data: antvStar, + xField, + yField, + radius: 0.8, + innerRadius: 0.2, + xAxis: false, + }); + bar.render(); + const geometry = bar.chart.geometries[0]; + const positionFields = geometry.getAttribute('position').getFields(); + expect(geometry.type).toBe('interval'); + expect(geometry.coordinate.type).toBe('polar'); + expect(geometry.coordinate.innerRadius).toBe(0.2); + expect(geometry.coordinate.isTransposed).toBe(true); + expect(bar.chart.geometries[0].elements.length).toBe(antvStar.length); + expect(positionFields).toHaveLength(2); + expect(positionFields[0]).toBe(xField); + expect(positionFields[1]).toBe(yField); + }); + + it('xField*yField*color', () => { + const color = 'red'; + const bar = new RadialBar(createDiv(), { + width: 400, + height: 300, + data: antvStar, + xField, + yField, + color, + xAxis: false, + }); + + bar.render(); + const geometry = bar.chart.geometries[0]; + const colorFields = geometry.getAttribute('color').getFields(); + expect(colorFields).toHaveLength(1); + expect(colorFields[0]).toBe(color); + }); +}); diff --git a/__tests__/unit/plots/radial-bar/style-spec.ts b/__tests__/unit/plots/radial-bar/style-spec.ts new file mode 100644 index 0000000000..9cae16b6be --- /dev/null +++ b/__tests__/unit/plots/radial-bar/style-spec.ts @@ -0,0 +1,30 @@ +import { RadialBar } from '../../../../src'; +import { createDiv } from '../../../utils/dom'; +import { antvStar } from '../../../data/antv-star'; + +const xField = 'name'; +const yField = 'star'; + +describe('radial-bar style', () => { + it('bar styles', () => { + const bar = new RadialBar(createDiv(), { + width: 400, + height: 300, + data: antvStar, + xField, + yField, + barStyle: { + fill: 'red', + fillOpacity: 0.6, + cursor: 'pointer', + }, + }); + bar.render(); + const geometry = bar.chart.geometries[0]; + expect(geometry.elements[0].getModel().style).toMatchObject({ + fill: 'red', + fillOpacity: 0.6, + cursor: 'pointer', + }); + }); +}); diff --git a/__tests__/unit/plots/radial-bar/tooltip-spec.ts b/__tests__/unit/plots/radial-bar/tooltip-spec.ts new file mode 100644 index 0000000000..c7730558ff --- /dev/null +++ b/__tests__/unit/plots/radial-bar/tooltip-spec.ts @@ -0,0 +1,32 @@ +import { RadialBar } from '../../../../src'; +import { createDiv } from '../../../utils/dom'; +import { Datum } from '../../../../src/types'; +import { antvStar } from '../../../data/antv-star'; + +const xField = 'name'; +const yField = 'star'; + +describe('radial-bar tooltip', () => { + const formatter = (datum: Datum) => { + return { name: 'star', value: datum.percent * 100 + '%' }; + }; + it('tooltip default', () => { + const bar = new RadialBar(createDiv(), { + width: 400, + height: 300, + data: antvStar, + xField, + yField, + tooltip: { + formatter, + }, + }); + bar.render(); + // @ts-ignore + const tooltip = bar.chart.options.tooltip; + // @ts-ignore + expect(tooltip.showMarkers).toBe(false); + // @ts-ignore + expect(tooltip.formatter).toBe(formatter); + }); +}); diff --git a/__tests__/unit/plots/radial-bar/utils-spec.ts b/__tests__/unit/plots/radial-bar/utils-spec.ts new file mode 100644 index 0000000000..228d2c5858 --- /dev/null +++ b/__tests__/unit/plots/radial-bar/utils-spec.ts @@ -0,0 +1,14 @@ +import { getScaleMax } from '../../../../src/plots/radial-bar/utils'; +import { antvStar } from '../../../data/antv-star'; + +const yField = 'star'; + +describe('utils of radial-bar', () => { + const starArr = antvStar.map((item) => item[yField]); + const maxValue = Math.max(...starArr); + it('getScaleMax: normal', () => { + expect(getScaleMax(300, yField, antvStar)).toBe((maxValue * 360) / 300); + expect(getScaleMax(-300, yField, antvStar)).toBe((maxValue * 360) / 300); + expect(getScaleMax(660, yField, antvStar)).toBe((maxValue * 360) / 300); + }); +}); diff --git a/docs/manual/plots/radial-bar.en.md b/docs/manual/plots/radial-bar.en.md new file mode 100644 index 0000000000..4efa0baf18 --- /dev/null +++ b/docs/manual/plots/radial-bar.en.md @@ -0,0 +1,67 @@ +--- +title: 玉珏图 +order: 0 +--- + +## 配置属性 + +### 图表容器 + +`markdown:docs/common/chart-options.en.md` + +### 数据映射 + +#### data 📌 + +**必选**, _array object_ + +功能描述: 设置图表数据源 + +默认配置: 无 + +数据源为对象集合,例如:`[{ time: '1991',value: 20 }, { time: '1992',value: 30 }]`。 + +`markdown:docs/common/xy-field.en.md` + +`markdown:docs/common/meta.en.md` + +### 图形样式 + +#### radius + +**可选**, _string_ + +功能描述: 半径, 0 ~ 1。 + +默认配置: `1` + +#### innerRadius + +**可选**, _number_; + +功能描述: 内径,0 ~ 1。 + +#### maxAngle + +**可选**, _number_ + +功能描述: 最大旋转角度,由 data 中最大的数值决定,最大值是 360 度。 + +默认配置: 240 + + +#### barStyle + +**可选**, _StyleAttr | Function_ + +功能描述: 样式配置 。 + +默认配置: 无 + +`markdown:docs/common/shape-style.en.md` + +`markdown:docs/common/color.en.md` + +### 图表组件 + +`markdown:docs/common/component.en.md` diff --git a/docs/manual/plots/radial-bar.zh.md b/docs/manual/plots/radial-bar.zh.md new file mode 100644 index 0000000000..1d7f0e493c --- /dev/null +++ b/docs/manual/plots/radial-bar.zh.md @@ -0,0 +1,67 @@ +--- +title: 玉珏图 +order: 0 +--- + +## 配置属性 + +### 图表容器 + +`markdown:docs/common/chart-options.zh.md` + +### 数据映射 + +#### data 📌 + +**必选**, _array object_ + +功能描述: 设置图表数据源 + +默认配置: 无 + +数据源为对象集合,例如:`[{ time: '1991',value: 20 }, { time: '1992',value: 30 }]`。 + +`markdown:docs/common/xy-field.zh.md` + +`markdown:docs/common/meta.zh.md` + +### 图形样式 + +#### radius + +**可选**, _string_ + +功能描述: 半径, 0 ~ 1。 + +默认配置: `1` + +#### innerRadius + +**可选**, _number_; + +功能描述: 内径,0 ~ 1。 + +#### maxAngle + +**可选**, _number_ + +功能描述: 最大旋转角度,由 data 中最大的数值决定,最大值是 360 度。 + +默认配置: 240 + + +#### barStyle + +**可选**, _StyleAttr | Function_ + +功能描述: 样式配置 。 + +默认配置: 无 + +`markdown:docs/common/shape-style.zh.md` + +`markdown:docs/common/color.en.md` + +### 图表组件 + +`markdown:docs/common/component.zh.md` diff --git a/examples/radial-bar/basic/API.en.md b/examples/radial-bar/basic/API.en.md new file mode 100644 index 0000000000..0abc22d0e9 --- /dev/null +++ b/examples/radial-bar/basic/API.en.md @@ -0,0 +1,70 @@ +## 配置属性 + +### 图表容器 + +`markdown:docs/common/chart-options.en.md` + +### 数据映射 + +#### data 📌 + +**必选**, _array object_ + +功能描述: 设置图表数据源 + +默认配置: 无 + +数据源为对象集合,例如:`[{ time: '1991',value: 20 }, { time: '1992',value: 20 }]`。 + +`markdown:docs/common/meta.en.md` + + +#### colorField 📌 + +**可选**, _string_ + +功能描述: 颜色映射对应的数据字段名。 + +默认配置: 无 + +### 图形样式 + +#### radius + +**可选**, _string_ + +功能描述: 半径, 0 ~ 1。 + +默认配置: `1` + +#### innerRadius + +**可选**, _number_; + +功能描述: 内径,0 ~ 1。 + + +#### maxAngle + +**可选**, _number_ + +功能描述: 最大旋转角度,由 data 中最大的数值决定,最大值是 360 度。 + +默认配置: 240 + +`markdown:docs/common/color.en.md` + +#### barStyle + +**可选**, _StyleAttr | Function_ + +功能描述: 样式配置 。 + +默认配置: 无 + +`markdown:docs/common/shape-style.en.md` + + +### 图表组件 + +`markdown:docs/common/component-no-axis.en.md` diff --git a/examples/radial-bar/basic/API.zh.md b/examples/radial-bar/basic/API.zh.md new file mode 100644 index 0000000000..cc63d3cb3a --- /dev/null +++ b/examples/radial-bar/basic/API.zh.md @@ -0,0 +1,70 @@ +## 配置属性 + +### 图表容器 + +`markdown:docs/common/chart-options.zh.md` + +### 数据映射 + +#### data 📌 + +**必选**, _array object_ + +功能描述: 设置图表数据源 + +默认配置: 无 + +数据源为对象集合,例如:`[{ time: '1991',value: 20 }, { time: '1992',value: 20 }]`。 + +`markdown:docs/common/meta.zh.md` + + +#### colorField 📌 + +**可选**, _string_ + +功能描述: 颜色映射对应的数据字段名。 + +默认配置: 无 + +### 图形样式 + +#### radius + +**可选**, _string_ + +功能描述: 半径, 0 ~ 1。 + +默认配置: `1` + +#### innerRadius + +**可选**, _number_; + +功能描述: 内径,0 ~ 1。 + + +#### maxAngle + +**可选**, _number_ + +功能描述: 最大旋转角度,由 data 中最大的数值决定,最大值是 360 度。 + +默认配置: 240 + +`markdown:docs/common/color.zh.md` + +#### barStyle + +**可选**, _StyleAttr | Function_ + +功能描述: 样式配置 。 + +默认配置: 无 + +`markdown:docs/common/shape-style.zh.md` + + +### 图表组件 + +`markdown:docs/common/component-no-axis.zh.md` diff --git a/examples/radial-bar/basic/demo/basic.ts b/examples/radial-bar/basic/demo/basic.ts new file mode 100644 index 0000000000..ab400b1671 --- /dev/null +++ b/examples/radial-bar/basic/demo/basic.ts @@ -0,0 +1,29 @@ +import { RadialBar } from '@antv/g2plot'; + +const data = [ + { name: 'X6', star: 297 }, + { name: 'G', star: 506 }, + { name: 'AVA', star: 805 }, + { name: 'G2Plot', star: 1478 }, + { name: 'L7', star: 2029 }, + { name: 'G6', star: 7100 }, + { name: 'F2', star: 7346 }, + { name: 'G2', star: 10178 }, +]; + +const bar = new RadialBar('container', { + width: 400, + height: 300, + data, + xField: 'name', + yField: 'star', + // maxAngle: 90, //最大旋转角度, + radius: 0.8, + innerRadius: 0.2, + tooltip: { + formatter: (datum) => { + return { name: 'star数', value: datum.star }; + }, + }, +}); +bar.render(); diff --git a/examples/radial-bar/basic/demo/meta.json b/examples/radial-bar/basic/demo/meta.json new file mode 100644 index 0000000000..a95f5f5c84 --- /dev/null +++ b/examples/radial-bar/basic/demo/meta.json @@ -0,0 +1,16 @@ +{ + "title": { + "zh": "中文分类", + "en": "Category" + }, + "demos": [ + { + "filename": "basic.ts", + "title": { + "zh": "玉珏图", + "en": "basic Radial-Bar chart" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*wmldRZZj9lIAAAAAAAAAAABkARQnAQ" + } + ] +} diff --git a/examples/radial-bar/basic/design.en.md b/examples/radial-bar/basic/design.en.md new file mode 100644 index 0000000000..39392da41f --- /dev/null +++ b/examples/radial-bar/basic/design.en.md @@ -0,0 +1,5 @@ +--- +title: 设计规范 +--- + + diff --git a/examples/radial-bar/basic/design.zh.md b/examples/radial-bar/basic/design.zh.md new file mode 100644 index 0000000000..39392da41f --- /dev/null +++ b/examples/radial-bar/basic/design.zh.md @@ -0,0 +1,5 @@ +--- +title: 设计规范 +--- + + diff --git a/examples/radial-bar/basic/index.en.md b/examples/radial-bar/basic/index.en.md new file mode 100644 index 0000000000..b7cc0cf982 --- /dev/null +++ b/examples/radial-bar/basic/index.en.md @@ -0,0 +1,4 @@ +--- +title: Radial-Bar +order: 0 +--- diff --git a/examples/radial-bar/basic/index.zh.md b/examples/radial-bar/basic/index.zh.md new file mode 100644 index 0000000000..b97fe20ad0 --- /dev/null +++ b/examples/radial-bar/basic/index.zh.md @@ -0,0 +1,4 @@ +--- +title: 玉珏图 +order: 0 +--- diff --git a/gatsby-config.js b/gatsby-config.js index bb0ca52819..cbfdd6242b 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -264,6 +264,14 @@ module.exports = { en: 'Sunburst Charts', }, }, + { + slug: 'radial-bar', + icon: 'other', + title: { + zh: '玉珏图', + en: 'Radial Bar', + }, + }, // OTHERS { slug: 'general', diff --git a/src/index.ts b/src/index.ts index 8f5687c9e9..40d796ff6b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -88,6 +88,9 @@ export { Gauge, GaugeOptions } from './plots/gauge'; // 瀑布图 | author by [hustcc](https://github.com/me-momo) export { Waterfall, WaterfallOptions } from './plots/waterfall'; +// 玉珏图 | author by [yujs](https://github.com/yujs) +export { RadialBar, RadialBarOptions } from './plots/radial-bar'; + // 对称条形图及类型定义 | author by [arcsin1](https://github.com/arcsin1) export { BidirectionalBar, BidirectionalBarOptions } from './plots/bidirectional-bar'; diff --git a/src/plots/radial-bar/adaptor.ts b/src/plots/radial-bar/adaptor.ts index 33193149a2..5b0773f626 100644 --- a/src/plots/radial-bar/adaptor.ts +++ b/src/plots/radial-bar/adaptor.ts @@ -1,7 +1,10 @@ -import { interaction, animation, theme, scale } from '../../adaptor/common'; +import { deepMix } from '@antv/util'; +import { interaction, animation, theme, scale, tooltip, legend } from '../../adaptor/common'; import { Params } from '../../core/adaptor'; import { flow } from '../../utils'; +import { interval } from '../../adaptor/geometries'; import { RadialBarOptions } from './types'; +import { getScaleMax } from './utils'; /** * geometry 处理 @@ -9,12 +12,18 @@ import { RadialBarOptions } from './types'; */ function geometry(params: Params): Params { const { chart, options } = params; - const { data, xField, yField } = options; - + const { data, barStyle: style, color, tooltip } = options; chart.data(data); - - chart.interval().position(`${xField}*${yField}`); - + const p = deepMix({}, params, { + options: { + tooltip, + interval: { + style, + color, + }, + }, + }); + interval(p); return params; } @@ -24,28 +33,53 @@ function geometry(params: Params): Params { */ export function meta(params: Params): Params { const { options } = params; - const { xAxis, yAxis, xField, yField } = options; - + const { yField, data, maxAngle } = options; return flow( scale({ - [xField]: xAxis, - [yField]: yAxis, + [yField]: { + min: 0, + max: getScaleMax(maxAngle, yField, data), + }, }) )(params); } + +/** + * coordinate 配置 + * @param params + */ +function coordinate(params: Params): Params { + const { chart, options } = params; + const { radius, innerRadius } = options; + + chart + .coordinate({ + type: 'polar', + cfg: { + radius, + innerRadius, + }, + }) + .transpose(); + return params; +} + +/** + * axis 配置 + * @param params + */ +export function axis(params: Params): Params { + const { chart, options } = params; + const { xField, xAxis } = options; + chart.axis(xField, xAxis); + return params; +} + /** * 图适配器 * @param chart * @param options */ export function adaptor(params: Params) { - // flow 的方式处理所有的配置到 G2 API - return flow( - geometry, - meta, - interaction, - animation, - theme - // ... 其他的 adaptor flow - )(params); + return flow(geometry, meta, axis, coordinate, interaction, animation, theme, tooltip, legend)(params); } diff --git a/src/plots/radial-bar/index.ts b/src/plots/radial-bar/index.ts index 8a6e154c70..1260f698eb 100644 --- a/src/plots/radial-bar/index.ts +++ b/src/plots/radial-bar/index.ts @@ -1,3 +1,4 @@ +import { deepMix } from '@antv/util'; import { Plot } from '../../core/plot'; import { Adaptor } from '../../core/adaptor'; import { RadialBarOptions } from './types'; @@ -12,6 +13,25 @@ export class RadialBar extends Plot { /** 图表类型 */ public type: string = 'radial-bar'; + /** + * 获取默认配置 + */ + protected getDefaultOptions(): Partial { + return deepMix({}, super.getDefaultOptions(), { + interactions: [{ type: 'element-active' }], + legend: false, + tooltip: { + showMarkers: false, + }, + xAxis: { + grid: null, + tickLine: null, + line: null, + }, + maxAngle: 240, + }); + } + /** * 获取适配器 */ diff --git a/src/plots/radial-bar/types.ts b/src/plots/radial-bar/types.ts index c4b99a673c..92b0312935 100644 --- a/src/plots/radial-bar/types.ts +++ b/src/plots/radial-bar/types.ts @@ -1,3 +1,4 @@ +import { ShapeAttrs } from '@antv/g-base/lib/types'; import { Options } from '../../types'; /** 配置类型定义 */ @@ -6,4 +7,12 @@ export interface RadialBarOptions extends Options { readonly xField?: string; /** y 轴字段 */ readonly yField?: string; + /** 样式 */ + readonly barStyle?: ShapeAttrs; + /** 最大旋转角度 0~360 */ + readonly maxAngle?: number; + /** 圆半径 */ + readonly radius?: number; + /** 圆内半径 */ + readonly innerRadius?: number; } diff --git a/src/plots/radial-bar/utils.ts b/src/plots/radial-bar/utils.ts new file mode 100644 index 0000000000..ee59ab7be5 --- /dev/null +++ b/src/plots/radial-bar/utils.ts @@ -0,0 +1,11 @@ +import { Data } from '../../types'; + +export function getScaleMax(maxAngle: number, yField: string, data: Data): number { + const yData = data.map((item) => item[yField]); + const maxValue = Math.max(...yData); + const formatRadian = Math.abs(maxAngle) % 360; + if (!formatRadian) { + return maxValue; + } + return (maxValue * 360) / formatRadian; +}