From e420761723c583c193b2ca85785d798c89ed5eb6 Mon Sep 17 00:00:00 2001 From: arcsin1 Date: Thu, 29 Oct 2020 10:19:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AF=B9=E7=A7=B0=E6=9D=A1=E5=BD=A2?= =?UTF-8?q?=E5=9B=BE=EF=BC=88Bi-directional=EF=BC=89=20(#1746)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 对称条形图(new) * feat: 完善小细节部分 * feat: 完整的实现和单测 * feat: 基本完善对称条形图 * fix: 优化几个小问题 --- __tests__/data/bi-directional.ts | 11 + .../unit/plots/bidirectional-bar/axis-spec.ts | 77 +++++++ .../unit/plots/bidirectional-bar/data-spec.ts | 31 +++ .../plots/bidirectional-bar/index-spec.ts | 126 +++++++++++ .../plots/bidirectional-bar/label-spec.ts | 35 +++ .../plots/bidirectional-bar/layout-spec.ts | 58 +++++ .../plots/bidirectional-bar/legend-spec.ts | 32 +++ .../plots/bidirectional-bar/style-spec.ts | 52 +++++ src/index.ts | 3 + src/plots/bidirectional-bar/adaptor.ts | 211 ++++++++++++++++++ src/plots/bidirectional-bar/constant.ts | 2 + src/plots/bidirectional-bar/index.ts | 30 +++ src/plots/bidirectional-bar/types.ts | 21 ++ src/plots/bidirectional-bar/utils.ts | 27 +++ 14 files changed, 716 insertions(+) create mode 100644 __tests__/data/bi-directional.ts create mode 100644 __tests__/unit/plots/bidirectional-bar/axis-spec.ts create mode 100644 __tests__/unit/plots/bidirectional-bar/data-spec.ts create mode 100644 __tests__/unit/plots/bidirectional-bar/index-spec.ts create mode 100644 __tests__/unit/plots/bidirectional-bar/label-spec.ts create mode 100644 __tests__/unit/plots/bidirectional-bar/layout-spec.ts create mode 100644 __tests__/unit/plots/bidirectional-bar/legend-spec.ts create mode 100644 __tests__/unit/plots/bidirectional-bar/style-spec.ts create mode 100644 src/plots/bidirectional-bar/adaptor.ts create mode 100644 src/plots/bidirectional-bar/constant.ts create mode 100644 src/plots/bidirectional-bar/index.ts create mode 100644 src/plots/bidirectional-bar/types.ts create mode 100644 src/plots/bidirectional-bar/utils.ts diff --git a/__tests__/data/bi-directional.ts b/__tests__/data/bi-directional.ts new file mode 100644 index 0000000000..7835e5bbf8 --- /dev/null +++ b/__tests__/data/bi-directional.ts @@ -0,0 +1,11 @@ +export const data = [ + { country: '乌拉圭', '2016年耕地总面积': 13.4, '2016年转基因种植面积': 12.3 }, + { country: '巴拉圭', '2016年耕地总面积': 14.4, '2016年转基因种植面积': 6.3 }, + { country: '南非', '2016年耕地总面积': 18.4, '2016年转基因种植面积': 8.3 }, + { country: '巴基斯坦', '2016年耕地总面积': 34.4, '2016年转基因种植面积': 13.8 }, + { country: '阿根廷', '2016年耕地总面积': 44.4, '2016年转基因种植面积': 19.5 }, + { country: '巴西', '2016年耕地总面积': 24.4, '2016年转基因种植面积': 18.8 }, + { country: '加拿大', '2016年耕地总面积': 54.4, '2016年转基因种植面积': 24.7 }, + { country: '中国', '2016年耕地总面积': 104.4, '2016年转基因种植面积': 5.3 }, + { country: '美国', '2016年耕地总面积': 165.2, '2016年转基因种植面积': 72.9 }, +]; diff --git a/__tests__/unit/plots/bidirectional-bar/axis-spec.ts b/__tests__/unit/plots/bidirectional-bar/axis-spec.ts new file mode 100644 index 0000000000..c371330093 --- /dev/null +++ b/__tests__/unit/plots/bidirectional-bar/axis-spec.ts @@ -0,0 +1,77 @@ +import { BidirectionalBar } from '../../../../src'; +import { data } from '../../../data/bi-directional'; +import { createDiv } from '../../../utils/dom'; + +describe('Bidirectional axis', () => { + it('x*y*yAxis', () => { + const bidirectional = new BidirectionalBar(createDiv('x*y*xAxis'), { + width: 400, + height: 400, + data, + xField: 'country', + yField: ['2016年耕地总面积', '2016年转基因种植面积'], + yAxis: { + '2016年耕地总面积': { + nice: true, + }, + '2016年转基因种植面积': { + nice: true, + min: 0, + max: 100, + }, + }, + }); + bidirectional.render(); + + const firstView = bidirectional.chart.views[0]; + const secondView = bidirectional.chart.views[1]; + + //@ts-ignore + expect(firstView.options.axes['2016年耕地总面积'].nice).toEqual(true); + //@ts-ignore + expect(secondView.options.axes['2016年转基因种植面积'].nice).toEqual(true); + //@ts-ignore + expect(secondView.options.axes['2016年转基因种植面积'].min).toEqual(0); + //@ts-ignore + expect(secondView.options.axes['2016年转基因种植面积'].max).toEqual(100); + //@ts-ignore + expect(secondView.options.axes.country).toEqual(false); + }); + + it('x*y*xAxis*false', () => { + const bidirectional = new BidirectionalBar(createDiv('x*y*xAxis*false'), { + width: 400, + height: 400, + data, + xField: 'country', + yField: ['2016年耕地总面积', '2016年转基因种植面积'], + xAxis: false, + }); + bidirectional.render(); + + const firstView = bidirectional.chart.views[0]; + const secondView = bidirectional.chart.views[1]; + // @ts-ignore + expect(firstView.options.axes.country).toEqual(false); + //@ts-ignore + expect(secondView.options.axes.country).toEqual(false); + }); + it('x*y*yAxis*false', () => { + const bidirectional = new BidirectionalBar(createDiv('x*y*yAxis*false'), { + width: 400, + height: 400, + data, + xField: 'country', + yField: ['2016年耕地总面积', '2016年转基因种植面积'], + yAxis: false, + }); + bidirectional.render(); + + const firstView = bidirectional.chart.views[0]; + const secondView = bidirectional.chart.views[1]; + // @ts-ignore + expect(firstView.options.axes['2016年耕地总面积']).toEqual(false); + //@ts-ignore + expect(secondView.options.axes['2016年转基因种植面积']).toEqual(false); + }); +}); diff --git a/__tests__/unit/plots/bidirectional-bar/data-spec.ts b/__tests__/unit/plots/bidirectional-bar/data-spec.ts new file mode 100644 index 0000000000..e6bec49d88 --- /dev/null +++ b/__tests__/unit/plots/bidirectional-bar/data-spec.ts @@ -0,0 +1,31 @@ +import { transformData } from '../../../../src/plots/bidirectional-bar/utils'; +import { data } from '../../../data/bi-directional'; + +export const hopedata = [ + { country: '乌拉圭', type: '2016年耕地总面积', '2016年耕地总面积': 13.4 }, + { country: '巴拉圭', type: '2016年耕地总面积', '2016年耕地总面积': 14.4 }, + { country: '南非', type: '2016年耕地总面积', '2016年耕地总面积': 18.4 }, + { country: '巴基斯坦', type: '2016年耕地总面积', '2016年耕地总面积': 34.4 }, + { country: '阿根廷', type: '2016年耕地总面积', '2016年耕地总面积': 44.4 }, + { country: '巴西', type: '2016年耕地总面积', '2016年耕地总面积': 24.4 }, + { country: '加拿大', type: '2016年耕地总面积', '2016年耕地总面积': 54.4 }, + { country: '中国', type: '2016年耕地总面积', '2016年耕地总面积': 104.4 }, + { country: '美国', type: '2016年耕地总面积', '2016年耕地总面积': 165.2 }, + + { country: '乌拉圭', type: '2016年转基因种植面积', '2016年转基因种植面积': 12.3 }, + { country: '巴拉圭', type: '2016年转基因种植面积', '2016年转基因种植面积': 6.3 }, + { country: '南非', type: '2016年转基因种植面积', '2016年转基因种植面积': 8.3 }, + { country: '巴基斯坦', type: '2016年转基因种植面积', '2016年转基因种植面积': 13.8 }, + { country: '阿根廷', type: '2016年转基因种植面积', '2016年转基因种植面积': 19.5 }, + { country: '巴西', type: '2016年转基因种植面积', '2016年转基因种植面积': 18.8 }, + { country: '加拿大', type: '2016年转基因种植面积', '2016年转基因种植面积': 24.7 }, + { country: '中国', type: '2016年转基因种植面积', '2016年转基因种植面积': 5.3 }, + { country: '美国', type: '2016年转基因种植面积', '2016年转基因种植面积': 72.9 }, +]; +describe('bullet*data*transfrom', () => { + it('data*transfrom', () => { + // 校验数据转换 + const transDS = transformData('country', ['2016年耕地总面积', '2016年转基因种植面积'], data); + expect(transDS).toEqual(hopedata); + }); +}); diff --git a/__tests__/unit/plots/bidirectional-bar/index-spec.ts b/__tests__/unit/plots/bidirectional-bar/index-spec.ts new file mode 100644 index 0000000000..5b18882062 --- /dev/null +++ b/__tests__/unit/plots/bidirectional-bar/index-spec.ts @@ -0,0 +1,126 @@ +import { BidirectionalBar } from '../../../../src'; +import { data } from '../../../data/bi-directional'; +import { createDiv } from '../../../utils/dom'; + +describe('Bidirectional', () => { + it('default', () => { + const bidirectional = new BidirectionalBar(createDiv('default'), { + width: 400, + height: 400, + data, + xField: 'country', + yField: ['2016年耕地总面积', '2016年转基因种植面积'], + }); + bidirectional.render(); + + expect(bidirectional.type).toEqual('bidirectional-bar'); + + const leftView = bidirectional.chart.views[0]; + const rightView = bidirectional.chart.views[1]; + const leftG = bidirectional.chart.views[0].geometries[0]; + const rightG = bidirectional.chart.views[1].geometries[0]; + //@ts-ignore + expect(leftView.options.axes.country.position).toEqual('top'); + //@ts-ignore + expect(rightView.options.axes.country).toEqual(false); + expect(bidirectional.chart.getOptions().scales.type.sync).toEqual(true); + + // 类型 + expect(leftG.type).toBe('interval'); + expect(rightG.type).toBe('interval'); + + // x & y + const LpositionFields = leftG.getAttribute('position').getFields(); + const RpositionFields = rightG.getAttribute('position').getFields(); + expect(LpositionFields).toHaveLength(2); + expect(LpositionFields[0]).toBe('country'); + expect(LpositionFields[1]).toBe('2016年耕地总面积'); + + expect(RpositionFields).toHaveLength(2); + expect(RpositionFields[0]).toBe('country'); + expect(RpositionFields[1]).toBe('2016年转基因种植面积'); + + const LcolorAttribute = leftG.getAttribute('color'); + const LseriesFields = LcolorAttribute.getFields(); + + expect(LseriesFields).toHaveLength(1); + expect(LseriesFields[0]).toBe('type'); + + const RcolorAttribute = rightG.getAttribute('color'); + const RseriesFields = RcolorAttribute.getFields(); + + expect(RseriesFields).toHaveLength(1); + expect(RseriesFields[0]).toBe('type'); + + expect(bidirectional.chart.getController('legend').visible).toEqual(true); + + expect(bidirectional.chart.getController('legend').getComponents()[0].direction).toEqual('bottom'); + + expect(bidirectional.chart.getController('legend').getComponents()[0].extra.scale.field).toEqual('type'); + }); + it('x*y*color', () => { + const bidirectional = new BidirectionalBar(createDiv('x*y*color'), { + width: 400, + height: 400, + data, + xField: 'country', + yField: ['2016年耕地总面积', '2016年转基因种植面积'], + }); + bidirectional.render(); + + const leftG = bidirectional.chart.views[0].geometries[0]; + const rightG = bidirectional.chart.views[1].geometries[0]; + const LseriesFields = leftG.getAttribute('color').getFields(); + const RseriesFields = rightG.getAttribute('color').getFields(); + + expect(LseriesFields).toHaveLength(1); + expect(LseriesFields[0]).toBe('type'); + expect(RseriesFields).toHaveLength(1); + expect(RseriesFields[0]).toBe('type'); + }); + + it('x*y*color with color', () => { + const palette = ['red', 'green']; + const bidirectional = new BidirectionalBar(createDiv('x*y*color with color'), { + width: 400, + height: 400, + data, + xField: 'country', + yField: ['2016年耕地总面积', '2016年转基因种植面积'], + color: palette, + }); + bidirectional.render(); + + bidirectional.render(); + + const leftG = bidirectional.chart.views[0].geometries[0]; + const rightG = bidirectional.chart.views[1].geometries[0]; + + leftG.elements.forEach((element) => { + const color = element.getModel().color; + expect(color).toBe(palette[0]); + }); + + rightG.elements.forEach((element) => { + const color = element.getModel().color; + expect(color).toBe(palette[1]); + }); + }); + + it('widthRatio', () => { + const bidirectional = new BidirectionalBar(createDiv('widthRatio'), { + width: 400, + height: 400, + data, + xField: 'country', + yField: ['2016年耕地总面积', '2016年转基因种植面积'], + widthRatio: 0.7, + }); + bidirectional.render(); + + const leftG = bidirectional.chart.views[0].geometries[0]; + const rightG = bidirectional.chart.views[1].geometries[0]; + expect(leftG.theme.columnWidthRatio).toBe(0.7); + expect(rightG.theme.columnWidthRatio).toBe(0.7); + }); +}); diff --git a/__tests__/unit/plots/bidirectional-bar/label-spec.ts b/__tests__/unit/plots/bidirectional-bar/label-spec.ts new file mode 100644 index 0000000000..f7e6d9ccdd --- /dev/null +++ b/__tests__/unit/plots/bidirectional-bar/label-spec.ts @@ -0,0 +1,35 @@ +import { BidirectionalBar } from '../../../../src'; +import { data } from '../../../data/bi-directional'; +import { createDiv } from '../../../utils/dom'; + +describe('Bidirectional laebl', () => { + it('x*y*label*true', () => { + const bidirectional = new BidirectionalBar(createDiv(), { + width: 400, + height: 400, + data, + xField: 'country', + yField: ['2016年转基因种植面积', '2016年耕地总面积'], + label: { + position: 'middle', + style: { + fill: '#fff', + }, + }, + }); + bidirectional.render(); + + const leftG = bidirectional.chart.views[0].geometries[0]; + const rightG = bidirectional.chart.views[1].geometries[0]; + + // @ts-ignore + expect(leftG.labelOption.cfg.position).toEqual('middle'); + // @ts-ignore + expect(leftG.labelOption.cfg.style.fill).toEqual('#fff'); + + // @ts-ignore + expect(rightG.labelOption.cfg.position).toEqual('middle'); + // @ts-ignore + expect(rightG.labelOption.cfg.style.fill).toEqual('#fff'); + }); +}); diff --git a/__tests__/unit/plots/bidirectional-bar/layout-spec.ts b/__tests__/unit/plots/bidirectional-bar/layout-spec.ts new file mode 100644 index 0000000000..43371890b3 --- /dev/null +++ b/__tests__/unit/plots/bidirectional-bar/layout-spec.ts @@ -0,0 +1,58 @@ +import { BidirectionalBar } from '../../../../src'; +import { data } from '../../../data/bi-directional'; +import { createDiv } from '../../../utils/dom'; + +describe('Bidirectional layout', () => { + it('layout*default*horizontal', () => { + const bidirectional = new BidirectionalBar(createDiv('x*y*horizontal'), { + width: 400, + height: 400, + data, + xField: 'country', + yField: ['2016年耕地总面积', '2016年转基因种植面积'], + }); + bidirectional.render(); + + const firstView = bidirectional.chart.views[0]; + const secondView = bidirectional.chart.views[1]; + expect(firstView.getCoordinate().isTransposed).toBe(true); + expect(secondView.getCoordinate().isTransposed).toBe(true); + //@ts-ignore + expect(firstView.getCoordinate().isReflectX).toBe(true); + }); + it('layout*default*vertical', () => { + const bidirectional = new BidirectionalBar(createDiv('x*y*vertical'), { + width: 400, + height: 600, + data, + xField: 'country', + yField: ['2016年耕地总面积', '2016年转基因种植面积'], + layout: 'vertical', + yAxis: { + '2016年耕地总面积': { + nice: true, + }, + '2016年转基因种植面积': { + min: 0, + max: 100, + }, + }, + }); + bidirectional.render(); + + const firstView = bidirectional.chart.views[0]; + const secondView = bidirectional.chart.views[1]; + + //@ts-ignore + expect(firstView.options.axes.country.position).toEqual('bottom'); + + expect(firstView.getCoordinate().isTransposed).toBe(false); + expect(secondView.getCoordinate().isTransposed).toBe(false); + //@ts-ignore + expect(secondView.getCoordinate().isReflectY).toBe(true); + //@ts-ignore + expect(secondView.options.axes['2016年转基因种植面积'].min).toEqual(0); + //@ts-ignore + expect(secondView.options.axes['2016年转基因种植面积'].max).toEqual(100); + }); +}); diff --git a/__tests__/unit/plots/bidirectional-bar/legend-spec.ts b/__tests__/unit/plots/bidirectional-bar/legend-spec.ts new file mode 100644 index 0000000000..183b70a314 --- /dev/null +++ b/__tests__/unit/plots/bidirectional-bar/legend-spec.ts @@ -0,0 +1,32 @@ +import { BidirectionalBar } from '../../../../src'; +import { data } from '../../../data/bi-directional'; +import { createDiv } from '../../../utils/dom'; + +describe('Bidirectional legend', () => { + it('x*y*legend*top', () => { + const bidirectional = new BidirectionalBar(createDiv(), { + width: 400, + height: 400, + data, + xField: 'country', + yField: ['2016年耕地总面积', '2016年转基因种植面积'], + legend: { + position: 'top', + }, + }); + bidirectional.render(); + expect(bidirectional.chart.getController('legend').getComponents()[0].direction).toEqual('top'); + }); + it('x*y*legend*false', () => { + const bidirectional = new BidirectionalBar(createDiv(), { + width: 400, + height: 400, + data, + xField: 'country', + yField: ['2016年耕地总面积', '2016年转基因种植面积'], + legend: false, + }); + bidirectional.render(); + expect(bidirectional.chart.getController('legend').getComponents().length).toEqual(0); + }); +}); diff --git a/__tests__/unit/plots/bidirectional-bar/style-spec.ts b/__tests__/unit/plots/bidirectional-bar/style-spec.ts new file mode 100644 index 0000000000..e67dff45c4 --- /dev/null +++ b/__tests__/unit/plots/bidirectional-bar/style-spec.ts @@ -0,0 +1,52 @@ +import { BidirectionalBar } from '../../../../src'; +import { data } from '../../../data/bi-directional'; +import { createDiv } from '../../../utils/dom'; + +describe('Bidirectional style', () => { + it('x*y*barStyle', () => { + const bidirectional = new BidirectionalBar(createDiv('barStyle'), { + width: 400, + height: 400, + data, + xField: 'country', + yField: ['2016年耕地总面积', '2016年转基因种植面积'], + barStyle: { + stroke: 'black', + lineWidth: 2, + }, + }); + bidirectional.render(); + + const leftG = bidirectional.chart.views[0].geometries[0]; + const rightG = bidirectional.chart.views[1].geometries[0]; + + expect(leftG.elements[0].shape.attr('stroke')).toBe('black'); + expect(leftG.elements[0].shape.attr('lineWidth')).toBe(2); + expect(rightG.elements[0].shape.attr('stroke')).toBe('black'); + expect(rightG.elements[0].shape.attr('lineWidth')).toBe(2); + }); + it('x*y*barStyle*callback', () => { + const bidirectional = new BidirectionalBar(createDiv('barStyle*callback'), { + width: 400, + height: 400, + data, + xField: 'country', + yField: ['2016年耕地总面积', '2016年转基因种植面积'], + barStyle: () => { + return { + stroke: 'black', + lineWidth: 2, + }; + }, + }); + bidirectional.render(); + + const leftG = bidirectional.chart.views[0].geometries[0]; + const rightG = bidirectional.chart.views[1].geometries[0]; + + expect(leftG.elements[0].shape.attr('stroke')).toBe('black'); + expect(leftG.elements[0].shape.attr('lineWidth')).toBe(2); + expect(rightG.elements[0].shape.attr('stroke')).toBe('black'); + expect(rightG.elements[0].shape.attr('lineWidth')).toBe(2); + }); +}); diff --git a/src/index.ts b/src/index.ts index 9785ea6668..8f5687c9e9 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 [arcsin1](https://github.com/arcsin1) +export { BidirectionalBar, BidirectionalBarOptions } from './plots/bidirectional-bar'; + // 以下开放自定义图表开发的能力(目前仅仅是孵化中) /** 所有开放图表都使用 G2Plot.P 作为入口开发,理论上官方的所有图表都可以走 G2Plot.P 的入口(暂时不处理) */ export { P } from './plugin'; diff --git a/src/plots/bidirectional-bar/adaptor.ts b/src/plots/bidirectional-bar/adaptor.ts new file mode 100644 index 0000000000..707097933d --- /dev/null +++ b/src/plots/bidirectional-bar/adaptor.ts @@ -0,0 +1,211 @@ +import { View } from '@antv/g2'; +import { deepMix, groupBy } from '@antv/util'; +import { Params } from '../../core/adaptor'; +import { tooltip, interaction, animation, theme, scale } from '../../adaptor/common'; +import { interval } from '../../adaptor/geometries'; +import { flow, findViewById, findGeometry, transformLabel } from '../../utils'; +import { BidirectionalBarOptions } from './types'; +import { FIRST_AXES_VIEW, SECOND_AXES_VIEW } from './constant'; +import { transformData } from './utils'; + +/** + * geometry 处理 + * @param params + */ +function geometry(params: Params): Params { + const { chart, options } = params; + const { data, xField, yField, color, barStyle, widthRatio, legend, layout } = options; + + // 处理数据 + const ds = transformData(xField, yField, data); + // 再次处理数据,通过 type 字段分成左右数据 + const groupData: any[] = Object.values(groupBy(ds, 'type')); + chart.scale({ + type: { + sync: true, + }, + }); + // 在创建子 view 执行后不行,需要在前面处理 legend + if (legend) { + chart.legend('type', legend); + } else if (legend === false) { + chart.legend(false); + } + // 创建 view + let firstView: View; + let secondView: View; + + // 横向 + if (layout === 'horizontal') { + firstView = chart.createView({ + region: { + start: { x: 0, y: 0 }, + end: { x: 0.5, y: 1 }, + }, + id: FIRST_AXES_VIEW, + }); + + firstView.coordinate().transpose().reflect('x'); + secondView = chart.createView({ + region: { + start: { x: 0.5, y: 0 }, + end: { x: 1, y: 1 }, + }, + id: SECOND_AXES_VIEW, + }); + secondView.coordinate().transpose(); + } + + // 纵向 + if (layout === 'vertical') { + firstView = chart.createView({ + region: { + start: { x: 0, y: 0 }, + end: { x: 1, y: 0.5 }, + }, + id: FIRST_AXES_VIEW, + }); + secondView = chart.createView({ + region: { + start: { x: 0, y: 0.5 }, + end: { x: 1, y: 1 }, + }, + id: SECOND_AXES_VIEW, + }); + + secondView + .coordinate() + .reflect('y') + .rotate(Math.PI * 0); // 旋转 + } + + firstView.data(groupData[0]); + const left = deepMix({}, params, { + chart: firstView, + options: { + widthRatio, + xField, + yField: yField[0], + seriesField: 'type', + interval: { + color, + style: barStyle, + }, + }, + }); + interval(left); + + secondView.data(groupData[1]); + const right = deepMix({}, params, { + chart: secondView, + options: { + xField, + yField: yField[1], + seriesField: 'type', + widthRatio, + interval: { + color, + style: barStyle, + }, + }, + }); + + interval(right); + return params; +} + +/** + * meta 配置 + * @param params + */ +function meta(params: Params): Params { + const { options, chart } = params; + const { xAxis, yAxis, xField, yField } = options; + const firstView = findViewById(chart, FIRST_AXES_VIEW); + const secondView = findViewById(chart, SECOND_AXES_VIEW); + + scale({ + [xField]: xAxis, + [yField[0]]: yAxis[yField[0]], + })(deepMix({}, params, { chart: firstView })); + + scale({ + [xField]: xAxis, + [yField[1]]: yAxis[yField[1]], + })(deepMix({}, params, { chart: secondView })); + + return params; +} + +/** + * axis 配置 + * @param params + */ +function axis(params: Params): Params { + const { chart, options } = params; + const { xAxis, yAxis, xField, yField } = options; + + const firstView = findViewById(chart, FIRST_AXES_VIEW); + const secondView = findViewById(chart, SECOND_AXES_VIEW); + // 第二个 view axis 始终隐藏 + secondView.axis(xField, false); + + // 为 false 则是不显示 firstView 轴 + if (xAxis === false) { + firstView.axis(xField, false); + } else { + firstView.axis(xField, xAxis); + } + + if (yAxis === false) { + firstView.axis(yField[0], false); + secondView.axis(yField[1], false); + } else { + firstView.axis(yField[0], yAxis[yField[0]]); + secondView.axis(yField[1], yAxis[yField[1]]); + } + return params; +} + +/** + * label 配置 + * @param params + */ +function label(params: Params): Params { + const { chart, options } = params; + const { label, yField } = options; + + const firstView = findViewById(chart, FIRST_AXES_VIEW); + const secondView = findViewById(chart, SECOND_AXES_VIEW); + const leftGeometry = findGeometry(firstView, 'interval'); + const rightGeometry = findGeometry(secondView, 'interval'); + + if (!label) { + leftGeometry.label(false); + rightGeometry.label(false); + } else { + const { callback, ...cfg } = label; + leftGeometry.label({ + fields: [yField[0]], + callback, + cfg: transformLabel(cfg), + }); + rightGeometry.label({ + fields: [yField[1]], + callback, + cfg: transformLabel(cfg), + }); + } + + return params; +} + +/** + * 对称条形图适配器 + * @param chart + * @param options + */ +export function adaptor(params: Params) { + // flow 的方式处理所有的配置到 G2 API + return flow(geometry, meta, axis, theme, label, tooltip, interaction, animation)(params); +} diff --git a/src/plots/bidirectional-bar/constant.ts b/src/plots/bidirectional-bar/constant.ts new file mode 100644 index 0000000000..045b8a49a0 --- /dev/null +++ b/src/plots/bidirectional-bar/constant.ts @@ -0,0 +1,2 @@ +export const FIRST_AXES_VIEW = 'first-axes-view'; +export const SECOND_AXES_VIEW = 'second-axes-view'; diff --git a/src/plots/bidirectional-bar/index.ts b/src/plots/bidirectional-bar/index.ts new file mode 100644 index 0000000000..9fe433836d --- /dev/null +++ b/src/plots/bidirectional-bar/index.ts @@ -0,0 +1,30 @@ +import { deepMix } from '@antv/util'; +import { Plot } from '../../core/plot'; +import { Adaptor } from '../../core/adaptor'; +import { BidirectionalBarOptions } from './types'; +import { adaptor } from './adaptor'; + +export { BidirectionalBarOptions }; + +export class BidirectionalBar extends Plot { + /** 图表类型 */ + public type: string = 'bidirectional-bar'; + + /** + * 获取对称条形图的适配器 + */ + protected getSchemaAdaptor(): Adaptor { + return adaptor; + } + + protected getDefaultOptions(options: BidirectionalBarOptions) { + const { layout = 'horizontal' } = options; + return deepMix({}, super.getDefaultOptions(), { + xAxis: { + // 不同布局 firstView 的坐标轴显示位置 + position: layout === 'vertical' ? 'bottom' : 'top', + }, + layout, + }); + } +} diff --git a/src/plots/bidirectional-bar/types.ts b/src/plots/bidirectional-bar/types.ts new file mode 100644 index 0000000000..ced3a54303 --- /dev/null +++ b/src/plots/bidirectional-bar/types.ts @@ -0,0 +1,21 @@ +import { Axis } from '../../types/axis'; +import { Options, StyleAttr } from '../../types'; + +export interface BidirectionalBarOptions extends Omit { + /** x 轴字段 */ + readonly xField: string; + /** y 轴映射字段 */ + readonly yField: [string, string]; + /** yAxis 为多个 key 为 yField 里面的 2 个字段 */ + readonly yAxis?: + | false + | { + [key: string]: Axis; + }; + /** 柱状图宽度占比 [0-1] */ + readonly widthRatio?: number; + /** 柱子样式配置,可选 */ + readonly barStyle?: StyleAttr; + /** layout 方向选择,默认水平 horizontal*/ + readonly layout?: 'horizontal' | 'vertical'; +} diff --git a/src/plots/bidirectional-bar/utils.ts b/src/plots/bidirectional-bar/utils.ts new file mode 100644 index 0000000000..3a28788c3a --- /dev/null +++ b/src/plots/bidirectional-bar/utils.ts @@ -0,0 +1,27 @@ +import { Datum } from '../../types'; + +type TransformData = { + type: string; + [key: string]: string | number; +}[]; + +/** + * bidirectional-bar 处理数据 + * @param xField + * @param yField + * @param data + */ +export function transformData(xField: string, yField: string[], data: Datum): TransformData { + const hopeData: TransformData = []; + yField.forEach((d: string) => { + data.forEach((k: any) => { + const obj = { + [xField]: k[xField], + type: d, + [d]: k[d], + }; + hopeData.push(obj); + }); + }); + return hopeData; +}