结合接口数据一键配置数据图表(Charts)

1,147 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

开始

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

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

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

gitHub地址:Ant Design Pro V5

在线地址:Domesy Ant Design Pro V5

干了什么

有人可能会问,在 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>
      }
    </>
    );
  };

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