组件化可视化图表 - Recharts

Recharts 是 2016 年初团队可视化组推出的一款可视化组件库,为基础表格的绘制提供了另外一种可能。

Recharts 含义是重新定义(redefined)图表。这个名字的背后在于这个图表在设计上带给开发者的是不一样的体验,不仅是用 React 设计,也在于重新定义了组合与配置方式。

Recharts 到今天的版本是 0.9.3,支持 React 0.14.x 或 15.0.x 版本,现在有至少四个国外团队在产品中使用。为方便国际化,文档只有英文官网 Recharts,中文官网还在编写中。如果试用有问题,欢迎给项目提 issue(P.S. 请使用英文来提,谢谢)。

接下来我们会从思想层面来剖析 Recharts 的原理和精髓。

大家可以回顾一下在做图表类的需求时,碰到最纠结的问题是什么?这里列了一些我碰到最多的问题:

  • 配置非常复杂,可配置的内容太多,找不到到底使用什么配置项来达到想要的目的

  • 很多样式无法完全统一,变化很多。这个线图怎么多了条线?这个柱图的“柱子”怎么是个三角形?

那 Recharts 是怎么解决这些问题呢?

  • 声明式的标签,让写图表和写 HTML 一样简单

  • 贴近原生 SVG 的配置项,让配置项更加自然

  • 接口式的 API,解决各种个性化的需求

下面我们将仔细分析这些是怎么实现的。

声明式的标签

在看代码实现之前,我们先看看怎样一步步的根据各自的需求创建一个线图:

首先,通过调用 LineChart 添加一条 dataKeypvLine

const data = [{ name: 'a',uv: 4000,pv: 2400 },{ name: 'b',uv: 3000,pv: 1398 },....];

<LineChart width={600} height={300} data={data}>
  <Line dataKey="pv" stroke="black" />
</LineChart>

运行代码后结果如下:

然后,我们可以根据自己的需求去丰富这个线图,比如这个线图需要一个 X 轴和 Y 轴,那只需要在 LineChart添加一个 XAxisYAxis 标签即可:

const data = [{ name: 'a',....];

<LineChart width={600} height={300} data={data}>
  <XAxis />
  <YAxis />
  <Line dataKey="pv" stroke="black" />
</LineChart>

运行代码结果如下:

大家看到用 Recharts 绘制图表很多时候就想拼积木一样,那 LineChart 内部是如何去识别这些『零件』的呢?我们先来看一个简单的函数

const getdisplayName = (Comp) => {
  if (!Comp) { return ''; }
  if (typeof Comp === 'string') { return Comp; }
  return Comp.displayName || Comp.name || 'Component';
};

这个方法很简单,可以用来读取某个 ReactComponent 的名称。在 LineChart代码实现中,就是根据 ReactComponent 的 displayName 来识别所有的 Children。我们先来看一个工具方法

const findAllByType = (children,type) => {
  const result = [];
  let types = [];

  if (_.isArray(type)) {
    types = type.map(t => getdisplayName(t));
  } else {
    types = [getdisplayName(type)];
  }

  React.Children.forEach(children,child => {
    const childType = child && child.type && (child.type.displayName || child.type.name);
    if (types.indexOf(childType) !== -1) {
      result.push(child);
    }
  });

  return result;
};

这里 type 可以是 ReactComponent 或者 ReactComponent 数组。而 LineChart 内部实现的时候就是调用这个方法来识别各个『零件』:

...
    render() {
        const { children } = this.props;
        const lineItems = findAllByType(children,Line);
        ...
    }

贴近原生的配置项

图表的配置项可以非常多,但是有很多配置项如填充颜色、描边颜色、描边宽度等等这些都是SVG标签原生就支持属性,为了减小大家的配置的成本,Recharts 的组件会去解析原生的属性。举个例子,一个线图里面有两条曲线,我想给一条曲线设置成虚线,一条设置成实线,我们只需要像原生的 SVG 元素一样设置 stroke-dasharray 属性就行:

const data = [{ name: 'a',....];

<LineChart width={600} height={300} data={data}>
  <XAxis />
  <YAxis />
  <Line dataKey="pv" stroke="black" strokeDasharray="5 5" />
  <Line dataKey="uv" stroke="black" />
</LineChart>

结果如下:

实现原理也比较简单,首先 Recharts 内部维护一份 SVG 元素支持的所有属性,然后在渲染 SVG 元素之前,我们会去解析相应的ReactElement的 props,看看哪些是 SVG 元素能够支持属性,最终这些属性可以传入到渲染的 SVG 元素中。

const PRESENTATION_ATTRIBUTES = {
    fill: PropTypes.string,strokeDasharray: PropTypes.string,...
};
const getPresentationAttributes = (el) => {
  if (!el || _.isFunction(el)) { return null; }

  const props = React.isValidElement(el) ? el.props : el;
  let result = null;

  for (const key in props) {
    if (props.hasOwnProperty(key) && PRESENTATION_ATTRIBUTES[key]) {
      if (!result) {result = {};}
      result[key] = props[key];
    }
  }

  return result;
};

关于更多SVG属性,大家可以参考W3C标准文档

接口式的 API

很多时基础图表往往不能满足所有的要求,那怎么去满足各种个性化的需求成了图表组件必须要考虑的事情。

Recharts 对可能会变化的元素都提供了自定义的接口,以x轴的刻度为例,普通的刻度就是一些文字,在信息图表中,为了让图表更佳的生动,视觉往往希望能够将文字替换成形象的 icon。

对于这种自定义的需求,Recharts 提供了两种方式,第一种是通过 React Element 的方式:

const CustomizedTick = (props) => {
  const { x,y,payload,bgColor,index } = props;

  return (
      <g>
          <circle cx={x} cy={y + 15} r={10} fill={bgColor}/>
      <text x={x} y={y + 22} textAnchor="middle" fill="#fff">{index}</text>
    </g>
  );
};

<LineChart data={data}>
  <XAxis tick={<CustomizedTick />}/>
  <YAxis/>
  <Line dataKey="pv" stroke="black" strokeDasharray="5 5"/>
  <Line dataKey="uv" stroke="black"/>
</LineChart>

通过将 tick 设置成一个 React Element,在拿到内部 props 的同时,也可以非常方便的从外部传入 props

第二种自定义的方式是通过 function:

const renderCustomizedTick = (props) => {
  const { x,index } = props;

  return (
      <g>
          <circle cx={x} cy={y + 15} r={10} fill="#666"/>
      <text x={x} y={y + 22} textAnchor="middle" fill="#fff">{index}</text>
    </g>
  );
};

<LineChart data={data}>
  <XAxis tick={renderCustomizedTick} />
  <YAxis/>
  <Line dataKey="pv" stroke="black" strokeDasharray="5 5"/>
  <Line dataKey="uv" stroke="black"/>
</LineChart>

这种方法renderCustomizedTick 中拿到的参数和 CustomizedTickprops 是一样的,当然这种自定义方法外部传参数会稍微麻烦一些。

看到这里大家可能会好奇内部是怎么去实现?原理也非常简单,我们在内部计算好 tick 的位置等信息,然后判读 tick 参数的类型,实现代码简化如下:

let tickItem;

if (React.isValidElement(tick)) {
  tickItem = React.cloneElement(tick,props);
} else if (_.isFunction(tick)) {
  tickItem = tick(props);
} else {
  tickItem = <text {...props} className="recharts-cartesian-axis-tick-value">{value}</text>;
}

看到这里大家可以发现 Recharts 内部主要做了计算各种 layout 的事,每个区块具体展示什么内容都是可以自定义的。

延伸

到这里我们已经介绍了 Recharts 实现可视化组件的一些核心思想,其实这些思想不只是在可视化组件中可以应用,很多组件也可以考虑利用这种思想来实现,例如表格组件就可以抽取 TableColumn 两个组件,然后大家使用表格也非常简单:

<Table data={data}>
  <Column name="名称" dataKey="name"/>
  <Column name="数量" dataKey="count" align="right" th={<SortableTh order="asc" onChange={handleSort}/>}/>
  <Column name="金额" dataKey="amt" td="float" align="right"/>
</Table>

关于 1.0 版本的发布

我们大约会在本月末,或下月初发布,

  1. 更好的动画支持

  2. 同步文档更新

  3. 增加一些图表的支持

  4. 90% 的测试覆盖率

关于无线支持可能会放到 1.0 之后再考虑,因为 SVG 对手机的兼容性支持度一般。1.0 版本之后,会切分出 React 15.x 的 Recharts。因为 15.x 对 SVG 的支持更加完善。

尽管 web 端已经有不少优秀的可视化库,亦或是图表库,比如 Echarts,highcharts,科学界有 ggplot,他们都是可视化界的前辈。在可视化的探索上,给我们很多启发。我们造 Recharts 的初忠是给 React 社区贡献一个代码更优雅,灵活可装卸的图表库的图表库。

感谢团队可视化组的小伙伴。最后是安利时间,第一款使用 Recharts 的线上项目 阿里指数

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom