D3.jsを使用した単純なグラフ

グアルティエーロ・ボフィ|  Dreamstime.com







D3.js (または単にD3)は、信じられないほど巨大な機能でデータを処理および視覚化するためのJavaScriptライブラリです。 最初にそれを知ったとき、おそらくD3で作成されたデータの視覚化の例を見て、少なくとも2時間を費やしました。 そしてもちろん、私自身が企業の小さな内部サイトのグラフを作成する必要があったとき、D3について最初に思い出し、「今では誰もが最もクールな視覚化で驚きます」という考えで、例のソースコードを研究することに着手しました...







...そして、私自身は絶対に何も理解していないことに気付きました! ライブラリの奇妙なロジック、例では、最も単純なスケジュールを作成するためのコード行全体-もちろん、それは主に誇りに満ちた打撃でした。 さて、鼻水を拭きました-D3を簡単に撮ることができず、このライブラリを理解するには、その基礎から始める必要があることに気付きました。 したがって、私は別の方法で行くことを決めました-私のスケジュールの基礎としてライブラリの1つを採用するために-D3のアドオン。 判明したように、このようなライブラリがたくさんあります-それは、私が理解していない唯一の人ではないことを意味します(灰からの私の誇りは言った)。







いくつかのライブラリを試した後、私は自分のニーズに多かれ少なかれ適切なディンプルに落ち着き、 それですべてのスケジュールを再構築しましたが、それでも不満がありました。 いくつかのことは思い通りに機能しませんでした。ディンプルを深く掘らない他の機能は実装できず、延期されました。 とにかく、深く掘り下げる必要がある場合は、追加の設定ではなく、D3で直接行う方が良いでしょう。私の場合、その豊富な機能は5〜10%しか使用されませんが、必要な設定は逆です。 そして、何が起こったのか、それがD3.jsです。







試行番号2



まず、 Habréの D3にあるすべてのものを読み直します。 また、記事の1つにある解説では、 「Web用の対話型データ視覚化 」という本へのリンクを見つけました。 彼は開き、見て、読み始めました-そしてすぐに関与しました! この本はシンプルでわかりやすい英語で書かれており、著者自身も素晴らしく興味深いストーリーテラーであり、基本からD3のトピックをよく明らかにしています。







この本を読んだ結果に基づいて(トピックに関する他のドキュメントと並行して)、シンプルでミニマルな線形グラフを構築するための小さな(より正確には、おそらく顕微鏡の)ライブラリを自分で書きました。 そして次に、このライブラリの例で、D3.jsを使用してグラフを作成することはまったく難しくないことを示したいと思います。







だから(私の好きな言葉)、始めましょう。







まず、どのデータをグラフの形で再構築するかを決めましょう。 一連の条件付きデータを自分から強要しないことを決めましたが、毎日遭遇する実際のデータを取得し、より理解しやすくするためにそれらを単純化および非個人化しました。







ある種の条件付き鉱床での鉄鉱石の抽出と処理のためのある種のプラントを想像してみてください(「キャンドルファクトリー」)、文学の古典からのキャッチフレーズを思い出しますが、キャンドルファクトリーは次の日まで延期されるため、データはすでに準備されています回)。







そこで、鉱石を抽出して鉄を生産します。 フィールド、生産施設などの地質学的特徴を考慮して、技術者が作成した生産計画があります。 などなど。 計画(この場合)は月ごとに分類されており、この計画を達成するために特定の月にどれだけの鉱石を採掘および精錬する必要があるかを確認できます。 事実もあります-計画の実施に関する毎月の実際のデータ。 これらすべてをグラフに表示しましょう。







サーバーは、上記のデータを次のtsvファイルの形式で提供します。







Category Date Metal month Mined % fact 25.10.2010 2234 0.88 fact 25.11.2010 4167 2.55 ... plan 25.09.2010 1510 1 plan 25.10.2010 2790 2 plan 25.11.2010 3820 4 ...
      
      





カテゴリ列の計画値または実際値はどこにありますか、日付は各月のデータです(25日目に日付を記入します)、金属月は計画した(または受け取った)月の金属量、およびMined%列は金属の割合です現在採掘されています。







データですべてが明確になったと思いますが、今はプログラムを開始しています。 ライブラリの呼び出し、CSSスタイルなど、すべてのコードを表示するわけではありません。記事を煩雑にせず、主要なことに集中しないようにするためです。記事の最後にあるリンクで、ここで説明するgithubをダウンロードできます。







まず、d3.tsv関数を使用して、データをロードします。







 d3.tsv("sample.tsv", function(error, data) { if (error) throw error; //      }
      
      





D3へのデータのアップロードは非常に簡単です。 データを別の形式、たとえばcsvで読み込む必要がある場合は、呼び出しをd3.tsvからd3.svに変更するだけです。 データはJSON形式ですか? 呼び出しをd3.jsonに変更します。 3つの形式すべてを試し、私にとって最も便利なtsvに決めました。 好きなものを使用したり、プログラムで直接データを生成することもできます。







上の図では、プログラムでダウンロードしたデータがどのように見えるかを確認できます。







i01







図をよく見ると、データが行の形式でロードされていることがわかります。プログラムの次の段階は、日付を日付データ型に、デジタル値を数値型に変換することです。 これらの削減がないと、D3は日付を正しく処理できず、デジタル値に対して選択的なアプローチを取ります。 いくつかの数字が使用され、他の数字は単に無視されます。 これらのキャストでは、次の関数を呼び出します。







  preParceDann("Date","%d.%m.%Y",["Metal month","Mined %"],data);
      
      





この関数のパラメーターでは、日付が書き込まれる列の名前、D3で日付書き込むため規則に従った日付形式を渡します。 次に、デジタル値の変換を行う列の名前を含む配列が付属します。 最後のパラメーターは、以前にダウンロードしたデータです。 変換関数自体は非常に小さいため、再びそれに戻らないように、すぐにそれをもたらします。







 function preParceDann(dateColumn,dateFormat,usedNumColumns,data){ var parse = d3.time.format(dateFormat).parse; data.forEach(function(d) { d[dateColumn] = parse(d[dateColumn]); for (var i = 0, len = usedNumColumns.length; i < len; i += 1) { d[usedNumColumns[i]] = +d[usedNumColumns[i]]; } }); };
      
      





ここでは、日付解析関数を初期化し、各データ行について日付を変換し、指定された列について行を数値に変換します。







この機能を実行すると、データは次の形式で表示されます。







i02







すぐに考えられる質問に答えます-この機能で、デジタルデータをフォーマットする列のリストが複雑になるのはなぜですか? -そして、この答えは簡単です。実際のテーブルでは、はるかに多くの列が存在する可能性があり(また存在します)、すべてがデジタルであるとは限りません。 そして、グラフのすべての列を構築するわけではないので、データを変換するために特別な操作が必要なのはなぜですか?







次のステップに進む前に、データファイルを呼び出してみましょう。まず、実際のデータ、次に設計データが順番に書き込まれます。 データをそのまま再構築すると、完全に混乱します。 事実と計画の両方が単一の図に描かれているからです。 したがって、好奇心が強い名前のネスト(ソケット)を使用してD3関数を使用して、別のデータ操作を実行します。







  var dataGroup = d3.nest() .key(function(d) { return d.Category; }) .entries(data);
      
      





この関数の結果として、次のデータセットを取得します。







i03







データ配列は既に2つのサブ配列に分割されていることがわかります。1つの事実と別の計画です。







すべてが完了し、データの準備が完了しました。次に、プロット用のパラメーターを設定するタスクに進みます。







  var param = { parentSelector: "#chart1", width: 600, height: 300, title: "Iron mine work", xColumn: "Date", xColumnDate: true, yLeftAxisName: "Tonnes", yRightAxisName: "%", categories: [ {name: "plan", width: "1px"}, {name: "fact", width: "2px"} ], series: [ {yColumn: "Metal month", color: "#ff6600", yAxis: "left"}, {yColumn: "Mined %", color: "#0080ff", yAxis: "right"} ] };
      
      





ここではすべてが簡単です:







パラメータ 価値
parentSelector チャートが再構築されるページの要素のID
幅:600
高さ:300 身長
タイトル:「鉄鉱山の仕事」 見出し
xColumn:「日付」 X軸の座標が取得される列の名前
xColumnDate:true trueの場合、x軸は日付です(残念ながら、この機能はまだ未完成です。つまり、x軸に日付しかプロットできません)
yLeftAxisName:「トン」 左のy軸の名前
yRightAxisName: "%" 右のy軸の名前
カテゴリ: 私は長い間、それをどう命名するかを考えました。 これは「ソケット」D3から飛び出し、カテゴリよりも優れたものはありませんでした。 各カテゴリには、名前が設定されます-データでどのように表記されるか、構造の幅
シリーズ: 実際には、ダイアグラム自体、y軸の値を取得する列、色、およびダイアグラムが左または右に属する軸を指定します。


すべての初期データを設定し、最後にプロットを呼び出して結果を楽しみます:







  d3sChart(param,data,dataGroup);
      
      





i04







このチャートには何が見えますか? しかし、計画は過度に楽観的であり、信頼性の高い予測を行うためには、避けられない修正が必要であることがわかります。 また、制作を詳しく見る必要があります。実際のスケジュールは痛々しいほど引き裂かれています...わかりました、わかりました-プログラマーと呼ばれる人がいない場所にすでに行きました。







繰り返しますが、チャート作成関数の呼び出しを繰り返します。







  d3sChart(param,data,dataGroup);
      
      





それを見ると、合理的な質問が生じますが、おそらくあなたは私に尋ねたいでしょう-なぜ2つのデータ配列、dataとdataGroupが関数に転送されますか? 私は答えます:軸のデータ範囲を正しく設定するには、初期データ配列が必要です。 これはあまりはっきりしていないように思えますが、すぐにこの点を説明しようと思います。







構築関数で最初に行うことは、チャートを構築するオブジェクトがあるかどうかを確認することです。 このオブジェクト自体が存在しない場合、私たちは多くのことを誓います:







 function d3sChart (param,data,dataGroup){ // check availability the object, where is displayed chart var selectedObj = null; if (param.parentSelector === null || param.parentSelector === undefined) { parentSelector = "body"; }; selectedObj = d3.select(param.parentSelector); if (selectedObj.empty()) { throw "The '" + param.parentSelector + "' selector did not match any elements. Please prefix with '#' to select by id or '.' to select by class"; };
      
      





次のアクション:さまざまなインデント、サイズを初期化し、スケールを作成します。







  var margin = {top: 30, right: 40, bottom: 30, left: 50}, width = param.width - margin.left - margin.right, height = param.height - margin.top - margin.bottom; // set the scale for the transfer of real values var xScale = d3.time.scale().range([0, width]); var yScaleLeft = d3.scale.linear().range([height, 0]); var yScaleRight = d3.scale.linear().range([height, 0]);
      
      





インキュベーションプロセスが人為的に加速されているため、ライブラリがhatch化したばかりで、外部からの調整(インデントなど)に慣れていないことを忘れないでください。 したがって、もう一度、私はあなたに理解して許すようお願いします。







冗談として冗談を言っていますが、上記のコードに戻ります-インデントとサイズを使用すると、すべてが明確であると思います。元の座標を構築領域の座標に変換するためのスケールが必要です。 x軸はタイムラインとして初期化され、左右のy軸スケールは線形として初期化されていることがわかります。 一般に、D3にはさまざまなスケールがありますが、それらを考慮することは、他の多くのことと同様、この記事の範囲をはるかに超えています。







継続して、スケールを作成しました。次に、スケールを構成する必要があります。 そして、ここでそのソースデータセットが役立ちます。 非常に簡単な場合-前のアクションで、グラフの座標にスケールの範囲を設定し、次のコマンドでこの範囲をデータ範囲に関連付けます。







  xScale.domain([d3.min(data, function(d) { return d[param.xColumn]; }), d3.max(data, function(d) { return d[param.xColumn]; })]); yScaleLeft.domain([0,d3.max(data, function(d) { return d[param.series[0].yColumn]; })]); yScaleRight.domain([0,d3.max(data, function(d) { return d[param.series[1].yColumn]; })]);
      
      





Xスケールでは、データの最小日付に最小値を設定し、最大値に最大値を設定します。 Y軸の場合、最小値として0を使用し、データから最大値も学習します。 このため、最小値と最大値を見つけるために切れ目のないデータが必要でした。







次のステップは、軸を設定することです。 ここから少し混乱が始まります。 D3にはスケールと軸があります。 スケールは、ソース座標をプロット領域の座標に変換する役割を果たしますが、軸は、チャート上に表示される棒やダッシュをロシア語で表示するように設計されています。 したがって、将来、スケールを書く場合、軸について話していることに注意してください。 チャートにスケールを描くことについて。







したがって、Y軸用に2つのスケールとX軸用に1つのスケールがあることを思い出してください。 問題の事実は、D3がデフォルトで日付スケールを表示する方法に完全に不満だったことです。 しかし、私が必要とする方法で日付の署名を設定しようとするすべての試みは、このライブラリの力と記念碑の岩の上で波のように壊れました。 したがって、私は偽造と欺deに行かなければなりませんでした:私はX軸上に2つのスケールを作成しました。 数か月間、出力から最初の月を除外する小さなチェックが追加されました。 結局、ほんの数文前に、私はこのライブラリを記念碑的なものだと非難しました。ここに、柔軟性の素晴らしい例があります。







  var xAxis = d3.svg.axis().scale(xScale).orient("bottom") .ticks(d3.time.year,1).tickFormat(d3.time.format("%Y")) .tickSize(10); var monthNameFormat = d3.time.format("%m"); var xAxis2 = d3.svg.axis().scale(xScale).orient("bottom") .ticks(d3.time.month,2).tickFormat(function(d) { var a = monthNameFormat(d); if (a == "01") {a = ""}; return a;}) .tickSize(2); var yAxisLeft = d3.svg.axis().scale(yScaleLeft).orient("left"); var yAxisRight = d3.svg.axis().scale(yScaleRight).orient("right");
      
      





コードの検討を続けます。 すべての準備作業を実施し、現在、画像の形成に直接進んでいます。 次の4行のコードは、 svg領域を順番に作成し、等高線フレームを描画し、指定されたオフセットを使用してsvgオブジェクトのグループを作成します。この中にチャートが構築されます。 最後のアクション-タイトルが表示されます。







  var svg = selectedObj.append("svg") .attr({width: param.width, height: param.height}); // outer border svg.append("rect").attr({width: param.width, height: param.height}) .style({"fill": "none", "stroke": "#ccc"}); // create group in svg for generate graph var g = svg.append("g").attr({transform: "translate(" + margin.left + "," + margin.top + ")"}); // add title g.append("text").attr("x", margin.left) .attr("y", 0 - (margin.top / 2)) .attr("text-anchor", "middle").style("font-size", "14px") .text(param.title);
      
      





次の大きなコードは、3つの軸の測定単位に署名します。 すべてが明確であり、詳細に検討する必要はないと思います。







  g.append("g").attr("class", "x axis").attr("transform", "translate(0," + height + ")") .call(xAxis) .append("text") .attr("x", width-20).attr("dx", ".71em") .attr("y", -4).style("text-anchor", "end") .text(param.xColumn); g.append("g").attr("class", "x axis2").attr("transform", "translate(0," + height + ")") .call(xAxis2); g.append("g").attr("class", "y axis") .call(yAxisLeft) .append("text").attr("transform", "rotate(-90)") .attr("y", 6).attr("dy", ".71em").style("text-anchor", "end") .text(param.yLeftAxisName); g.append("g").attr("class", "y axis").attr("transform", "translate(" + width + " ,0)") .call(yAxisRight) .append("text").attr("transform", "rotate(-90)") .attr("y", -14).attr("dy", ".71em").style("text-anchor", "end") .text(param.yRightAxisName);
      
      





最後に、チャート作成機能の中心は、ダイアグラム自体の描画です。







  dataGroup.forEach(function(d, i) { for (var i = 0, len = param.categories.length; i < len; i += 1) { if (param.categories[i].name == d.key){ for (var j = 0, len1 = param.series.length; j < len1; j += 1) { if (param.series[j].yAxis == "left"){ // init line for left axis var line = d3.svg.line() .x(function(d) { return xScale(d[param.xColumn]); }) .y(function(d) { return yScaleLeft(d[param.series[j].yColumn] ); }); }; if (param.series[j].yAxis == "right"){ // init line for right axis var line = d3.svg.line() .x(function(d) { return xScale(d[param.xColumn]); }) .y(function(d) { return yScaleRight(d[param.series[j].yColumn] ); }); }; // draw line g.append("path").datum(d.values) .style({"fill": "none", "stroke": param.series[j].color, "stroke-width": param.categories[i].width}) .attr("d", line); }; }; }; });
      
      





「互いに埋め込まれた3つのサイクル!」-怒りで叫ぶ。 そして、あなたはあなたのinりに絶対に正しいでしょう-私自身はそのような入れ子構造を作るのは好きではありませんが、時々そうしなければなりません。 ループの3番目のネストでは、チャートラインを初期化します。シリーズに応じて、このラインが右目盛と左目盛のどちらに属するかを示します。 その後、2番目のネストで、グラフに線を表示し、カテゴリプロパティから太さを設定します。 つまり 実際、構築に関与するコードは2行のみです。他のすべては、チャート上の異なる数のダイアグラムを処理するために必要なバインディングです。







さて、スケジュールの最後のアクションは、伝説の結論です。 私は伝説を悔い改めました-私はすでに急いでいて失敗しました、このコードはすぐに書き換えられ、D3ではすべてが非常にシンプルであることをもう一度実証するためだけにそれを示します。 そして、まだ-あなたがする必要がない方法の良い例です:







 var legend = svg.append("g").attr("class", "legend").attr("height", 40).attr("width", 200) .attr("transform", "translate(180,20)"); legend.selectAll('rect').data(param.series).enter() .append("rect").attr("y", 0 - (margin.top / 2)).attr("x", function(d, i){ return i * 90;}) .attr("width", 10).attr("height", 10) .style("fill", function(d) {return d.color; }); legend.selectAll('text').data(param.series).enter() .append("text").attr("y", 0 - (margin.top / 2)+10).attr("x", function(d, i){ return i * 90 + 11;}) .text(function(d) { return d.yColumn; }); // add legend for categories var legend1 = svg.append("g").attr("class", "legend").attr("height", 40).attr("width", 200) .attr("transform", "translate(350,20)"); legend1.selectAll('line').data(param.categories).enter() .append("line").attr("y1", 0 - (margin.top / 2)+5).attr("x1", function(d, i){ return i * 60;}) .attr("y2", 0 - (margin.top / 2)+5).attr("x2", function(d, i){ return i * 60+15;}) .style("stroke", "black").style("stroke-width", function(d) { return d.width; }); legend1.selectAll('text').data(param.categories).enter() .append("text").attr("y", 0 - (margin.top / 2)+10).attr("x", function(d, i){ return i * 60 + 17;}) .text(function(d) { return d.name; });
      
      





以上です。 ご清聴ありがとうございました! 私の記事であなたを失望させなかったことを願っています。







コードとソースデータのサンプルは、 Githubからダウンロードできます。







結論として、D3ライブラリを自分で理解しようとしたときに、同様の記事またはチュートリアルを探していたことを付け加えます。 データをロードおよび準備する方法、構築エリアを作成および構成する方法、このデータを表示する方法など、例を使用して個別に順番に表示される記事を探していました。 残念ながら、そのようなものは見られませんでした。著者のD3の例ではすべてが混同されているため、作業のロジックを理解せず、このライブラリの最初の知識もありません。このデータの表示、およびその逆。







2016年6月23日更新 Githubでプログラムを更新しました。タイムラインを完成させ、X軸の日付の代わりに数値シリーズを開始できるようにし、いくつかのエラーを修正し、さらにカテゴリなしでグラフを作成できるようになりました。







2016年8月12日更新 d3の4番目のバージョンで動作するようにプログラムを再編集しました。 多くの非互換性が出ました。 この記事で説明する例は、ライブラリの3番目のバージョンでのみ機能し、ファイル名に_v3プレフィックスが付いたファイルのgithubにあります。








All Articles