开始

在 Ant Design Pro 中,官方推荐使用 Ant Design Chart 这个 React 图表库,当然这个 也是基于 G2 的高交互可视化图形语法

在这里,我将常用的图表进行封装成一个组件,并保持原有的属性,然后通过一个参数来控制:type

共有 column(柱状图) line(折线图) dualAxes(双轴图) bar(条形图) area(面积图) pie(饼图) 六种图表

干了什么

有人可能会问,在 G2 中结构已经非常简便了,为何还需要封装?
是的,我也认为在原本的属性上已经封装的非常简便,没必要进行二次封装,同时,属性也确实非常多,我在这边写示例的时候只能列举一些常见的属性,那么封装的意义在于什么呢? 最为主要的一点就是:数据源(data)
我们先来看看 官网给的数据源是什么样的格式

  1. const data = [
  2. { name: '中国', value: '123', time: '2021' },
  3. { name: '美国', value: '69', time: '2021' },
  4. { name: '中国', value: '223', time: '2020' },
  5. { name: '美国', value: '73', time: '2020' },
  6. ...
  7. ]

我们大概可以看出,所有的表格基本上分为三个参数 name, value, time(三个参数可以自由设置),以 time 为横轴, value为纵轴,name 为区分字段。
乍一看,这个数据源非常简单,能够清楚的看到数据结构,似乎没什么问题。
那么运用在实际的项目中,我们来看看后端提供的数据源是如何的4

  1. const data = [
  2. { a: '123', b: '69', start: '2021' },
  3. { a: '223', b: '73', start: '2020' },
  4. ...
  5. ]

我相信大部分接口都是这样提供数据的,所以我主要就是将这种数据源转化为 Ant Design Charts的数据源,并结合接口,让他自动转化,自动匹配字段,转化为上述的格式—-这个是最主要的原因
其次,遵循一个系统一种类型,统一更改,样式匹配,我们可以根据具体项目进行对应的封装

注意事项

数据源提供两种方式,一共分为两种,第一种是直接将接口的值匹配给 data, 第二种则是 直接将接口放入onRequest,建议使用 onRequest

主要注意下这两个参数 fields 和 payload

  • fields:返回接口匹配字段,用于实现 自定义匹配的功能
  • payload:接口参数,请求接口时所带的参数
  • 此外,还需要注意 legend tooltip label 三个参数,分别对应 配置图例 提示语 文本标签 三个字段


演示示例

做了一些相对的参数,与操作

image.pngimage.png
image.pngimage.pngimage.pngimage.png

卡片图表(Charts.Card)

通常而言,数据看板需要看不同维度,时间段的统计,为了简便起来,我们将结合 Charts、ProCard 做成卡片图标,并结合 DatePicker, DatePicker.RangePicker, Radio 形成限定条件
我们先说说需要注意的几点:

  • 首先全部包含 Charts、ProCard的属性,并支持全局自定义配置
  • 目前卡片图表只接受 onRequest(接口) 的方式,传输数据,不能通过 data 直接渲染
  • 特别注意 condition, 目前设置三种查询状态,日期、日期时间段、单选按钮 三个查询以供图表查询
  • 其次 要注意 payload 参数,它也 Charts 不同,他会返回一个查询条件的集合,方便作为接口参数
  • 有什么好的建议,欢迎讨论,感谢~~

image.png

核心代码

这个最主要的功能就是数据转化,因此也没有太多的介绍,感兴趣可以在gitHub上参观~~

  1. export const calcData = (listAll: Object, { xField, fields, fieldsLine, type, ...props}: ChartProps) => {
  2. const list = Array.isArray(listAll) ? listAll : [listAll]
  3. if(type === 'pie' && Array.isArray(fields)){
  4. if(fields.length !== 2){
  5. message.error('请输入对应的名称和值')
  6. return []
  7. }
  8. let res:any = [];
  9. list.map((item) => {
  10. res = [...res, { ...item, label: item[fields[0]], value: item[fields[1]]}]
  11. })
  12. if(props?.pie?.zero){
  13. res = res.filter((item:any) => item.value !== 0)
  14. }
  15. return res
  16. } else if(type === 'dualAxes'){
  17. if(!fieldsLine){
  18. message.error('请传入对应的折线图数据')
  19. return [[], []]
  20. }
  21. const keys = Object.keys(fields)
  22. const values = Object.values(fields)
  23. const keys1 = Object.keys(fieldsLine)
  24. const values1 = Object.values(fieldsLine)
  25. let res:any = []
  26. let res1:any = []
  27. list.map((item) => {
  28. keys.map((ele, index) => {
  29. if((item[ele] || item[ele] === 0) && xField){
  30. res = [...res, { ...item, label: values[index], value: item[ele], time: item[xField] || index }]
  31. }
  32. })
  33. keys1.map((ele, index) => {
  34. if((item[ele] || item[ele] === 0) && xField){
  35. res1 = [...res1, { ...item, label: values1[index], value: item[ele], time: item[xField] || index }]
  36. }
  37. })
  38. })
  39. return [res, res1]
  40. } else {
  41. const keys = Object.keys(fields)
  42. const values = Object.values(fields)
  43. let res:any = []
  44. list.map((item) => {
  45. keys.map((ele, index) => {
  46. if((item[ele] || item[ele] === 0) && xField){
  47. res = [...res, { ...item, label: values[index], value: item[ele], time: type ==='pie' ? undefined : item[xField] || index }]
  48. }
  49. })
  50. })
  51. if(props?.pie?.zero){
  52. res = res.filter((item:any) => item.value !== 0)
  53. }
  54. return res
  55. }
  56. }

具体代码

文件位置:src/components/Charts

全局配置文件:src/utils/Setting/ChartsSy

如何使用

主要以柱状图使用为例

  1. import React, { useEffect } from 'react';
  2. import { Charts } from '@/components';
  3. import { Switch, Tooltip, Select } from 'antd';
  4. import { InfoCircleOutlined } from '@ant-design/icons';
  5. import { queryData } from './services';
  6. import { positionData, positionLabel, positionTooltip } from './test'
  7. import { useReactive } from 'ahooks';
  8. const TextShow: React.FC<{text: string, title: string}> = ({text='', title='', children}) => {
  9. return <span style={{marginTop: 8, fontWeight: 'bolder'}}>{text} <Tooltip title={title}><InfoCircleOutlined /></Tooltip> : {children}</span>
  10. }
  11. const { Option } = Select;
  12. const Mock: React.FC<any> = () => {
  13. const state = useReactive<any>({
  14. show: true,
  15. data: [],
  16. isRequest: true,
  17. legend: true,
  18. layout: false,
  19. position: 'top-left',
  20. labelPosition: 'middle',
  21. noSelect: false,
  22. label: true,
  23. labelContent: false,
  24. color: false,
  25. slider: true,
  26. sliderValue: false,
  27. tooltipCustom: false,
  28. tooltipTitle: false,
  29. tooltipPosition: 'right',
  30. })
  31. useEffect(() => {
  32. if(!state.isRequest){
  33. queryData({detail: 'data'}).then((res) => {
  34. state.data = [...res]
  35. })
  36. }
  37. }, [state.isRequest])
  38. const switchShow = (label:string, name:string, flag?: boolean) => {
  39. return <>
  40. <span style={{marginLeft: 12, fontWeight: 'normal'}}>{label}:</span>
  41. <Switch checked={state[name]} onChange={(e) => { if(flag){
  42. state.show = false;
  43. setTimeout(() => {state.show = true}, 200)
  44. } state[name] = e }}/>
  45. </>
  46. }
  47. const selectShow = (list: Array<any>, label:string, name:string) => {
  48. return <>
  49. <span style={{ marginLeft: 8}} >{label}:</span>
  50. <Select value={state[name]} style={{ width: 120,marginLeft:8, marginTop:8 }} onChange={(e) => { state[name] = e }}>
  51. {list.map((data, i) => <Option key={i} value={data.value}>
  52. {data.name}
  53. </Option>)}
  54. </Select>
  55. </>
  56. }
  57. return (
  58. <>
  59. <div>
  60. <TextShow text={'数据请求onRequest'} title="是否直接传入接口获取数据" >
  61. <Switch checked={state.isRequest} onChange={(e) => {state.isRequest = e }}/>
  62. </TextShow>
  63. </div>
  64. <div style={{marginTop: 4}}>
  65. <TextShow text={'图例'} title="legend的属性" >
  66. { switchShow('是否展示', 'legend') }
  67. { switchShow('是否垂直', 'layout') }
  68. { selectShow(positionData, '位置', 'position') }
  69. { switchShow('是否置灰', 'noSelect', true) }
  70. </TextShow>
  71. </div>
  72. <div style={{marginTop: 4}}>
  73. <TextShow text={'文本标签'} title="label的属性" >
  74. { switchShow('是否展示', 'label') }
  75. { selectShow(positionLabel, '位置', 'labelPosition') }
  76. { switchShow('是否改变文字', 'labelContent') }
  77. </TextShow>
  78. </div>
  79. <div style={{marginTop: 4}}>
  80. <TextShow text={'提示语'} title="tooltip的属性" >
  81. { switchShow('更改title', 'tooltipTitle') }
  82. { selectShow(positionTooltip, '位置', 'tooltipPosition') }
  83. { switchShow('是否自定义', 'tooltipCustom') }
  84. </TextShow>
  85. </div>
  86. <div style={{marginTop: 4}}>
  87. <TextShow text={'其他'} title="有关的表格其余属性都在 colum" >
  88. { switchShow('改变颜色', 'color') }
  89. { switchShow('是否启动缩略轴', 'slider') }
  90. { switchShow('改变缩略的值', 'sliderValue') }
  91. </TextShow>
  92. </div>
  93. {
  94. state.show && <Charts
  95. fields={{ a: '北方人口', b: '南方人口'}}
  96. type='column'
  97. onRequest={state.isRequest ? queryData : undefined}
  98. payload={state.isRequest ? () => ({ detail: 'data' }) : undefined}
  99. data={state.isRequest ? undefined : state.data}
  100. legend={ state.legend ? {
  101. layout: state.layout ? 'vertical' : 'horizontal',
  102. position: state.position,
  103. noSelect: state.noSelect ? ['北方人口'] : undefined,
  104. } : false}
  105. label={ state.label ? {
  106. position: state.labelPosition,
  107. content: state.labelContent ? (data:any) => {
  108. return data.name
  109. } : undefined
  110. } : false}
  111. tooltip={{
  112. title: state.tooltipTitle ? 'address' : undefined,
  113. position: state.tooltipPosition,
  114. customContent: state.tooltipCustom ? (title:any, data:any) => {
  115. return “<div style="padding: 8px 0px">
  116. <div>title</div>
  117. <div style="margin-top: 8px">
  118. <p>data[0]?.data?.label : data[0]?.data?.name</p>
  119. <p>data[1]?.data?.label : data[1]?.data?.name</p>
  120. </div>
  121. </div>“
  122. } : undefined,
  123. }}
  124. colum={{
  125. color: state.color ? ['red', 'yellow'] : undefined,
  126. slider: state.slider ? state.sliderValue ? {
  127. start: 0.1,
  128. end: 0.5
  129. } : {} : undefined,
  130. }}
  131. ></Charts>
  132. }
  133. </>
  134. );
  135. };

此外,配置的相对较少,后续会根据具体项目逐渐迭代~~