【D3.js】複数のデータファイルの読み込み(非同期処理)をまとめる
d3で複数のデータファイル(jsonやcsv)を読み込む場合、以下のようにコールバック地獄に陥りやすいです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
d3.json('test.json', function(json){ d3.csv('test1.csv', function(csv1){ d3.csv('test2.csv', function(csv2){ d3.csv('test3.csv', function(csv3){ d3main(json, csv1, csv2, csv3); }); }); }); }); function d3main(){ var data = Array.prototype.slice.call(arguments); } |
まぁ、そんなに一度に大量のファイルを読み込むことはそうないんですが、コールバックが入れ子になるのはあまりうれしくないので、データセットをまとめて読み込む関数を作成してみました。
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
function loadDataSet(option){ var files = option["files"]; var endFn = option["endFn"]; var loadingStartFn = option["loadingStartFn"]; var loadingSuccessFn = option["loadingSuccessFn"]; if (!Array.isArray(files)) throw "TypeError: files is not a array!"; if (loadingStartFn && typeof loadingStartFn != "function") throw "TypeError: loadingStartFn is not a function!"; if (loadingSuccessFn && typeof loadingSuccessFn != "function") throw "TypeError: loadingSuccessFn is not a function!"; if (typeof endFn != "function") throw "TypeError: endFn is not a function!"; var dataStack = {}; //読み込んだデータを保存するスタック var fnStack = []; //データ読み込みに必要なajax関数を保存するスタック //非同期通信処理をチェインを使って順次実行する。各ファンクションにコールバックを仕込む var chain = function(functions) { return functions.reduceRight(function (next, curr) { return function () { curr.apply({next: next}, arguments); } }); } //ファイルの数だけ非同期処理fanctionをスタックに積む files.forEach(function(arg){ if (loadingStartFn) loadingStartFn(arg); fnStack.push( function() { var that = this; var exte = arg.file.split("?")[0].split(".")[arg.file.split(".").length-1]; if (arg.filetype) exte = arg.filetype; var readfile; switch(exte){ case "json": case "geojson": case "topojson": readfile = d3.json; break; case "csv": readfile = d3.csv; break; case "tsv": readfile = d3.tsv; break; default: throw "TypeError: " + exte + " is not supported"; break; } return readfile(arg.file, function(data){ if (arg.callbackData) arg.callbackData = data; if (loadingSuccessFn) loadingSuccessFn(arg); dataStack[arg.key] = data; that.next(); }); } ) }); //スタックの最後にendFnを追加する fnStack.push(function(){ endFn(dataStack); }); //チェイン処理実行 chain(fnStack)(); } |
ファイル名(データセット)を収めた配列とコールバックを渡すと、配列の頭からデータを読み込んで最後にコールバックを実行します。
コールバックの引数には、読み込んだデータセットを全てまとめたデータが渡されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
loadDataSet( files: [ {key:"geo", file:"geo.topojson", callbackData:true}, {key:"hoge1", file:"test1.csv"}, {key:"hoge2", file:"test2.csv"}, {key:"hoge3", file:"test3.csv"}, ], endFn:d3main ); function d3main(data){ console.log(data); } |
他、オプション指定。
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 |
loadDataSet({ files: [ {key:"geo", file:"geo.topojson", callbackData:true}, //loadingSuccessFnのコールバックにデータを返すフラグ {key:"2013年1月", file:"csv/201301.txt", filetype:"tsv"}, //読み込みファイルタイプを指定 {key:"2013年2月", file:"csv/201302.csv"}, {key:"2013年3月", file:"csv/201303.csv"}, ], loadingStartFn: function(d){ //各ファイルの読み込み前に呼び出される関数 console.log(d.key+" data loading..."); }, loadingSuccessFn: function(d){ //各ファイルの読み込みが成功する毎に呼び出される関数 if (d.key === "geo") mapDraw(d.callbackData); //先にマップだけ描画しておく console.log(d.key+" data load success!"); }, endFn:d3main }); function d3main(loadData){ console.log(loadData); } function mapDraw(geoData){ //地図を描画する処理 } |