ということで、これまでの練習の成果という感じで折れ線グラフを1つ描いてみた。
- TypeScriptとd3.js - かずきのBlog@hatena
- TypeScriptとd3.jsでグラフ描こうぞ - かずきのBlog@hatena
- TypeScriptとd3.jsでグラフを描こうぞ 2 - かずきのBlog@hatena
点と線
ということで、これまで、べたっと描いてたのを点をあらわすクラスと線を表すクラスにわけてみた。
// 点 class Point<TX, TY> { constructor(public x: TX = null, public y: TY = null) { } } // 線 class Line<TX, TY> { constructor(public points: Array<Point<TX, TY>> = []) { } }
そして、Lineの配列を管理して描画するTimelineChartというクラスを作成。drawメソッドに今までやってきたことを使ってSVGでグラフを描画してる。
// 横軸が時間の折れ線グラフ class TimelineChart { constructor(public lines: Array<Line<Date, number>> = []) { } draw(position: { x: number; y: number; width: number; height: number; padding: number }, svg: D3.Selection): void { // 全データから最小値最大値を取得する var minMax = { minX: d3.min(this.lines, l => d3.min(l.points, p => p.x)), minY: Math.min(0, d3.min(this.lines, l => d3.min(l.points, p => p.y))), maxX: d3.max(this.lines, l => d3.max(l.points, p => p.x)), maxY: d3.max(this.lines, l => d3.max(l.points, p => p.y)) }; // スケール。x軸は時間 var xScale = d3.time.scale() .domain([minMax.minX, minMax.maxX]) .range([position.padding, position.width - position.padding]); var yScale = d3.scale.linear() .domain([minMax.minY, minMax.maxY]) .range([position.height - position.padding, position.padding]); // 軸 // x軸は月/日でラベル表示する var xAxis = d3.svg.axis().scale(xScale).tickFormat(d3.time.format("%m/%d")); var yAxis = d3.svg.axis().scale(yScale).orient("left"); // 描画のホストになる要素を作成 var host = svg.append("g") .attr("x", position.x) .attr("y", position.y) .attr("width", position.width) .attr("height", position.height); var line = d3.svg.line() .x(d => xScale(d.x)) .y(d => yScale(d.y)); host.selectAll("path") .data(this.lines) .enter() .append("path") .attr("d", l => line(l.points)) .attr("fill", "none") .attr("stroke", "steelblue") .attr("stroke-width", 1); // 軸の描画 host.append("g") .attr("class", "axis") .attr("transform", "translate(0, " + (position.height - position.padding) + ")") .call(xAxis); host.append("g") .attr("class", "axis") .attr("transform", "translate(" + position.padding + ", 0)") .call(yAxis); } }
目新しいのは、横軸を時間にしたかったのでx軸のデータのスケールをd3.scaleではなくd3.time.scaleから作っているところです。domainに日付を渡してrangeに数値を渡すような感じで使います。
ついでに横軸のラベルの日付は03/01みたいに月/日の形で表示するようにxAisのtickFormat関数を使って指定しています。フォーマットの処理はd3.time.formatにお任せできるので簡単でした。フォーマットに使える書式は、ドキュメントの以下のページを参照しました。
使ってみる
window.onloadで適当なデータを作って描画する処理を書きます。
window.onload = () => { var chart = new TimelineChart(); // テスト用ダミーデータ 03/01~03/10まで一日単位に適当なデータを突っ込む x 3本 for (var i = 0; i < 3; i++) { var l = new Line<Date, number>(); for (var j = 0; j < 10; j++) { l.points.push(new Point<Date, number>(new Date(2014, 2, 1 + j), Math.round(Math.random() * 1000))); } chart.lines.push(l); } // svg要素を作成 var svg = d3.select("#content").append("svg") .attr("width", 600) .attr("height", 600); // 描画 chart.draw({ x: 10, y: 10, width: 500, height: 500, padding: 50 }, svg); };
そうすると、以下のような感じで描画されます。線ごとに色情報を持たせるかランダムに色を変えたほうがよさげですね…。
コード全体
一応コード全体を。
default.htm
<!DOCTYPE html> <html lang="en"> <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>TypeScript HTML App</h1> <div id="content"></div> </body> </html>
app.css
body { font-family: 'Segoe UI', sans-serif; } span { font-style: italic; } .axis path, .axis line { fill: none; stroke: black; shape-rendering: crispEdges; } .axis text { font-family: sans-serif; font-size: 10px; }
app.ts
/// <reference path="scripts/typings/d3/d3.d.ts" /> // 点 class Point<TX, TY> { constructor(public x: TX = null, public y: TY = null) { } } // 線 class Line<TX, TY> { constructor(public points: Array<Point<TX, TY>> = []) { } } // 横軸が時間の折れ線グラフ class TimelineChart { constructor(public lines: Array<Line<Date, number>> = []) { } draw(position: { x: number; y: number; width: number; height: number; padding: number }, svg: D3.Selection): void { // 全データから最小値最大値を取得する var minMax = { minX: d3.min(this.lines, l => d3.min(l.points, p => p.x)), minY: Math.min(0, d3.min(this.lines, l => d3.min(l.points, p => p.y))), maxX: d3.max(this.lines, l => d3.max(l.points, p => p.x)), maxY: d3.max(this.lines, l => d3.max(l.points, p => p.y)) }; // スケール。x軸は時間 var xScale = d3.time.scale() .domain([minMax.minX, minMax.maxX]) .range([position.padding, position.width - position.padding]); var yScale = d3.scale.linear() .domain([minMax.minY, minMax.maxY]) .range([position.height - position.padding, position.padding]); // 軸 // x軸は月/日でラベル表示する var xAxis = d3.svg.axis().scale(xScale).tickFormat(d3.time.format("%m/%d")); var yAxis = d3.svg.axis().scale(yScale).orient("left"); // 描画のホストになる要素を作成 var host = svg.append("g") .attr("x", position.x) .attr("y", position.y) .attr("width", position.width) .attr("height", position.height); var line = d3.svg.line() .x(d => xScale(d.x)) .y(d => yScale(d.y)); host.selectAll("path") .data(this.lines) .enter() .append("path") .attr("d", l => line(l.points)) .attr("fill", "none") .attr("stroke", "steelblue") .attr("stroke-width", 1); // 軸の描画 host.append("g") .attr("class", "axis") .attr("transform", "translate(0, " + (position.height - position.padding) + ")") .call(xAxis); host.append("g") .attr("class", "axis") .attr("transform", "translate(" + position.padding + ", 0)") .call(yAxis); } } window.onload = () => { var chart = new TimelineChart(); // テスト用ダミーデータ 03/01~03/10まで一日単位に適当なデータを突っ込む x 3本 for (var i = 0; i < 3; i++) { var l = new Line<Date, number>(); for (var j = 0; j < 10; j++) { l.points.push(new Point<Date, number>(new Date(2014, 2, 1 + j), Math.round(Math.random() * 1000))); } chart.lines.push(l); } // svg要素を作成 var svg = d3.select("#content").append("svg") .attr("width", 600) .attr("height", 600); // 描画 chart.draw({ x: 10, y: 10, width: 500, height: 500, padding: 50 }, svg); };