本文主要介绍wrn-echarts项目。接下来,本文将从诞生背景、设计实现、效果演示等方面详细介绍该项目。官网:https://wuba.github.io/wrn-ec...源代码:https://github.com/wuba/wrn-e...背景在日常的业务需求开发中,我们经常会遇到需要绘制图表的场景,其中我们使用频率最高的图表库是ECharts。ECharts作为市面上最成熟的图表库之一,主要用于Web端。官方也为小程序端提供了解决方案。但是在RN开发场景下,目前还没有更好的实现方式。面对这种情况,之前我们的解决方案是:放弃ECharts,使用原生为RN开发的图表库,如react-native-charts-wrapper、victory-native等。通过Webview使用web端的ECahrts,如react-native-echarts-pro,native-Scheme1比如echarts,RN现有的图表库在风格和交互方面远远落后于ECahrts,图表的丰富度也有所不足。尤其是在有多种需求的场景下,需要单独为RN设计UI交互。实施成本很高。方案二,通过Webview,当页面有多个图表或图表元素过多时,会遇到性能瓶颈。比如Android端的大数据面积图、单轴散点图都会出现白屏。而且在正常渲染过程中会有比较明显的卡顿和掉帧现象。因此,我们希望开发一个可以使用RN原生渲染控件的图表库,将WebECharts的能力集成到RN应用中,以提高开发效率和不同平台产品体验的一致性,同时实现真正的跨端图表为我们以后建库打下基础。可行性分析既然要使用RN原生渲染,首先看一下RN目前支持的图形库:react-native-svg:提供了一个可以在RN中使用的基础SVG图形库,类似于上的SVG渲染模式网络。渲染图形(本方案以下简称WRNSVG模式)react-native-skia:Skia是一个跨平台的图形渲染引擎。这个库在RN中引入了Skia的2d图形库,同时也提供了一个支持SVG格式图片的ImageSVG组件,根据渲染分析(以下简称本方案中的WRNSkia模式),以上两种方案的实现核心是获取ECharts图表的SVG图形数据。而我们知道ECharts本身是支持SVG格式的渲染的,所以我们去ECharts代码仓库查看相关的实现,看看能不能拿到我们想要的数据。查看源码发现这部分功能是通过调用zrender库的SVGPainter组件实现的,我们可以通过修改渲染组件获取图表的SVG图形数据,所以我们的方案是可行的。并且由于WRNSVG模式和WRNSkia模式这两种方案的核心流程相似,我们计划同时支持这两种实现方式,让用户可以选择适合自己的。原理及实现1.架构设计2.核心流程以WRNSVG模式为例,核心流程为:替换ECharts的SVGRenderer,将注册的SVGPainter替换为自定义的CustomSVGPainterCustomSVGPainter继承自SVGPainter,重写了构造函数和刷新函数的一部分实现,当图表数据初始化或更新时,调用注册在SVGComponent上的patch函数,将计算出的新SVG数据传递给定义SVGComponent,SVGComponent管理当前图表实例,有一个核心patch函数用于接收实时SVG数据,然后调用SVGElement函数。SVGElement函数遍历SVG的所有节点,将其转化为react-native-svg提供的对应的SVG元素进行最终渲染。与WRNSkia模式的区别:WRNSkia模式整体流程比WRNSVG模式简单,在定义的SkiaComponent组件上有一个核心方法patchString,patchString接收变化的SVG数据,合并转换成SVG图像格式数据,传递给用于整体渲染的react-native-skia的ImageSVG组件。3、处理TouchEvent(手势事件)WebECharts事件是鼠标事件,如click、dblclick、mousedown、mousemove等,通过鼠标事件触发图表元素的显示或动画。RNECharts需要将移动端的TouchEvent模拟为鼠标事件,派发给EChartsinit方法生成的图表实例。比如跟随鼠标在图表上显示图例的动作对应的是移动端的TouchStart+TouchMove,mousedown+mousemove对应的是鼠标事件。另一个例子是图表的缩放。移动端是两指按下缩放,对应的mousewheel事件转化为鼠标的mousewheel事件,通过两指距离的变化计算对应的mousewheel滚动距离。关键代码:ConvertTouchEventtoMouseEvent//...PanResponder.create({onPanResponderGrant:({nativeEvent})=>{//动作开始,这里转换成鼠标点击和移动事件dispatchEvent(zrenderId,['mousedown','mousemove'],nativeEvent,'start');},onPanResponderMove:({nativeEvent})=>{//处理手指移动constlength=nativeEvent.touches.length;if(length===1){//在这里处理单指移动...}elseif(length===2){//在这里处理两指移动...if(!zooming){//...}else{//这里它转化为滚轮的事件const{initialX,initialY,prevDistance}=pan.current;constdelta=distance-prevDistance;pan.current.prevDistance=distance;dispatchEvent(zrenderId,['mousewheel'],nativeEvent,undefined,{zrX:initialX,zrY:initialY,zrDelta:delta/120,});}}},onPanResponderRelease:({nativeEvent})=>{//动作结束,这里转化为鼠标点击和释放操作...},}),将MouseEvent应用到ECharts图表实例函数dispatchEvent(zrenderId:number,types:HandlerName[],native事件:NativeTouchEvent,阶段:'开始'|'结束'|'改变'|undefined,props:any={zrX:nativeEvent.locationX,zrY:nativeEvent.locationY,}){if(zrenderId){varhandler=getInstance(zrenderId).handler;}types.forEach(function(type){handler.dispatch(type,{preventDefault:noop,stopImmediatePropagation:noop,stopPropagation:noop,...props,});stage&&handler.processGesture(wrapTouch(nativeEvent),stage);});}}4。问题及解决方案以上流程开发完成后,我们先用基本的简单图表进行测试,然后开始对比测试ECharts的各种图表类型。在测试过程中,我们发现并处理了很多图表显示的异常,例如:4.1WRNSkia模式下的中文乱码我们发现在WRNSkia模式下渲染时,图表显示的汉字是乱码。他们为什么乱码?首先我们猜测是字体有问题。查看字体文件信息,没有中文字体。检查反应本机滑雪。原因是fontfallbackisnotsupported(fontfallback:字符串中的某些字符在当前字体中不可用。支持时回退到在字体队列中检索支持的字体)。于是我们找到一个中文字体文件Skia.Typeface.MakeFreeTypeFaceFromData,在运行时导入,这时候就可以正常显示了。但是这个时候我们也考虑到不同系统使用的字体是不一样的。一个中文字体文件很大,我们不可能把所有的字体都导入进去。但是,如果只提供一种字体,体验肯定不友好。那我们是不是可以直接使用系统自带的中文字体呢?所以我们研究了iOS和Android支持的中文字体,最后我们决定在有中文文本设置的时候,Android设置font-family使用NotoSans,iOS使用PingFangSC。4.2字体重叠和空格中文乱码问题解决后,我们发现出现了英文字体重叠的问题。我们猜测是字体宽度的计算有问题,因为中文是等宽字体,所以没有问题。查看zrender的measureText实现,字体在svgrender中写成sans-serif,根据这个字体计算出一个宽度。如果其他字体的宽度超过,就会出现重叠。如果字体宽度小,就会出现空白。所以我们把这部分改成使用NotoSans和PingFangSC来计算宽度,解决了。项目中如何使用在实际应用中,wrn-echarts的整体流程与ECharts类似:根据使用的渲染方式,选择安装react-native-svg或@shopify/react-native-skia来安装wrn-echarts并引入相关组件使用wrn-echarts的SVGRenderer替换ECharts的SVGRenderer写入图表选项配置信息使用SkiaChart/SvgChart组件示例import*asechartsfrom'echarts/core';import{useEffect,useRef}from'react';import{SVGRenderer,SvgChart}from'wrn-echarts';//注册必要的组件echarts.use([SVGRenderer,//这里的SVGRenderer是wrn-echarts的SVGRenderer]);exportdefaultfunctionEchartsPage(){//svgRef用于保存图表实例constsvgRef=useRef
