【D3.js】proxyを使ってデータセットが変更されたらチャートを再描画する。
D3.jsを使ってデータビジュアライゼーションを作成するときにありがちな処理として、入力フォームの値が変更されたらチャートを再描画するというのがあります。入力フォームのchangeイベントが発火したらフォームの値を取得してredrawするわけですが、入力項目の数が増えてきたりすると管理が面倒になってきます。
できることなら、入力フォームのイベントとチャートの描画は疎結合にして、データセットが変更されたらチャートを描画するような処理にしたいところです。
ECMAScript 2015にて実装されたproxy機能を使うとオブジェクトの変更を簡単に監視することができるので、データセットの変更を監視して再描画処理を行うことができます。
※ Proxy Objectは2016/9/6現在、一部のブラウザでしか使用することができません。
Can I use… Support tables for HTML5, CSS3, etc
簡単な使い方
proxyオブジェクトの機能については、MDNを参照していただくとして、最も単純なgeter,seterの監視処理を試してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//データセット var data = {} //proxyに設定するハンドラ var proxyHandler = { get: function(target, prop, value){ console.log("get!", target) return target[prop] }, set: function(target, prop, value){ console.log("set!", target) target[prop] = value } } //データセットを束縛したプロキシオブジェクトを生成 var proxy = new Proxy(data, proxyHandler) |
上記のコードは、dataオブジェクトにgeter,seterのトラップを定義したハンドラを束縛し、proxyオブジェクトを生成しています。
実際にブラウザのJavaScriptコンソール上でproxyオブジェクトを操作した結果が以下です。
proxyオブジェクトに新たなプロパティを設定するとdataオブジェクトにその内容が反映されているのが見て取れます。
同時にハンドラ内に記述されたconsole.logが実行されています。
proxyオブジェクトを使用することでオブジェクトに簡単にobserveを仕込むことができました。
D3サンプル
proxyハンドラ内にチャートを再描画する処理を挟み込み、データセットに変更があったらチャートを再描画するサンプルです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
!(function(){ "use strict"; const dataSet = [ {name:"tokyo", value:"100"}, {name:"gunma", value:"200"}, {name:"saitama", value:"300"} ] //バーチャートモジュールを生成 const BarChart = createVBarChart() .margin({top:40, left:100, bottom:40, right:10}) .x(function(d){ return d["name"] }) .y(function(d){ return d["value"] }) //チャートを描画する const selector = d3.selectAll("#chart").datum(dataSet) .call(BarChart) //データセットの内容が変更されたらチャートをアップデートする const proxyHandler = { get: function(target, name, value){ return target[name] }, set: function(target, name, value){ if (name == "value" && isNaN(+value)) throw new TypeError("型がちがうよ") target[name] = value selector.update(dataSet) //チャートアップデート } } //データセットをネストし、proxyで包む const nested = d3.nest() .rollup(function(d){ return new Proxy(d[0], proxyHandler) }) .key(function(d){ return d.name }) .map(dataSet) //テキストボックスの値が変更されたらデータセットをporxy経由で変更する const updateDataset = function() { nested["$"+this.name].value = this.value } const textBox = d3.selectAll(".input") .on("change", updateDataset) //テキストボックスに初期値を渡す。 textBox.each(function(){ this.value = nested["$"+this.name].value }) }()); |