かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

TypeScriptとd3.jsでグラフ描こうぞ

昨日の続きです。

d3.jsで線を引きたい

基本的にSVGでやるのでpathを使うことになります。でもPathの書式が変態!!(XAMLとかもそうですけど、あんまり手書きするものではないですよね…)

ということでd3.jsにはpathを簡単に作れるd3.svg.lineというのがあるみたいです。d3.svg.lineを呼び出して返ってきた結果に対してx座標とy座標の変換ルールを指定してやればOK。あとは、データ(大体配列なのかなあ?)を渡して呼び出してやるとpathの書式になおしてくれる。

var svg = d3.select("body").append("svg");

var datas = [[1,0], [2,3], [3,2]];
var l = d3.svg.line()
    .x(data => data[0])
    .y(data => data[1])
svg.append("path").attr("d", l(datas)).attr("fill", "none");

上の例では、二次元配列で点を表してる。んで、二次元配列の要素の中の0番目がx座標、1番目がy座標として使うようにしてます。ちなみに、pathは、そのままだと線じゃなくて閉じた領域を塗りつぶすみたいなのでfillにnoneを指定して透明にしておかないと線にならなかったです。

ということで線を引いてみた

データを表示するためのHTMLをさくっと用意します。

<!DOCTYPE html>

<html lang="ja">
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
    <link rel="stylesheet" href="app.css" type="text/css" />
    <script src="Scripts/d3.v3.min.js"></script>
    <script src="app.js"></script>
</head>
<body>
    <h1>Graph</h1>
    <div id="content"></div>
</body>
</html>

そして、app.tsの中で適当にデータを作ってpathにしてます。ついでなのでデータの箇所に丸をうったり、データを表示したりしてます。

/// <reference path="Scripts/typings/d3/d3.d.ts" />

window.onload = () => {
    // 50セットのデータを生成
    var datas: Array<{ x: number; y: number }> = [];
    var seed = d3.random.normal(200, 200);
    for (var i = 0; i < 50; i++) {
        datas.push({ x: i * 10, y: seed() });
    }

    // xの最小値とxの最大値を0~500の間にスケーリングするrxを作成
    var rx = d3.scale
        .linear()
        .domain([d3.min(datas, a => a.x), d3.max(datas, a => a.x)])
        .range([0, 500]);
    // yの最小値とyの最大値を0~500の間にスケーリングするryを作成
    var ry = d3.scale
        .linear()
        .domain([d3.min(datas, a => a.y), d3.max(datas, a => a.y)])
        .range([0, 500]);

    // id="content"の中に500x500のsvg要素を作成
    var svg = d3.select("#content")
        .append("svg")
        .attr("width", 500)
        .attr("height", 500);

    // pathを組み立ててくれるlineを作成
    var line = d3.svg.line()
        // x座標はrxでスケーリングした結果
        .x(d => rx(d.x))
        // y座標はryでスケーリングした結果
        .y(d => ry(d.y));

    // pathを作成して
    svg.append("path")
        // データはlineで組み立てて
        .attr("d", line(datas))
        // 塗りつぶしは無しで
        .attr("fill", "none")
        // かっこいい青色で線の太さは1
        .attr("stroke", "steelblue")
        .attr("stroke-width", 1);

    // データの場所に点をうっていく
    svg.selectAll("circle")
        .data(datas)
        .enter()
        // x座標とy座標はスケーリングした結果で半径は2でかっこいい青色
        .append("circle")
        .attr("cx", d => rx(d.x))
        .attr("cy", d => ry(d.y))
        .attr("r", 2)
        .attr("fill", "steelblue");

    // データの場所にyの値を表示する
    svg.selectAll("text")
        .data(datas)
        .enter()
        .append("text")
        .attr("x", d => rx(d.x))
        .attr("y", d => ry(d.y))
        .attr("font-size", 10)
        .text(d => "[" + d3.round(d.y, 0) + "]");
};

昨日つかったスケーリングしてくれる機能を使ってデータがsvgタグの中に納まるようにしてるのもポイントかも?

実行すると以下のようにそれっぽい表示結果が得られました。データはランダムに作ってるので実行するたびにグラフの形は変わります。素敵。

f:id:okazuki:20140209161302p:plain