开始
在 Ant Design Pro 中,官方推荐使用 Ant Design Chart 这个 React 图表库,当然这个 也是基于 G2 的高交互可视化图形语法
在这里,我将常用的图表进行封装成一个组件,并保持原有的属性,然后通过一个参数来控制:type
共有 column(柱状图) line(折线图) dualAxes(双轴图) bar(条形图) area(面积图) pie(饼图) 六种图表
干了什么
有人可能会问,在 G2 中结构已经非常简便了,为何还需要封装?
是的,我也认为在原本的属性上已经封装的非常简便,没必要进行二次封装,同时,属性也确实非常多,我在这边写示例的时候只能列举一些常见的属性,那么封装的意义在于什么呢? 最为主要的一点就是:数据源(data)
我们先来看看 官网给的数据源是什么样的格式
const data = [
{ name: '中国', value: '123', time: '2021' },
{ name: '美国', value: '69', time: '2021' },
{ name: '中国', value: '223', time: '2020' },
{ name: '美国', value: '73', time: '2020' },
...
]
我们大概可以看出,所有的表格基本上分为三个参数 name, value, time(三个参数可以自由设置),以 time 为横轴, value为纵轴,name 为区分字段。
乍一看,这个数据源非常简单,能够清楚的看到数据结构,似乎没什么问题。
那么运用在实际的项目中,我们来看看后端提供的数据源是如何的4
const data = [
{ a: '123', b: '69', start: '2021' },
{ a: '223', b: '73', start: '2020' },
...
]
我相信大部分接口都是这样提供数据的,所以我主要就是将这种数据源转化为 Ant Design Charts的数据源,并结合接口,让他自动转化,自动匹配字段,转化为上述的格式—-这个是最主要的原因
其次,遵循一个系统一种类型,统一更改,样式匹配,我们可以根据具体项目进行对应的封装
注意事项
数据源提供两种方式,一共分为两种,第一种是直接将接口的值匹配给 data, 第二种则是 直接将接口放入onRequest,建议使用 onRequest
主要注意下这两个参数 fields 和 payload
- fields:返回接口匹配字段,用于实现 自定义匹配的功能
- payload:接口参数,请求接口时所带的参数
- 此外,还需要注意 legend tooltip label 三个参数,分别对应 配置图例 提示语 文本标签 三个字段
演示示例
做了一些相对的参数,与操作
卡片图表(Charts.Card)
通常而言,数据看板需要看不同维度,时间段的统计,为了简便起来,我们将结合 Charts、ProCard 做成卡片图标,并结合 DatePicker, DatePicker.RangePicker, Radio 形成限定条件
我们先说说需要注意的几点:
- 首先全部包含 Charts、ProCard的属性,并支持全局自定义配置
- 目前卡片图表只接受 onRequest(接口) 的方式,传输数据,不能通过 data 直接渲染
- 特别注意 condition, 目前设置三种查询状态,日期、日期时间段、单选按钮 三个查询以供图表查询
- 其次 要注意 payload 参数,它也 Charts 不同,他会返回一个查询条件的集合,方便作为接口参数
- 有什么好的建议,欢迎讨论,感谢~~
核心代码
这个最主要的功能就是数据转化,因此也没有太多的介绍,感兴趣可以在gitHub上参观~~
export const calcData = (listAll: Object, { xField, fields, fieldsLine, type, ...props}: ChartProps) => {
const list = Array.isArray(listAll) ? listAll : [listAll]
if(type === 'pie' && Array.isArray(fields)){
if(fields.length !== 2){
message.error('请输入对应的名称和值')
return []
}
let res:any = [];
list.map((item) => {
res = [...res, { ...item, label: item[fields[0]], value: item[fields[1]]}]
})
if(props?.pie?.zero){
res = res.filter((item:any) => item.value !== 0)
}
return res
} else if(type === 'dualAxes'){
if(!fieldsLine){
message.error('请传入对应的折线图数据')
return [[], []]
}
const keys = Object.keys(fields)
const values = Object.values(fields)
const keys1 = Object.keys(fieldsLine)
const values1 = Object.values(fieldsLine)
let res:any = []
let res1:any = []
list.map((item) => {
keys.map((ele, index) => {
if((item[ele] || item[ele] === 0) && xField){
res = [...res, { ...item, label: values[index], value: item[ele], time: item[xField] || index }]
}
})
keys1.map((ele, index) => {
if((item[ele] || item[ele] === 0) && xField){
res1 = [...res1, { ...item, label: values1[index], value: item[ele], time: item[xField] || index }]
}
})
})
return [res, res1]
} else {
const keys = Object.keys(fields)
const values = Object.values(fields)
let res:any = []
list.map((item) => {
keys.map((ele, index) => {
if((item[ele] || item[ele] === 0) && xField){
res = [...res, { ...item, label: values[index], value: item[ele], time: type ==='pie' ? undefined : item[xField] || index }]
}
})
})
if(props?.pie?.zero){
res = res.filter((item:any) => item.value !== 0)
}
return res
}
}
具体代码
文件位置:src/components/Charts
全局配置文件:src/utils/Setting/ChartsSy
如何使用
主要以柱状图使用为例
import React, { useEffect } from 'react';
import { Charts } from '@/components';
import { Switch, Tooltip, Select } from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import { queryData } from './services';
import { positionData, positionLabel, positionTooltip } from './test'
import { useReactive } from 'ahooks';
const TextShow: React.FC<{text: string, title: string}> = ({text='', title='', children}) => {
return <span style={{marginTop: 8, fontWeight: 'bolder'}}>{text} <Tooltip title={title}><InfoCircleOutlined /></Tooltip> : {children}</span>
}
const { Option } = Select;
const Mock: React.FC<any> = () => {
const state = useReactive<any>({
show: true,
data: [],
isRequest: true,
legend: true,
layout: false,
position: 'top-left',
labelPosition: 'middle',
noSelect: false,
label: true,
labelContent: false,
color: false,
slider: true,
sliderValue: false,
tooltipCustom: false,
tooltipTitle: false,
tooltipPosition: 'right',
})
useEffect(() => {
if(!state.isRequest){
queryData({detail: 'data'}).then((res) => {
state.data = [...res]
})
}
}, [state.isRequest])
const switchShow = (label:string, name:string, flag?: boolean) => {
return <>
<span style={{marginLeft: 12, fontWeight: 'normal'}}>{label}:</span>
<Switch checked={state[name]} onChange={(e) => { if(flag){
state.show = false;
setTimeout(() => {state.show = true}, 200)
} state[name] = e }}/>
</>
}
const selectShow = (list: Array<any>, label:string, name:string) => {
return <>
<span style={{ marginLeft: 8}} >{label}:</span>
<Select value={state[name]} style={{ width: 120,marginLeft:8, marginTop:8 }} onChange={(e) => { state[name] = e }}>
{list.map((data, i) => <Option key={i} value={data.value}>
{data.name}
</Option>)}
</Select>
</>
}
return (
<>
<div>
<TextShow text={'数据请求onRequest'} title="是否直接传入接口获取数据" >
<Switch checked={state.isRequest} onChange={(e) => {state.isRequest = e }}/>
</TextShow>
</div>
<div style={{marginTop: 4}}>
<TextShow text={'图例'} title="legend的属性" >
{ switchShow('是否展示', 'legend') }
{ switchShow('是否垂直', 'layout') }
{ selectShow(positionData, '位置', 'position') }
{ switchShow('是否置灰', 'noSelect', true) }
</TextShow>
</div>
<div style={{marginTop: 4}}>
<TextShow text={'文本标签'} title="label的属性" >
{ switchShow('是否展示', 'label') }
{ selectShow(positionLabel, '位置', 'labelPosition') }
{ switchShow('是否改变文字', 'labelContent') }
</TextShow>
</div>
<div style={{marginTop: 4}}>
<TextShow text={'提示语'} title="tooltip的属性" >
{ switchShow('更改title', 'tooltipTitle') }
{ selectShow(positionTooltip, '位置', 'tooltipPosition') }
{ switchShow('是否自定义', 'tooltipCustom') }
</TextShow>
</div>
<div style={{marginTop: 4}}>
<TextShow text={'其他'} title="有关的表格其余属性都在 colum" >
{ switchShow('改变颜色', 'color') }
{ switchShow('是否启动缩略轴', 'slider') }
{ switchShow('改变缩略的值', 'sliderValue') }
</TextShow>
</div>
{
state.show && <Charts
fields={{ a: '北方人口', b: '南方人口'}}
type='column'
onRequest={state.isRequest ? queryData : undefined}
payload={state.isRequest ? () => ({ detail: 'data' }) : undefined}
data={state.isRequest ? undefined : state.data}
legend={ state.legend ? {
layout: state.layout ? 'vertical' : 'horizontal',
position: state.position,
noSelect: state.noSelect ? ['北方人口'] : undefined,
} : false}
label={ state.label ? {
position: state.labelPosition,
content: state.labelContent ? (data:any) => {
return data.name
} : undefined
} : false}
tooltip={{
title: state.tooltipTitle ? 'address' : undefined,
position: state.tooltipPosition,
customContent: state.tooltipCustom ? (title:any, data:any) => {
return “<div style="padding: 8px 0px">
<div>title</div>
<div style="margin-top: 8px">
<p>data[0]?.data?.label : data[0]?.data?.name</p>
<p>data[1]?.data?.label : data[1]?.data?.name</p>
</div>
</div>“
} : undefined,
}}
colum={{
color: state.color ? ['red', 'yellow'] : undefined,
slider: state.slider ? state.sliderValue ? {
start: 0.1,
end: 0.5
} : {} : undefined,
}}
></Charts>
}
</>
);
};
此外,配置的相对较少,后续会根据具体项目逐渐迭代~~