使用 React.js 和 Chart.js 来绘制交互式图表
简述/综述
假设有一种页面,图表和表格放在一起。当用户在同一页上新添加数据后,表格会插入新数据,同时图表也会更新。为了实现这样的交互式页面,我尝试在React.js应用程序内部使用Chart.js。它是基于React.js状态数据(this.state)绘制图表的方法。
我没有使用JSX,而是用Rails和CoffeeScript编写了代码。
![Screenshot 2015-07-14 16.25.07.png](https://cdn.silicloud.com/blog-img/blog/img/657d32ab37434c4406c62e12/3-0.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
以上就是。