React 中使用 AntV G6
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React 中使用 AntV G6相关的知识,希望对你有一定的参考价值。
参考技术A G6 V3.1.0. Github: https://github.com/antvis/g6G6是一个纯JS库,不与任何框架耦合,也就是可以在任何前端框架中使用,如 React、Vue、Angular 等。由于我们内部绝大多数都是基于 React 技术栈的,所以我们也仅提供一个 G6 在 React 中使用的 Demo。
在 React 中使用 G6,和在 html 中使用基本相同,唯一比较关键的区分就是在实例化 Graph 时,要 保证 DOM 容器渲染完成,并能获取到 DOM 元素 。
在 Demo 中,我们以一个简单的流程图为例,实现如下的效果。
Demo 包括以下功能点:
- 自定义节点;
- 自定义边;
- 节点的 tooltip;
- 边的 tooltip;
- 节点上面弹出右键菜单;
- tooltip 及 ContextMenu 如何渲染自定义的 React 组件。
在 React 中,通过 ref.current 获取到真实的 DOM 元素。
import React, useEffect,useState from'react';
import ReactDOM from 'react-dom';
import data from './data';
import G6 from'@antv/g6';
export default function()
const ref=React.useRef(null)
let graph=null
useEffect(()=>
if(!graph)
graph=newG6.Graph(
container:ref.current,
width:1200,
height:800,
modes:
default: ['drag-canvas']
,
layout:
type:'dagre',
direction:'LR'
,
defaultNode:
shape:'node',
labelCfg:
style:
fill:'#000000A6',
fontSize:10
,
style:
stroke:'#72CC4A',
width:150
,
defaultEdge:
shape:'polyline'
)
graph.data(data)
graph.render()
, [])
return(<divref=ref></div> );
节点和边的 tooltip、节点上的右键菜单,G6 中内置的很难满足样式上的需求,这个时候我们就可以通过渲染自定义的 React 组件来实现。Tooltip 和 ContextMenu 都是普通的 React 组件,样式完全由用户控制。交互过程中,在G6 中需要做的事情就是确定何时渲染组件,以及渲染到何处。在 G6 中获取到是否渲染组件的标识值和渲染位置后,这些值就可以使用 React state 进行管理,后续的所有工作就全部由 React 负责了。
// 边tooltip坐标
const [showNodeTooltip,setShowNodeTooltip]=useState(false)
const [nodeTooltipX,setNodeToolTipX]=useState(0)
const[nodeTooltipY,setNodeToolTipY]=useState(0)
// 监听node上面mouse事件
graph.on('node:mouseenter',evt=>
const item=evt
const model=item.getModel()
const x,y=model
const point=graph.getCanvasByPoint(x,y)
setNodeToolTipX(point.x-75)
setNodeToolTipY(point.y+15)
setShowNodeTooltip(true)
)
// 节点上面触发mouseleave事件后隐藏tooltip和
ContextMenugraph.on('node:mouseleave', ()=>
setShowNodeTooltip(false)
)
return (<divref=ref>showNodeTooltip&&<NodeTooltipsx=nodeTooltipXy=nodeTooltipY/></div>);
完整的 Demo 源码请👉戳 这里 。
react + zarm + antV F2 实现账单数据统计饼图效果
需要实现的效果
为了方便展示,饼图放到右边标明:
实现过程
这里我们尝试用一下 antV F2 移动端可视化引擎来实现饼图效果
1.F2 移动端可视化引擎
F2 是一个专注于移动端,面向常规统计图表,开箱即用的可视化引擎,完美支持 H5 环境同时兼容多种环境(Node, 小程序),完备的图形语法理论,满足你的各种可视化需求,专业的移动设计指引为你带来最佳的移动端图表体验。
如何在 React 中使用:
npm install @antv/f2 --save
npm install @antv/f2-react --save
我们可以参考这里的例子:
2.封装饼图组件
我们在 components 里添加 PieChart 文件夹,里面新增 index.jsx
跟 style.module.less
文件,添加代码如下:
import Canvas from "@antv/f2-react";
import Chart, Interval, Legend, PieLabel from "@antv/f2";
import PropTypes from 'prop-types';
import s from './style.module.less';
const PieChart = ( chartData = [] ) =>
console.log('进入 PieChart', chartData)
return (
<div className=s.pieChart>
chartData.length > 0 ? <Canvas pixelRatio=window.devicePixelRatio>
<Chart
data=chartData
coord=
type: "polar",
transposed: true,
radius: 1,
scale=
>
<Interval
x="payType"
y="percent"
adjust="stack"
color=
field: "type_name",
range: ['#5a71c1', '#9eca7e', '#f3ca6b', '#df6e6b', '#84bedb', '#589f74', '#ed8a5c', '#1e80ff', '#fc5531', '#67c23a'],
selection=
selectedStyle: (record) =>
const yMax, yMin = record;
return
// 半径放大 1.1 倍
r: (yMax - yMin) * 1.1,
;
,
/>
<Legend position="top" marker="square" nameStyle=
fontSize: '14',
fill: '#000',
style=
justifyContent: 'space-between',
flexDirection: 'row',
flexWrap: 'wrap'
/>
<PieLabel
sidePadding=0
label1=(data) =>
return
text: `$data.type_name:$data.percent%`,
fill: "#0d1a26",
fontSize: 12,
;
/>
</Chart>
</Canvas> : null
</div>
);
;
PieChart.propTypes =
chartData: PropTypes.array,
export default PieChart;
.pie-chart
min-height: 200px;
3.编写数据分析页面的逻辑
我们在 container 里添加 Data 文件夹,里面新增 index.jsx
跟 style.module.less
文件,以及 api 相关配置文件,添加代码如下:
import React, useEffect, useRef, useState from 'react';
import Icon, Progress, Toast from 'zarm';
import cx from 'classnames';
import dayjs from 'dayjs';
import CustomIcon from '@/components/CustomIcon';
import PopupDate from '@/components/PopupDate';
import PieChart from '@/components/PieChart';
import analysisMonthBill from "./api/index.js";
import typeMap from '@/utils';
import s from './style.module.less';
const Data = () =>
const monthRef = useRef();
const [currentMonth, setCurrentMonth] = useState(dayjs().format('YYYY-MM')); // 当前月份
const [totalType, setTotalType] = useState('expense'); // 收入或支出类型
const [totalExpense, setTotalExpense] = useState(0); // 总支出
const [totalIncome, setTotalIncome] = useState(0); // 总收入
const [expenseData, setExpenseData] = useState([]); // 支出数据
const [incomeData, setIncomeData] = useState([]); // 收入数据
const [pieType, setPieType] = useState('expense'); // 饼图的「收入」和「支出」控制
const [chartData, setChartData] = useState([]); // 饼图需要渲染的数据
useEffect(() =>
getData();
, [currentMonth]);
// 获取数据详情
const getData = async () =>
const status, desc, data = await analysisMonthBill(
billDate: currentMonth // 示例值:2022-02
);
console.log('获取数据详情', status, desc, data)
if(status === 200)
// 总收支
setTotalExpense(data.totalExpense);
setTotalIncome(data.totalIncome);
// 过滤支出和收入
let expense_data = data.dataList.filter(item => item.pay_type == 1).sort((a, b) => b.number - a.number); // 过滤出账单类型为支出的项
let income_data = data.dataList.filter(item => item.pay_type == 2).sort((a, b) => b.number - a.number); // 过滤出账单类型为收入的项
expense_data = expense_data.map(item =>
return
...item,
payType: item.pay_type.toString(),
percent: Number(Number((item.number / Number(data.totalExpense)) * 100).toFixed(2))
)
income_data = income_data.map(item =>
return
...item,
payType: item.pay_type.toString(),
percent: Number(Number((item.number / Number(data.totalIncome)) * 100).toFixed(2))
)
setExpenseData(expense_data);
setIncomeData(income_data);
// 设置饼图数据
setChartData(pieType == 'expense' ? expense_data : income_data);
else
Toast.show(desc);
;
// 月份弹窗开关
const monthShow = () =>
monthRef.current && monthRef.current.show();
;
// 选择月份
const selectMonth = (item) =>
setCurrentMonth(item);
;
// 切换收支构成类型
const changeTotalType = (type) =>
setTotalType(type);
;
// 切换饼图收支类型
const changePieType = (type) =>
setPieType(type);
setChartData(type == 'expense' ? expenseData : incomeData);
return <div className=s.data>
<div className=s.total>
<div className=s.time onClick=monthShow>
<span>currentMonth</span>
<Icon className=s.date type="date" />
</div>
<div className=s.title>共支出</div>
<div className=s.expense>¥ totalExpense </div>
<div className=s.income>共收入¥ totalIncome </div>
</div>
<div className=s.structure>
<div className=s.head>
<span className=s.title>收支构成</span>
<div className=s.tab>
<span onClick=() => changeTotalType('expense') className=cx( [s.expense]: true, [s.active]: totalType == 'expense' )>支出</span>
<span onClick=() => changeTotalType('income') className=cx( [s.income]: true, [s.active]: totalType == 'income' )>收入</span>
</div>
</div>
<div className=s.content>
(totalType == 'expense' ? expenseData : incomeData).map(item => <div key=item.type_id className=s.item>
<div className=s.left>
<div className=s.type>
<span className=cx( [s.expense]: totalType == 'expense', [s.income]: totalType == 'income' )>
<CustomIcon
type=item.type_id ? typeMap[item.type_id].icon : 1
/>
</span>
<span className=s.name> item.type_name </span>
</div>
<div className=s.progress>¥ Number(item.number).toFixed(2) || 0 </div>
</div>
<div className=s.right>
<div className=s.percent>
<Progress
shape="line"
percent=Number((item.number / Number(totalType == 'expense' ? totalExpense : totalIncome)) * 100).toFixed(2)
theme='primary'
/>
</div>
</div>
</div>)
</div>
<div className=s.proportion>
<div className=s.head>
<span className=s.title>收支构成</span>
<div className=s.tab>
<span onClick=() => changePieType('expense') className=cx( [s.expense]: true, [s.active]: pieType == 'expense' )>支出</span>
<span onClick=() => changePieType('income') className=cx( [s.income]: true, [s.active]: pieType == 'income' )>收入</span>
</div>
</div>
/* 饼图 */
<PieChart chartData=chartData />
</div>
</div>
<PopupDate ref=monthRef mode="month" onSelect=selectMonth />
</div>
export default Data
.data
min-height: 100%;
background-color: #f5f5f5;
.total
background-color: #fff;
display: flex;
flex-direction: column;
align-items: center;
padding: 24px 0;
margin-bottom: 10px;
.time
position: relative;
width: 96px;
padding: 6px;
background-color: #f5f5f5;
color: rgba(0, 0, 0, .9);
border-radius: 4px;
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
span:nth-of-type(1)::after
content: '';
position: absolute;
top: 9px;
bottom: 8px;
right: 28px;
width: 1px;
background-color: rgba(0, 0, 0, .5);
.date
font-size: 16px;
color: rgba(0, 0, 0, .5);
.title
color: #007fff;
margin-bottom: 8px;
font-size: 12px;
font-weight: 500;
.expense
font-sizeReact下使用antv/g6实现树图/流程图