wrn-echarts:在 RN 中使用 ECharts

本篇文章主要介绍 wrn-echarts 项目,接下来本文会从诞生背景、设计与实现、效果演示等环节向大家详细介绍这个项目。

背景

在日常进行业务需求的开发时,经常会遇到需要绘制图表的场景,其中我们使用频率最高的图表库是 ECharts。ECharts 作为市面上最成熟的图表库之一,主要面向 Web 端使用,官方对小程序端也提供了解决方案,而在 RN 的开发场景中却没有比较好的实现方法,面对这种情况以前我们的解决方案有:

  1. 放弃 ECharts,使用针对 RN 原生开发的图表库,如 react-native-charts-wrapper、victory-native 等

  1. 通过 Webview 来使用 web 端的 ECahrts,如 react-native-echarts-pro、native-echarts 等

方案 1,RN 现有图表库的样式与交互与 ECahrts 相比有较大差距,图表的丰富性也不足,尤其是有多端需求的场景下,需要为 RN 单独进行 UI 交互设计,设计与实现成本高。

方案 2,通过 Webview 的方式,当页面上有多个图表或者图表元素过多时,会遇到性能方面的瓶颈,比如安卓端的大数据量面积图、单轴散点图等会有白屏现象,而且正常渲染过程中也会有比较明显的卡顿、掉帧的情况。

所以,我们希望开发一个图表库,可以使用 RN 的原生渲染控件将 Web ECharts 的能力集成到 RN 应用中,来提高开发效率以及不同平台产品体验的一致性,同时为我们将来实现真正的跨端图表库打下基础。

可行性分析

既然要用 RN 原生渲染,那首先看目前 RN 支持的图形库有哪些:

  • react-native-svg:提供可在 RN 使用的基础 SVG 图形库,该库通过类似于 Web 端的 SVG 渲染模式来渲染图形(此方案下文中简称 WRNSVG 模式)

  • react-native-skiaSkia 是跨平台的图形渲染引擎,该库将 Skia 的 2d 图形库引入 RN,同时也提供了 ImageSVG 组件,支持对 SVG 格式的图片进行渲染(此方案下文中简称 WRNSkia 模式)

分析得之,上述两种方案实现的核心都是要获取到 ECharts 图表的 SVG 图形数据。而我们知道 ECharts 本身就支持 SVG 格式的渲染,所以我们就去 ECharts 代码仓库查看相关的实现,看是否能获取到我们想要的数据。

查看源码时我们发现这部分功能是调用了 zrender 库的 SVGPainter 组件来实现,而且我们可以通过改造该渲染组件来获取图表的 SVG 图形数据,所以我们这个方案是可行的。并且因为 WRNSVG 模式和 WRNSkia 模式这两种方案核心流程相似,所以我们计划同时支持这两种实现方式,让用户可以自己选择合适自己的一种。

原理与实现

1. 架构设计

2. 核心流程

以 WRNSVG 模式为例,核心工作流程为:

  1. 替换 ECharts 的 SVGRenderer,将注册的 SVGPainter 替换为自定义的 CustomSVGPainter

  1. CustomSVGPainter 继承自 SVGPainter,重写了构造函数与 refresh 函数中的部分实现,当图表数据初始化或者更新时,调用 SVGComponent 上注册的 patch 函数,并把计算出的新的 SVG 数据传递过去

  1. 定义 SVGComponent,该组件管理当前图表实例,上有核心的 patch 函数,用来接收实时 SVG 数据,然后调用 SVGElement 函数

  1. SVGElement 函数遍历 SVG 所有节点,并转化为 react-native-svg 提供的相应 SVG 元素进行最终的渲染动作

与 WRNSkia 模式区别:

WRNSkia 模式相对 WRNSVG 模式来说整体流程比较简单,定义的 SkiaComponent 组件上有一个核心方法 patchString,patchString 接收变化后的 SVG 数据,合并转化为 SVG 图片格式数据,传递给给 react-native-skia 的 ImageSVG 组件进行整体渲染即可

3. 处理 TouchEvent(手势事件)

Web ECharts 的事件是鼠标事件,例如 click、dblclick、mousedown、mousemove 等,通过鼠标事件来触发图表的元素显示或者动画。RN ECharts 需要把移动端的 TouchEvent 模拟为鼠标事件,并派发到 ECharts init 方法生成的图表实例上。

比如图表上跟随鼠标显示图例的动作,对应到移动端就是 TouchStart + TouchMove,对应转化为鼠标事件是 mousedown + mousemove。还有比如图表的缩放,移动端是双指按下缩放,对应则转化为鼠标的 mousewheel 事件,并且通过双指距离变化来计算出对应的 mousewheel 滚动距离。

关键代码:

  1. TouchEvent 转化为 MouseEvent

// ...
PanResponder.create({
  onPanResponderGrant: ({ nativeEvent }) => {
    // 动作开始,在这里转化为鼠标的点击与移动事件
    dispatchEvent(
      zrenderId,
      ['mousedown', 'mousemove'],
      nativeEvent,
      'start'
    );
  },
  onPanResponderMove: ({ nativeEvent }) => {
    // 处理手指移动操作const length = nativeEvent.touches.length;
    if (length === 1) {
      // 在这里处理单指移动操作...
    } elseif (length === 2) {
      // 在这里处理双指移动操作...if (!zooming) {
        // ...
      } else {
        // 在这里转化为滚轮的事件const { initialX, initialY, prevDistance } = pan.current;
        const delta = distance - prevDistance;
        pan.current.prevDistance = distance;
        dispatchEvent(zrenderId, ['mousewheel'], nativeEvent, undefined, {
          zrX: initialX,
          zrY: initialY,
          zrDelta: delta / 120,
        });
      }
    }
  },
  onPanResponderRelease: ({ nativeEvent }) => {
    // 动作结束,在这里转化为鼠标点击释放操作...
  },
}),
  1. 将 MouseEvent 应用到 ECharts 图表实例上

functiondispatchEvent(zrenderId: number,types: HandlerName[],nativeEvent: NativeTouchEvent,stage: 'start' | 'end' | 'change' | 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.1 WRNSkia 模式下中文乱码问题

我们发现使用 WRNSkia 模式渲染时,图表中的中文显示乱码。为什么会乱码?首先我们猜测字体出了问题,查看字体文件信息,并没有中文字体,查阅了 react-native-skia,原因是不支持字体回退(字体回退:字符串中的某些字符在当前字体中不受支持时,会在字体队列中回退检索支持的字体)。

所以我们找了个中文字体文件 Skia.Typeface.MakeFreeTypeFaceFromData,在运行时进行导入,这个时候可以正常显示了。但是此时,我们又考虑到在不同系统上,使用字体是不一样的,一个中文字体文件很大,我们也不可能每种都导入,但是只提供一种字体的话体验肯定是不友好的,那我们能不能直接使用系统自带的中文字体呢?

于是我们对 iOS Android 支持的中文字体进行了调研,最后我们决定了在有中文的文本设置时,Android 设置 font-family 使用 Noto Sans,iOS 使用 PingFang SC。

4.2 字体重叠和留白现象

中文乱码问题处理好后我们又发现英文有字体重叠问题,我们猜测是字体宽度计算的有问题,因为中文是等宽字体,所以没有问题。

查阅了 zrender 的 measureText 实现,其中在 svgrender 写死了字体为 sans-serif,根据此字体计算了一个宽度,如果是其他字体宽度超出,则会出现重叠,如果设置的字体宽度小,又会出现空白。

所以我们将这部分改为使用 Noto Sans 和 PingFang SC 两种字体来计算宽度,遂解决。

项目中如何使用

在实际应用中,wrn-echarts 的整体流程与 ECharts 相似:

  1. 根据所使用渲染方式,选择安装 react-native-svg 或 @shopify/react-native-skia

  1. 安装 wrn-echarts 并引入相关组件

  1. 使用 wrn-echarts 的 SVGRenderer 替换掉 ECharts 的 SVGRenderer

  1. 编写图表的 option 配置信息

  1. 使用 SkiaChart / SvgChart 组件

示例

import * as echarts from'echarts/core';
import { useEffect, useRef } from'react';
import { SVGRenderer, SvgChart } from'wrn-echarts';

// 注册必须的组件
echarts.use([
  SVGRenderer, // 此处 SVGRenderer 为 wrn-echarts 的 SVGRenderer
]);

exportdefaultfunctionEchartsPage() {
  // svgRef 用来保存图表实例const svgRef = useRef<any>(null);
  // 编写图表配置文件(示例)const option = {
    xAxis: {
      type: 'category',
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    },
    yAxis: {
      type: 'value'
    },
    series: [
      {
        data: [150, 230, 224, 218, 135, 147, 260],
        type: 'line'
      }
    ]
  };

  useEffect(() => {
    let chart;
    if (svgRef.current) {
      // 图表初始化
      chart = echarts.init(svgRef.current, 'light', {
        renderer: 'svg',
        width: 300,
        height: 300
      });
      chart.setOption(option);
    }
    return() => chart?.dispose();
  }, []);

  return<SvgChartref={svgRef}></SvgChart>;
}

运行效果

真机演示

过去一段时间,我们进行了大量的测试,尝试了各种不同类型的图表,也修复了很多存在的问题,目前 ECharts 支持的图表我们绝大部分都已经支持。

下面我会展示一部分真机的效果图,更多的示例可以在 https://github.com/wuba/taro-playground 项目上查看

示例

方案对比

之前在背景中有提到过,我们目标的场景是在 RN 端使用 ECharts,目前主流的方案均为通过 WebView 来实现,而众多基于 WebView 的实现中,react-native-echarts-pro 的使用者较多,所以我们选择了 react-native-echarts-pro 做为对照来进行对比测试。

工具使用 “火山引擎” 进行性能测评,以华为 nova5Pro 作为测试机,测试时分别用 svg、skia、react-native-echarts-pro 绘制同一图表,取图表从初始化到渲染完成时间段内的各项性能数据平均值,每种采样 20 次;性能指标包含整机 CPU 使用率、App CPU 使用率、FPS 均值(去除零值)、JavaHeap、NativeHeap、Code、Graphics、Other、System、卡顿数、卡顿时长占比等。

部分核心指标对比数据如下:

1. 不同类型内存占用对比

单表大数据量的情况:

多图表同时渲染的情况:

对比结果:

JAVA 模块:在单表大数据量渲染时 WRNSVG 模式整体优于其他两种。而在进行多表同时渲染时,WRNSkia 与 WRNSVG 两种渲染模式都明显优于 react-native-echarts-pro 方案。

Native 模块:该项数据在不同场景下结论一致,WRNSVG 模式略优于其他两种方案。

图形渲染模块:在多图表同时渲染时,WRNSkia 与 WRNSVG 两种渲染模式整体都优于 react-native-echarts-pro 方案。

2. 整机 CPU 使用率对比

单表大数据量的情况:

多图表同时渲染的情况:

对比结果:该项对比数据表明,不同情况 WRNSkia 与 WRNSVG 两种模式在整机 CPU 使用率方面略优于 react-native-echarts-pro 方案。

3. FPS 均值对比(值越高越好)

单表大数据量的情况:

多图表同时渲染的情况:

对比结果:该项对比数据表明,在画面刷新频率上,react-native-echarts-pro 方案比较稳定,整体略优于本方案提供的两种模式,其中 WRNSVG 模式帧率相对 WRNSkia 模式更加不稳定。

4. 其他

除以上列举对比数据之外,在图表初始化过程中,react-native-echarts-pro 方案整体画面的渲染速度较慢,看下面的动图可以直观的感受到这一差异:

5. 对比总结

下表为各项对比数据整理,以 react-native-echarts-pro 方案为基准 3 分,按照 1 - 5 分来评价各种方式在不同指标下的表现,分值越高表现越好:

从表格对比可知,WRNSVG 模式整体表现最佳,WRNSkia 模式次之,这两种方案在内存相关的各项指标都不同程度的优于 react-native-echarts-pro 方案。只有在 FPS 指标中相对于 react-native-echarts-pro 方案有一定劣势,在使用中请结合实际情况选择适合自己的方案与渲染模式。

总结与规划

上面就是我们关于 wrn-echarts 从计划、设计到实现的大概的流程。

其实这个过程并不是一帆风顺,比如我们最开始是想在 RN 上实现跟 web 端 canvas 完全一致的 canvas2d 上下文,可是实验下来我们发现这种方式实现起来过于复杂,虽然我们已经能做到简单的图形绘制,但只是其中一部分的 API 的实现就耗费了我们大量的精力,所以我们放弃了这个方向并重新开始思考。

最终我们找到了现在这种方式,绕过了底层接口的实现,转而作为一个中间人的角色,充分利用现有的轮子,提供了现在这种全新的在 RN 上使用 ECharts 的方式。

后续规划

  1. 实现 WRNSkia 的 Canvas 渲染模式:
    react-native-skia 除了提供 ImageSVG 组件,还提供了可在 RN 中使用的 Canvas 基础图形组件,因为现有的 WRNSkia 模式和 WRNSVG 模式在面对大量的数据处理时,内存占用和渲染效率等方面表现不是很好,所以我们期待实现 WRNSkia 的 Canvas 渲染模式后在这些方面的表现会有提升。

  1. 封装 taro-echarts:
    将 wrn-echarts 与其他各端的图表实现方案整合,封装为 taro-echarts 库,让 Taro 在跨端开发中可以实现图表相关需求(Taro:开源跨端跨框架解决方案,支持使用前端脚本语法来开发 Web、小程序、原生等应用,实现一套代码多端运行)。

wrn-echarts 项目现已开源,项目地址 https://github.com/wuba/wrn-echarts ,欢迎 star。更多内容可在官网查看 https://wuba.github.io/wrn-echarts/

另外本篇文章中的示例代码在 https://github.com/wuba/taro-playground 项目上,该仓库也是开源的,感兴趣的同学也可以直接从应用商店安装新版本的 Taro Playground 应用进行体验。

使用过程中,如遇任何问题,可添加 “58 技术小秘书” 或 “Taro 小助手” 为好友,备注 “Taro RN”,加入官方交流群寻求帮助。

作者简介

李志豪:58 同城 资深前端开发工程师

鸣谢

陈志庆:方案设计与核心模块开发

杨杨:提供测试用例以及测试数据

王信健:图表显示问题修复

袁津津:图表 Demo 整理

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值