diff --git a/__tests__/unit/plots/box/change-data-spec.ts b/__tests__/unit/plots/box/change-data-spec.ts index 5045ea6deb..8425fd4216 100644 --- a/__tests__/unit/plots/box/change-data-spec.ts +++ b/__tests__/unit/plots/box/change-data-spec.ts @@ -60,8 +60,9 @@ describe('box change data', () => { box.render(); const outliersView = box.chart.views.find((v) => v.id === OUTLIERS_VIEW_ID); + const len = outliersData.reduce((r, d) => r + d.outliers.length, 0); expect(box.chart.geometries[0].elements.length).toEqual(outliersData.length); - expect(outliersView.geometries[0].elements.length).toEqual(outliersData.length); + expect(outliersView.geometries[0].elements.length).toEqual(len); const newData = [ ...outliersData, diff --git a/__tests__/unit/plots/box/outliers-spec.ts b/__tests__/unit/plots/box/outliers-spec.ts index 69a7826dfd..162c962fb1 100644 --- a/__tests__/unit/plots/box/outliers-spec.ts +++ b/__tests__/unit/plots/box/outliers-spec.ts @@ -26,7 +26,8 @@ describe('box outliers', () => { // 类型 expect(geometry.type).toBe('point'); // 图形元素个数 - expect(geometry.elements.length).toBe(outliersData.length); + const len = outliersData.reduce((r, d) => r + d.outliers.length, 0); + expect(geometry.elements.length).toBe(len); // 同步y轴度量 axis sync // @ts-ignore expect(outliersScale.sync).toEqual(BOX_SYNC_NAME); @@ -59,7 +60,7 @@ describe('box outliers', () => { const elements = view.geometries[0].elements; // 类型 - expect(elements[0].shape.cfg.children[0].attr('fill')).toBe('#f6f'); + expect(elements[0].shape.attr('fill')).toBe('#f6f'); box.destroy(); }); diff --git a/docs/common/label.en.md b/docs/common/label.en.md index c2a58c454c..116c076a2e 100644 --- a/docs/common/label.en.md +++ b/docs/common/label.en.md @@ -13,7 +13,7 @@ | labelLine | _null_ \| _boolean_ \| _LabelLineCfg_ | Used to set the style property of the text connector. NULL indicates that it is not displayed. | | labelEmit | _boolean_ | Only applies to text in polar coordinates, indicating whether the text is radially displayed according to the Angle. True means on and false means off | | layout | _'overlap' \| 'fixedOverlap' \| 'limitInShape'_ | Text layout type, support a variety of layout function combination. | -| position | _'top' \| 'bottom' \| 'middle' \| 'left' \| 'right'_ | Specifies the position of the current Label relative to the current graphic (Only works for column plot and bar plot, which geometry is interval) | +| position | _'top' \| 'bottom' \| 'middle' \| 'left' \| 'right'_ | Specifies the position of the current Label relative to the current graphic (💡 Attention: Only works for **column plot** and **bar plot**, which geometry is interval) | | animate | _boolean \| AnimateOption_ | Animation configuration. | | formatter | _Function_ | Format function | | autoHide | _boolean_ | Whether to hide it automatically, default to false | diff --git a/docs/common/label.zh.md b/docs/common/label.zh.md index 89dd3729d3..2ffac8299d 100644 --- a/docs/common/label.zh.md +++ b/docs/common/label.zh.md @@ -13,7 +13,7 @@ | labelLine | _null_ \| _boolean_ \| _LabelLineCfg_ | 用于设置文本连接线的样式属性,null 表示不展示。 | | labelEmit | _boolean_ | 只对极坐标下的文本生效,表示文本是否按照角度进行放射状显示,true 表示开启,false 表示关闭 | | layout | _'overlap' \| 'fixedOverlap' \| 'limitInShape'_ | 文本布局类型,支持多种布局函数组合使用。 | -| position | _'top' \| 'bottom' \| 'middle' \| 'left' \| 'right'_ | 指定当前 label 与当前图形的相对位置 (只对 geometry 为 interval 的 柱条形图生效) | +| position | _'top' \| 'bottom' \| 'middle' \| 'left' \| 'right'_ | 指定当前 label 与当前图形的相对位置 (💡 注意:只对 geometry 为 interval 的柱条形图生效) | | animate | _boolean \| AnimateOption_ | 动画配置。 | | formatter | _Function_ | 格式化函数 | | autoHide | _boolean_ | 是否自动隐藏,默认 false | diff --git a/examples/more-plots/box/demo/label.ts b/examples/more-plots/box/demo/label.ts new file mode 100644 index 0000000000..9a6fd6f7f4 --- /dev/null +++ b/examples/more-plots/box/demo/label.ts @@ -0,0 +1,30 @@ +import { Box } from '@antv/g2plot'; + +const data = [ + { x: '职业 A', low: 20000, q1: 26000, median: 27000, q3: 32000, high: 38000, outliers: [50000, 52000] }, + { x: '职业 B', low: 40000, q1: 49000, median: 62000, q3: 73000, high: 88000, outliers: [32000, 29000, 106000] }, + { x: '职业 C', low: 52000, q1: 59000, median: 65000, q3: 74000, high: 83000, outliers: [91000] }, + { x: '职业 D', low: 58000, q1: 96000, median: 130000, q3: 170000, high: 200000, outliers: [42000, 210000, 215000] }, + { x: '职业 E', low: 24000, q1: 28000, median: 32000, q3: 38000, high: 42000, outliers: [48000] }, + { x: '职业 F', low: 47000, q1: 56000, median: 69000, q3: 85000, high: 100000, outliers: [110000, 115000, 32000] }, + { x: '职业 G', low: 64000, q1: 74000, median: 83000, q3: 93000, high: 100000, outliers: [110000] }, + { x: '职业 H', low: 67000, q1: 72000, median: 84000, q3: 95000, high: 110000, outliers: [57000, 54000] }, +]; + +const outliersBoxPlot = new Box('container', { + data, + xField: 'x', + yField: ['low', 'q1', 'median', 'q3', 'high'], + outliersField: 'outliers', + outliersStyle: { + fill: '#f6f', + }, + label: { + style: { + textBaseline: 'top', + }, + layout: { type: 'hide-overlap' }, + }, +}); + +outliersBoxPlot.render(); diff --git a/examples/more-plots/box/demo/meta.json b/examples/more-plots/box/demo/meta.json index e9d09b69ca..2885f62f77 100644 --- a/examples/more-plots/box/demo/meta.json +++ b/examples/more-plots/box/demo/meta.json @@ -35,6 +35,23 @@ "en": "Set alias of field" }, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/BUWqiUYOhY/box-alias.png" + }, + { + "filename": "label.ts", + "title": { + "zh": "设置箱线图 label", + "en": "Box plot label" + }, + "new": true, + "screenshot": "" + }, + { + "filename": "tooltip.ts", + "title": { + "zh": "设置箱线图 tooltip", + "en": "Box plot tooltip" + }, + "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*cE6vR461omUAAAAAAAAAAAAAARQnAQ" } ] } diff --git a/examples/more-plots/box/demo/tooltip.ts b/examples/more-plots/box/demo/tooltip.ts new file mode 100644 index 0000000000..ea9c3e6b1f --- /dev/null +++ b/examples/more-plots/box/demo/tooltip.ts @@ -0,0 +1,52 @@ +import { Box } from '@antv/g2plot'; + +const data = [ + { x: '职业 A', low: 20000, q1: 26000, median: 27000, q3: 32000, high: 38000, outliers: [50000, 52000] }, + { x: '职业 B', low: 40000, q1: 49000, median: 62000, q3: 73000, high: 88000, outliers: [32000, 29000, 106000] }, + { x: '职业 C', low: 52000, q1: 59000, median: 65000, q3: 74000, high: 83000, outliers: [91000] }, + { x: '职业 D', low: 58000, q1: 96000, median: 130000, q3: 170000, high: 200000, outliers: [42000, 210000, 215000] }, + { x: '职业 E', low: 24000, q1: 28000, median: 32000, q3: 38000, high: 42000, outliers: [48000] }, + { x: '职业 F', low: 47000, q1: 56000, median: 69000, q3: 85000, high: 100000, outliers: [110000, 115000, 32000] }, + { x: '职业 G', low: 64000, q1: 74000, median: 83000, q3: 93000, high: 100000, outliers: [110000] }, + { x: '职业 H', low: 67000, q1: 72000, median: 84000, q3: 95000, high: 110000, outliers: [57000, 54000] }, +]; + +const boxPlot = new Box('container', { + width: 400, + height: 500, + data: data, + xField: 'x', + yField: ['low', 'q1', 'median', 'q3', 'high'], + meta: { + low: { + alias: '最低值', + }, + q1: { + alias: '下四分位数', + }, + median: { + alias: '最低值', + }, + q3: { + alias: '上四分位数', + }, + high: { + alias: '最高值', + }, + outliers: { + alias: '异常值', + }, + }, + outliersField: 'outliers', + tooltip: { + fields: ['high', 'q3', 'median', 'q1', 'low', 'outliers'], + }, + boxStyle: { + stroke: '#545454', + fill: '#1890FF', + fillOpacity: 0.3, + }, + animation: false, +}); + +boxPlot.render(); diff --git a/examples/plugin/pattern/demo/meta.json b/examples/plugin/pattern/demo/meta.json index 7a86ff5a1f..63f4bd3c67 100644 --- a/examples/plugin/pattern/demo/meta.json +++ b/examples/plugin/pattern/demo/meta.json @@ -10,7 +10,6 @@ "zh": "带贴图图案的饼图", "en": "Pie plot with pattern" }, - "new": true, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/cACfJO99IO/32b38815-8427-4111-a0f0-34208aa65dd8.png" }, { @@ -19,7 +18,6 @@ "zh": "图例 marker 设置贴图图案", "en": "Custom legend marker with pattern" }, - "new": true, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/N%26xo4t7zRW/79c6bdf8-0cc6-49b7-94a2-f462b9f825bc.png" }, { @@ -28,7 +26,6 @@ "zh": "带贴图图案的分组柱状图", "en": "Grouped column plot with pattern" }, - "new": true, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/wlurjHqyu%24/4b21f53e-7186-42c8-8f57-6f32c91a09ba.png" }, { @@ -37,7 +34,6 @@ "zh": "带贴图图案的条形图", "en": "Bar plot with pattern" }, - "new": true, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/APiY4THavu/93d5749b-1e0d-4118-b04f-4e928a7eb144.png" }, { @@ -46,7 +42,6 @@ "zh": "带贴图图案的堆叠图", "en": "Stack-bar plot with pattern" }, - "new": true, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/LGFxwRRwZl/d11e9835-a520-4a4c-9893-30b6a6ebb3c1.png" }, { @@ -55,7 +50,6 @@ "zh": "带贴图图案的环形图", "en": "Donut with pattern" }, - "new": true, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/spgIo%26zAzG/6562dc40-2ac6-4c8a-b433-88e41e20621a.png" }, { @@ -64,7 +58,6 @@ "zh": "带贴图图案的面积图", "en": "Area with pattern" }, - "new": true, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/msiCPIX%243T/16b69b77-6a87-42e0-9e8c-9767d6b9df79.png" }, { @@ -73,7 +66,6 @@ "zh": "带贴图图案的堆叠分组柱状图", "en": "Stacked grouped column plot with pattern" }, - "new": true, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/1Ei8A%24VtRI/a9f53248-bdb4-4bc4-a567-3cd0d76e2a26.png" }, { @@ -82,7 +74,6 @@ "zh": "带贴图图案的热力图", "en": "Heatmap with pattern" }, - "new": true, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/0zXfW5rjOu/e442fed3-047c-4f34-9536-8efe2e60a92f.png" }, { @@ -91,7 +82,6 @@ "zh": "带贴图图案的热力图", "en": "Heatmap with pattern (cookie)" }, - "new": true, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/vR9mVPFTmx/7b2e0aac-cee0-428b-bca3-c7082bb2b76f.png" }, { @@ -100,8 +90,7 @@ "zh": "带贴图图案的水波图", "en": "Liquid with pattern" }, - "new": true, "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/CBo9L7m9Ux/53202fa4-ad16-4c47-ba72-f8b9858f0c78.png" } ] -} +} \ No newline at end of file diff --git a/src/plots/box/adaptor.ts b/src/plots/box/adaptor.ts index 6b3ca1dcdb..25f3290e3c 100644 --- a/src/plots/box/adaptor.ts +++ b/src/plots/box/adaptor.ts @@ -1,4 +1,5 @@ import { isArray } from '@antv/util'; +import { Types } from '@antv/g2'; import { Params } from '../../core/adaptor'; import { interaction, animation, theme, tooltip } from '../../adaptor/common'; import { point, schema } from '../../adaptor/geometries'; @@ -34,6 +35,8 @@ function field(params: Params): Params { seriesField: groupField, tooltip: tooltipOptions, rawFields, + // 只有异常点视图展示 label + label: false, schema: { shape: 'box', color, @@ -50,21 +53,30 @@ function field(params: Params): Params { return params; } +/** + * 创建异常点 view + */ function outliersPoint(params: Params): Params { const { chart, options } = params; - const { xField, data, outliersField, outliersStyle, padding } = options; + const { xField, data, outliersField, outliersStyle, padding, label } = options; if (!outliersField) return params; const outliersView = chart.createView({ padding, id: OUTLIERS_VIEW_ID }); - outliersView.data(data); + const outliersViewData = data.reduce((ret, datum) => { + const outliersData = datum[outliersField]; + outliersData.forEach((d) => ret.push({ ...datum, [outliersField]: d })); + return ret; + }, []) as Types.Datum[]; + outliersView.data(outliersViewData); point({ chart: outliersView, options: { xField, yField: outliersField, point: { shape: 'circle', style: outliersStyle }, + label, }, });