目录
公司的小程序项目需求中需要使用图表展示一些数据趋势,于是就去调研了一些图表的组件库,最后结合各组件库的图表样式、性能以及后期维护和扩展等方面决定使用antv/f2这个图表库,记录一下使用方式、样式等属性的修改方法以及遇到的一些坑点。
一、项目背景
项目技术框架是使用的Taro脚手架搭建的(React+ts),antv/f2是适用于移动端的图表组件库,适用于vue、react以及小程序(快速上手 | F2),官方文档上也介绍了如何在vue、react以及小程序中使用。but官网上介绍的小程序的使用方法是在小程序原生框架上的,墙裂建议文档上可以介绍一下在taro项目下的使用方式,因为最后在taro项目下成功把第一个图表demo跑下来发现,还是跟文档上介绍的小程序的使用方式挺不一样的
二、使用方式
1)npm install @antv/f2 --save
这里需要注意的是我用的antv/f2的版本是4.0.29版本的,不同版本的组件的调用方式是不一样的
我踩的第一个坑就是我在项目中安装的版本是4.x.x的,但是我的调用方式是import F2 from '@antv/f2' ,于是就一直报错,后面看文档才知道需要import {} from '@antv/f2'这样使用,所以大家开发的时候一定要仔细看文档哦
2)封装Canvas组件,后期在调用antv/f2画图表组件时都需要基于这个组件,直接附上踩完坑的代码,并且代码注释上会标明一些我踩的坑点(tips1,tips2,tips3...),给各位第一次使用的小伙伴们节省一点时间学(摸)习(鱼)呀
import { ReactNode, FC, useEffect, useRef, memo,CSSProperties } from 'react';
import { useReady, createSelectorQuery, getSystemInfoSync } from '@tarojs/taro';
import { ITouchEvent, CanvasTouchEvent, Canvas } from '@tarojs/components';
import { Canvas as FFCanvas } from '@antv/f2';
import useUnmount from './use-unmount';
interface F2CanvasProps {
id?: string;//tips1:在同一个组件渲染多个图表时,每个图表的id需要唯一
style?:CSSProperties | string;
children?: ReactNode;
}
type CanvasEvent = ITouchEvent | CanvasTouchEvent;
interface CanvasElement {
dispatchEvent: (type: string, event: CanvasEvent) => void;
}
function wrapEvent(e: CanvasEvent) {
if (e && !e.preventDefault) {
e.preventDefault = function () {};
}
return e;
}
const F2Canvas: FC<F2CanvasProps> = (props) => {
const { children, id = 'f2Canvas',style = 'width:100%;height:100%;display:block;padding: 0;margin: 0;', } = props;
const canvasRef = useRef<FFCanvas>();
const canvasElRef = useRef<CanvasElement>();
const childrenRef = useRef<ReactNode>(children);
useEffect(() => {
childrenRef.current = children;
canvasRef.current?.update({ children });
}, [children]);
useUnmount(() => {
canvasRef.current?.destroy();
});
useEffect(() => {
//tips3:一开始使用的是taro的useReady,后面发现在页面控制图表组件条件显示时会出现图表组件如果隐藏了再显示的时候会出现空白的情况,就是没有绘制成功,后面查找问题才发现是使用useReady导致的问题,于是改成了useEffect
const renderCanvas = () => {
const query = createSelectorQuery();
query
.select(`#${id}`)
.fields({
node: true,
size: true,
})
.exec((res) => {
const { node, width, height } = res[0];
const pixelRatio = getSystemInfoSync().pixelRatio;
// 高清设置
node.width = width * pixelRatio;
node.height = height * pixelRatio;
const context = node.getContext('2d');
const canvas = new FFCanvas({
pixelRatio,
width,
height,
context,
children: childrenRef.current,
});
canvas.render();
canvasRef.current = canvas;
canvasElRef.current = canvas.canvas.get('el');
// console.log('canvasElRef', canvasRef);
});
};
setTimeout(renderCanvas);//tips2:延迟是为了确保能获取到 node 对象,直接获取会出现 node 为 null 的情况
}, []);
const handleClick = (e: ITouchEvent) => {
const canvasEl = canvasElRef.current;
if (!canvasEl) {
return;
}
const event = wrapEvent(e);
// 包装成 touch 对象
event.touches = [e.detail];
canvasEl.dispatchEvent('click', event);
};
const handleTouchStart = (e: CanvasTouchEvent) => {
const canvasEl = canvasElRef.current;
if (!canvasEl) {
return;
}
canvasEl.dispatchEvent('touchstart', wrapEvent(e));
};
const handleTouchMove = (e: CanvasTouchEvent) => {
const canvasEl = canvasElRef.current;
if (!canvasEl) {
return;
}
canvasEl.dispatchEvent('touchmove', wrapEvent(e));
};
const handleTouchEnd = (e: CanvasTouchEvent) => {
const canvasEl = canvasElRef.current;
if (!canvasEl) {
return;
}
canvasEl.dispatchEvent('touchend', wrapEvent(e));
};
return (
<Canvas
id={id}
type='2d'
style={style}
onClick={handleClick}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
/>
);
};
export default memo(F2Canvas);
use-unmount.ts
import { useEffect, useRef } from 'react';
function useUnmount(effectCallback: () => void) {
const ref = useRef(effectCallback);
ref.current = effectCallback;
useEffect(() => () => ref.current(), []);
}
export default useUnmount;
3)封装完canvas组件,那我们就来写一个demo吧
import F2Canvas from '@/components/common/F2Canvas';
import { Axis, Chart, Line, Tooltip, PointGuide, Area } from '@antv/f2';
const data = [{date:'2021-07-18',score:'26'},{date:'2022-07-18',score:'27'}]
const pointRecords = [{date:'2021-07-18',score:'26'}]//该点的样式为下面设置的style
<F2Canvas id='demo'>
<Chart data={data}>
<Axis field='date'></Axis> //x轴
<Axis field='score'></Axis> //y轴
<Line x='date' y='score' shape='smooth' /*曲线是否平滑*/></Line>//折线图
<PointGuide records={pointRecords}
style={{fill: '#fff',lineWidth: 2,stroke: '#F1825E'}}></PointGuide>//标记点
<Tooltip></Tooltip>//手指移到对应的点时的提示框
</Chart>
</F2Canvas>
三、自定义图表的样式
由于我在项目中只用了折线图,所以在这里就只记录折线图的样式修改,后续如果有其他图表的使用再补上。
1)修改折现的颜色:如果我们要定义的折线的样式不是渐变的,那就直接设置color="#F17F5B"就可以了,但是如果颜色是渐变的,则这样设置color="l(180) 0:#f0805d8e 0.5:#F17F5B 1:#f17f5c26"
需要注意的是当图表只有一个点时,color如果设置为渐变控制台会有报错信息,因此我们可以设置color={data.length === 1? '#F17F5B': 'l(180) 0:#f0805d8e 0.5:#F17F5B 1:#f17f5c26'}
2)当我们需要在某个特殊的点进行一些标记时则可以使用
3)当我们x轴或y轴的数据是2022-07-18 但是我们希望在图表上展示时是07/18时,可设置<Axis formatter={(data: string)=>{return dayjs(data).format('MM/DD');}} />
4)当数据比较多时,可以设置x轴或者y轴的tickCount去设置要显示几个点或者设置nice={true}自适应显示点的个数,这样不会导致数据多时挤在一起
更多的图表设置可查看文档,或者点击进对应的组件的index.d.ts文件去查看