かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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