組件化可視化圖表 - Recharts

Recharts 是 2016 年初團隊可視化組推出的一款可視化組件庫,爲基礎表格的繪製提供了另一種可能。html

Recharts 含義是從新定義(Redefined)圖表。這個名字的背後在於這個圖表在設計上帶給開發者的是不同的體驗,不只是用 React 設計,也在於從新定義了組合與配置方式。git

Recharts 到今天的版本是 0.9.3,支持 React 0.14.x 或 15.0.x 版本,如今有至少四個國外團隊在產品中使用。爲方便國際化,文檔只有英文官網 Recharts,中文官網還在編寫中。若是試用有問題,歡迎給項目提 issue(P.S. 請使用英文來提,謝謝)。github

接下來咱們會從思想層面來剖析 Recharts 的原理和精髓。web

你們能夠回顧一下在作圖表類的需求時,碰到最糾結的問題是什麼?這裏列了一些我碰到最多的問題:數組

  • 配置很是複雜,可配置的內容太多,找不到到底使用什麼配置項來達到想要的目的echarts

  • 不少樣式沒法徹底統一,變化不少。這個線圖怎麼多了條線?這個柱圖的「柱子」怎麼是個三角形?函數

那 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', uv: 4000, pv: 2400 }, { name: 'b', uv: 3000, pv: 1398 }, ....];

<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', uv: 4000, pv: 2400 }, { name: 'b', uv: 3000, pv: 1398 }, ....];

<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, y, payload, 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 的線上項目 阿里指數

相關文章
相關標籤/搜索