サーバ側でd3を使って作ったsvgを画像に変換して返す
おはようございます、この記事はd3.js Advent Calendar 2014の21日目の記事です。
d3.js Advent Calendar 2014 - Qiita
つい先日からd3を使い始めました。
d3のpackage.json読んでたらnode側から使う場合はjsdomを使ってるっぽいのみつけた。 ドキュメントにも書いてあったけどサーバサイドでも普通にjsdom使ってdom操作的なやつできるらしい。
ならサーバサイドでデータを元にsvg組み立てて、それをnode-canvasに転写してpngなりに変換してstreamでレスポンスにながせるよねって思ったのでやってみた。
必要なモジュールのインストール
svgをnode-canvasに転写するのfabricが多分一番簡単なのでこれをいれる
でもその前にそれが依存してるnode-canvasのためにcairoいれる必要がある。
多分一番辛い作業だけどこの辺がんばってください
Home · Automattic/node-canvas Wiki · GitHub
このcairoがんばる作業が終わったらあとは普通に必要なモジュールをnpmからもってきます
npm install fabric npm install d3
アプリケーションの作成
まずはサーバサイドでsvgを組み立てます
var d3 = require('d3') , body = d3.select('body') , svg ; // 赤い丸を作る例 body .append('svg') .attr({width: 300, height: 300}) .append('circle') .attr({cx: 150, cy: 150, r: 100, fill: '#ff0000'}); svg = body.node().innerHTML console.log(svg); // <svg width="300" height="300"> // <circle cx="150" cy="150" r="100" fill="#ff0000"></circle> // </svg>
これをfabricを使ってcanvasに転写する
// svg文字列から画像情報を読みこむ fabric.loadSVGFromString(svg, function (objects, options) { var canvas, obj, stream; canvas = fabric.createCanvasForNode(300, 300); obj = fabric.util.groupSVGElements(objects, options); canvas.add(obj).renderAll(); });
あとはサーバ書いてレスポンスに垂れ流す 完成版
var http = require('http') , d3 = require('d3') , fabric = require('fabric').fabric ; http.createServer(function(req, res) { var canvas = fabric.createCanvasForNode(300, 300) , body = d3.select('body') , svg ; body .append('svg') .attr({width: 300, height: 300}) .append('circle') .attr({cx: 150, cy: 150, r: 100, fill: '#ff0000'}); svg = body.node().innerHTML; fabric.loadSVGFromString(svg, function (objects, options) { var obj, stream; obj = fabric.util.groupSVGElements(objects, options); canvas.add(obj).renderAll(); stream = canvas.createPNGStream(); stream.on('data', function(chunk) { res.write(chunk); }); stream.on('end', function() { res.end(); }); }); }).listen(3000);
サーバ起動して該当ページ ( http://localhost:3000/ )にアクセスすると日の丸ができてるはず。
表示
それっぽいやつ
// ... // それっぽいデータ color = d3.scale.category20(); data = [5, 10, 2, 20, 5, 8, 13, 25, 26, 29, 22, 6]; body .append("svg") .attr({width: width, height: height}) .append('g') .selectAll('rect') .data(data).enter() .append('rect').attr('x', function (d,i) { return (i * 25) + 5 }) .attr('y', function (d,i) { return height - (d * 10) }) .attr('width', 20).attr('height', function (d, i) { return d * 10 }) .attr('fill', function (d, i) { return color(i); }); svg = body.node().innerHTML; fabric.loadSVGFromString(svg, function (objects, options) { // ...
表示
それっぽいですね!><