使用 React.js 和 Chart.js 来绘制交互式图表

简述/综述

假设有一种页面,图表和表格放在一起。当用户在同一页上新添加数据后,表格会插入新数据,同时图表也会更新。为了实现这样的交互式页面,我尝试在React.js应用程序内部使用Chart.js。它是基于React.js状态数据(this.state)绘制图表的方法。

我没有使用JSX,而是用Rails和CoffeeScript编写了代码。

Screenshot 2015-07-14 16.25.07.png

#环境

Option 1: 环境

    • ruby 2.2.1

 

    • Rails 4.2.3

 

    • react-rails

 

    • chart-js-rails

 

    • bootstrap

 

    • font-awesome-rails

 

#步骤

在render方法中,我们定义了一个canvas的图形,然后调用之前定义的canvas对象。

# React.DOMの記述を省略するための変数。
R = React.DOM

PieChartCanvas = React.createClass
  render: ->
    R.canvas
      style: { height: 200, width: 200 }

BarChartCanvas = React.createClass
  render: ->
    R.canvas
      style: { height: 200, width: 400 }

記住圖表物件的指標。
由於Chart.js所建立的在畫布上的圖表物件是React.js無法管理的,當整個組件更新時,我們需要自己來銷毀它們。因此,我們需要記住圖表的物件,以便以後可以訪問它們。

@MovingRecordsApp = React.createClass

  getInitialState: ->
    records: @props.data
    # グラフのオブジェクトを覚えておくための変数。
    barChartInstance: null
    pieChartInstance: null

使用`render()`方法来定义模板。


# React.DOMの記述を省略するための変数。
R = React.DOM

# メインの部品
@MovingRecordsApp = React.createClass

  getInitialState: ->
    records: @props.data  # 外部から供給されるデータ。
    # グラフのオブジェクトを覚えておくための変数。
    barChartInstance: null
    pieChartInstance: null

  getDefaultProps: ->
    records: []

  ...

  # グラフと額縁のテンプレート
  chartsPanel: ->
    R.div
      className: "panel panel-blue"
      R.div
        className: 'panel-heading'
        R.div
          className: "row"
          R.div
            className: "col-xs-3"
            R.div
              className: "fa fa-home fa-5x"
          R.div
            className: "col-xs-9 text-right"
            R.div
              className: 'huge'
              "Total: #{@totalVolume()}"
            R.div null,
              "cubic feet"
      R.div
        className: 'panel-body'
        R.div
          className: 'row text-center'
          # 棒グラフ用キャンバス
          R.div
            className: 'col-sm-6'
            React.createElement BarChartCanvas,
              ref: "bar"  # 後にアクセスするために使用
          # 円グラフ用キャンバス
          R.div
            className: 'col-sm-6'
            React.createElement PieChartCanvas,
              ref: "chart"  # 後にアクセスするために使用

  render: ->
    R.div
      className: "app_wrapper"

      # グラフと額縁
      R.div null, @chartsPanel()
      R.hr null

      # 新規作成フォームの部品(この部品についての本題ではないので、詳細は省略しています。)
      R.h2 null, "Add a new item"
      React.createElement NewMovingRecordForm,
        handleNewRecord: @addRecord
        roomSuggestions: @props.roomSuggestions
        categorySuggestions: @props.categorySuggestions
      R.hr null

      # 表の部品(この部品についての本題ではないので、詳細は省略しています。)
      React.createElement Records,
        records: @state.records,
        handleDeleteRecord: @deleteRecord,
        handleUpdateRecord: @updateRecord

      ...

使用React.js的生命周期方法,绘制图表。

在React.js中,可以使用生命周期方法,在组件的生命周期特定时刻执行处理。由于Chart.js无法访问React.js内部的虚拟DOM,因此图表绘制会在组件实际添加到DOM后进行。相反,React.js不了解Chart.js的处理内容,因此需要在每次DOM更新时手动销毁图表对象(如果不销毁,更新后仍会保留旧图表)。

  ...

  # 部品がDOMに搭載された後に、グラフを書く。
  componentDidMount: ->
    @drawCharts()

  # 部品が更新された後に、古いグラフを破壊し新しいグラフを書く。
  componentWillUnmount: ->
    @state.barChartInstance.destroy()
    @state.pieChartInstance.destroy()

  # DOM上のキャンバスを探し、そこにグラフを描画。
  drawCharts: ->
    # 棒グラフ
    canvas = React.findDOMNode(@refs.bar)  # refを手掛かりにキャンバスを探します。
    ctx    = canvas.getContext("2d")       # 絵を書くための場所をゲットします。
    # グラフ用データを渡し、グラフのオブジェクトを作ります。
    # そのオブジェクトを後ほど破壊するためにポインターを保存しておきます。
    @setState.barChartInstance = new Chart(ctx).Bar(@dataForBarChart())

    # 円グラフ
    canvas = React.findDOMNode(@refs.chart)
    ctx    = canvas.getContext("2d")
    @setState.pieChartInstance = new Chart(ctx).Pie(@dataForPieChart())

  # 棒グラフ用データ(これはドキュメンテーションから引用した例)
  # ここで実際は@state.recordsのデータを加工してデータを準備します。
  dataForBarChart: ->
    labels: ["January", "February", "March", "April", "May", "June", "July"]
    datasets: [
      {
        label: "My First dataset"
        fillColor: "rgba(220,220,220,0.5)"
        strokeColor: "rgba(220,220,220,0.8)"
        highlightFill: "rgba(220,220,220,0.75)"
        highlightStroke: "rgba(220,220,220,1)"
        data: [65, 59, 80, 81, 56, 55, 40]
      }
      {
        label: "My Second dataset"
        fillColor: "rgba(151,187,205,0.5)"
        strokeColor: "rgba(151,187,205,0.8)"
        highlightFill: "rgba(151,187,205,0.75)"
        highlightStroke: "rgba(151,187,205,1)"
        data: [28, 48, 40, 19, 86, 27, 90]
      }
    ]

  # 円グラフ用データ(これはドキュメンテーションから引用した例)
  # ここで実際は@state.recordsのデータを加工してデータを準備します。
  dataForPieChart: ->
    [
      {
        value: 300
        color:"#F7464A"
        highlight: "#FF5A5E"
        label: "Red"
      }
      {
        value: 50
        color: "#46BFBD"
        highlight: "#5AD3D1"
        label: "Green"
      }
      {
        value: 100
        color: "#FDB45C"
        highlight: "#FFC870"
        label: "Yellow"
      }
    ]

为了使结构更容易理解,在上述例子中我们用一个静态数据进行替换。
为了将组件的更新反映到图表中,需要动态地处理@state.records来生成图表数据。而且,不同类型的图表具有不同的数据结构。请详细参阅文档。

举个例子,我用这种方式创建了一个图表的数据。

  dataForPieChart: ->
    source = @volumeSortedBy("room")
    ary = []
    colors = ["#FE2E2E", "#FE9A2E", "#FE9A2E", "#9AFE2E", "#2EFE2E", "#2EFE9A",
              "#2EFEF7", "#2E9AFE", "#2E2EFE", "#9A2EFE", "#FE2EF7", "#FE2E9A"]
    @shuffleArray(colors)
    for item, i in source
      obj =
        value:     item.volume
        color:     colors[i]
        highlight: colors[i]
        label:     item.room
      ary.push(obj)
    ary

  dataForBarChart: ->
    source = @volumeSortedBy("category")
    labels = source.map (obj) -> obj.category
    data   = source.map (obj) -> obj.volume
    datasets = [
        {
          fillColor:       "rgba(151,187,205,0.5)"
          strokeColor:     "rgba(151,187,205,0.8)"
          highlightFill:   "rgba(151,187,205,0.75)"
          highlightStroke: "rgba(151,187,205,1)"
          data:            data
        }
      ]
    { labels: labels, datasets: datasets }

整体形象的一个例子


R = React.DOM

# Canvases for charts
PieChartCanvas = React.createClass
  render: ->
    R.canvas
      style: { height: 200, width: 200 }

BarChartCanvas = React.createClass
  render: ->
    R.canvas
      style: { height: 200, width: 400 }

@MovingRecordsApp = React.createClass

  getInitialState: ->
    records: @props.data
    # Remember Chart.js instances so we can delete them later.
    barChartInstance: null
    pieChartInstance: null

  getDefaultProps: ->
    records: []

  addRecord: (record) ->
    records = React.addons.update(@state.records, { $unshift: [record] })
    @setState records: records

  deleteRecord: (record) ->
    index = @state.records.indexOf record
    records = React.addons.update(@state.records, { $splice: [[index, 1]] })
    @replaceState records: records

  updateRecord: (record, newRecord) ->
    index = @state.records.indexOf record
    records = React.addons.update(@state.records, { $splice: [[index, 1, newRecord]] })
    @replaceState records: records

  chartsPanel: ->
    R.div
      className: "panel panel-blue"
      R.div
        className: 'panel-heading'
        R.div
          className: "row"
          R.div
            className: "col-xs-3"
            R.div
              className: "fa fa-home fa-5x"
          R.div
            className: "col-xs-9 text-right"
            R.div
              className: 'huge'
              "Total: #{@totalVolume()}"
            R.div null,
              "cubic feet"
      R.div
        className: 'panel-body'
        R.div
          className: 'row text-center'
          R.div
            className: 'col-sm-6'
            React.createElement BarChartCanvas,
              ref: "bar"
          R.div
            className: 'col-sm-6'
            React.createElement PieChartCanvas,
              ref: "chart"

  totalVolume: ->
    sum = 0
    for obj in @state.records
      sum += (obj.volume * obj.quantity)
    sum

  render: ->
    R.div
      className: "app_wrapper"
      @chartsPanel()

      R.hr null

      R.h2 null, "Add a new item"
      React.createElement NewMovingRecordForm,
        handleNewRecord: @addRecord
        roomSuggestions: @props.roomSuggestions
        categorySuggestions: @props.categorySuggestions
      R.hr null

      React.createElement Records,
        records: @state.records,
        handleDeleteRecord: @deleteRecord,
        handleUpdateRecord: @updateRecord

  # 部品がDOMに搭載された後に、グラフを書く。
  componentDidMount: ->
    @drawCharts()

  # 部品が更新された後に、古いグラフを破壊し新しいグラフを書く。
  componentWillUnmount: ->
    @state.barChartInstance.destroy()
    @state.pieChartInstance.destroy()

  # DOM上のキャンバスを探し、そこにグラフを描画。
  drawCharts: ->
    # 棒グラフ
    canvas = React.findDOMNode(@refs.bar)  # refを手掛かりにキャンバスを探します。
    ctx    = canvas.getContext("2d")       # 絵を書くための場所をゲットします。
    # グラフ用データを渡し、グラフのオブジェクトを作ります。
    # そのオブジェクトを後ほど破壊するためにポインターを保存しておきます。
    @setState.barChartInstance = new Chart(ctx).Bar(@dataForBarChart())

    # 円グラフ
    canvas = React.findDOMNode(@refs.chart)
    ctx    = canvas.getContext("2d")
    @setState.pieChartInstance = new Chart(ctx).Pie(@dataForPieChart())

  dataForPieChart: ->
    [
      {
        value: 300
        color:"#F7464A"
        highlight: "#FF5A5E"
        label: "Red"
      }
      {
        value: 50
        color: "#46BFBD"
        highlight: "#5AD3D1"
        label: "Green"
      }
      {
        value: 100
        color: "#FDB45C"
        highlight: "#FFC870"
        label: "Yellow"
      }
    ]

  dataForBarChart: ->
    labels: ["January", "February", "March", "April", "May", "June", "July"]
    datasets: [
      {
        label: "My First dataset"
        fillColor: "rgba(220,220,220,0.5)"
        strokeColor: "rgba(220,220,220,0.8)"
        highlightFill: "rgba(220,220,220,0.75)"
        highlightStroke: "rgba(220,220,220,1)"
        data: [65, 59, 80, 81, 56, 55, 40]
      }
      {
        label: "My Second dataset"
        fillColor: "rgba(151,187,205,0.5)"
        strokeColor: "rgba(151,187,205,0.8)"
        highlightFill: "rgba(151,187,205,0.75)"
        highlightStroke: "rgba(151,187,205,1)"
        data: [28, 48, 40, 19, 86, 27, 90]
      }
    ]

通过考虑在生活方式的哪个环节来重新绘制图表,我一开始遇到了困扰。我在使用console.log进行确认的同时尝试了各种模式。如果在更新图表后旧图表没有被清除,旧图表会重叠并渲染出来。经过一番摸索,我发现只需要在componentDidMount中设置图表,在componentWillUnmount中销毁图表就能成功解决这个问题。

我发现通过在React.js组件内部绘制Chart.js图表,可以立即将用户输入的结果内容反映在图表中。而且这个过程相对简单。目前,由于文件过于拥挤,我们计划将图表组件作为一个独立的部件进行重构。

根据在这里学到的结果,我们对图形组件进行了封装。

# Github
https://github.com/mnishiguchi/InteractiveChartComponent

# Github
请访问以下链接查看我的Github仓库,里面有我开发的交互式图表组件。
https://github.com/mnishiguchi/InteractiveChartComponent

#参考资料:
请提供以下内容的中文本地化释义,仅需一个选项:

    • http://www.chartjs.org/docs/

 

    https://facebook.github.io/react/docs/component-specs.html

以上就是。

广告
将在 10 秒后关闭
bannerAds