D3.js v4/v5 インタラクティブなサンバースト図の作り方
D3によるサンバースト(sun burst)図で、クリック/タッチにより対象部分をズームする方法を紹介します。
サンプルデモ – 外側をクリック/タッチしてください(CかHがおススメ)。真ん中を再びクリック/タッチすると元に戻ります。
サンプルプログフラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>D3 zoomable sunburst v4/v5</title> <script src="https://d3js.org/d3.v5.min.js"></script> </head> <body> <script> // 1. 描画用のデータ準備 var width = 800; var height = 600; var radius = Math.min(width, height) / 2; var centerRadius = 0.4 * radius; var backCircleRadius = 0.1 * radius; var data = { "name": "A", "children": [ { "name": "B", "value": 25 }, { "name": "C", "children": [{ "name": "D", "value": 10 }, { "name": "E", "value": 15 }, { "name": "F", "value": 10 }] }, { "name": "G", "value": 15 }, { "name": "H", "children": [{ "name": "I", "value": 20 }, { "name": "J", "value": 10 }] }, { "name": "K", "value": 10 } ] }; // 2. SVG表示用要素の設定 var svg = d3.select("#d3Graph4me").append("svg").attr("width", width).attr("height", height); g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + (height / 2) + ")"); // 3. 描画用スケールの設定 var colorScale = d3.scaleOrdinal().range([ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" ]); var xScale = d3.scaleLinear().range([0, 2 * Math.PI]); var rScale = d3.scaleLinear().range([0.4 * radius, radius]); // 4. 描画用のデータ変換 root = d3.hierarchy(data); root.sum(function(d) { return d.value; }) .sort(function(a, b) { return b.height - a.height || b.value - a.value; }); var partition = d3.partition(); partition(root); // 5. SVG要素の設定 var arc = d3.arc() .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, xScale(d.x0))); }) .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, xScale(d.x1))); }) .innerRadius(function(d) { return Math.max(0, rScale(d.y0)); }) .outerRadius(function(d) { return Math.max(0, rScale(d.y1)); }); g.selectAll("path") .data(root.descendants()) .enter() .append("path") .attr("d", arc) .attr('stroke', '#fff') .attr("fill", function(d) { while(d.depth > 1) d = d.parent; if(d.depth == 0) return "lightgray"; return colorScale(d.value); }) .attr("opacity", 0.8) .on("click", click) .append("title") .text(function(d) { return d.data.name + "\n" + d.value; }); g.selectAll("text") .data(root.descendants()) .enter() .append("text") .attr("fill", "black") .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; }) .attr("dy", "5px") .attr("font", "10px") .attr("text-anchor", "middle") .on("click", click) .text(function(d) { return d.data.name; }); //6. クリック時のイベント関数 function click(d) { var tween = g.transition() .duration(500) .tween("scale", function() { var xdomain = d3.interpolate(xScale.domain(), [d.x0, d.x1]); var ydomain = d3.interpolate(rScale.domain(), [d.y0, 1]); var yrange = d3.interpolate(rScale.range(), [d.y0 ? backCircleRadius : centerRadius, radius]); return function(t) { xScale.domain(xdomain(t)); rScale.domain(ydomain(t)).range(yrange(t)); }; }); tween.selectAll("path") .attrTween("d", function(d) { return function() { return arc(d); }; }); tween.selectAll("text") .attrTween("transform", function(d) { return function() { return "translate(" + arc.centroid(d) + ")"; }; }) .attrTween("opacity", function(d) { return function() { return(xScale(d.x0) < 2 * Math.PI) && (xScale(d.x1) > 0.0) && (rScale(d.y1) > 0.0) ? 1.0 : 0; }; }) .attrTween("font", function(d) { return function() { return(xScale(d.x0) < 2 * Math.PI) && (xScale(d.x1) > 0.0) && (rScale(d.y1) > 0.0) ? "10px" : 1e-6; }; }); } </script> </body> </html> |
解説
基本的なサンバースト図の作り方は、こちらを参照ください。
1. 描画用のデータ準備
インタラクティブな動作に必要な、デフォルト時の真ん中の円の半径と、元に戻す用の中心円の半径を変数として準備します。
1 2 |
var centerRadius = 0.4 * radius; var backCircleRadius = 0.1 * radius; |
3. 描画用スケールの設定
描画時のスケール変換関数を設定しておきます。クリック時にスケール変換関数を変更することでグラフを変化させます。
1 2 |
var xScale = d3.scaleLinear().range([0, 2 * Math.PI]); var rScale = d3.scaleLinear().range([0.4 * radius, radius]); |
4. 描画用のデータ変換
partitionへのデータ変換時に.sizeを設定せず、デフォルトで0~1の範囲で定義されるようにします。
5. SVG要素の設定
サイズ変更に対応できるように、path要素の”d”属性を算出する関数を設定します。
1 2 3 4 5 |
var arc = d3.arc() .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, xScale(d.x0))); }) .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, xScale(d.x1))); }) .innerRadius(function(d) { return Math.max(0, rScale(d.y0)); }) .outerRadius(function(d) { return Math.max(0, rScale(d.y1)); }); |
startAngle、endAngleは0~2πの値に収まるようにし、範囲外のものはstartAngleとendAngleが0,0もしくは2π,2πとなり、見えなくなるように設定します。同様にinnerRadius、outerRadiusも0以下の半径のものはinnerRadius、outerRadiusともに0となるように設定し、見えなくします。
SVG要素の追加はサンバースト図の基本的な設定と同じです。クリックしたときのイベント関数をonメソッドで追加します。
6.クリック時のイベント関数
クリックした際のイベント関数を設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
function click(d) { var tween = g.transition() .duration(500) .tween("scale", function() { var xdomain = d3.interpolate(xScale.domain(), [d.x0, d.x1]); var ydomain = d3.interpolate(rScale.domain(), [d.y0, 1]); var yrange = d3.interpolate(rScale.range(), [d.y0 ? backCircleRadius : centerRadius, radius]); return function(t) { xScale.domain(xdomain(t)); rScale.domain(ydomain(t)).range(yrange(t)); }; }); tween.selectAll("path") .attrTween("d", function(d) { return function() { return arc(d); }; }); tween.selectAll("text") .attrTween("transform", function(d) { return function() { return "translate(" + arc.centroid(d) + ")"; }; }) .attrTween("opacity", function(d) { return function() { return(xScale(d.x0) < 2 * Math.PI) && (xScale(d.x1) > 0.0) && (rScale(d.y1) > 0.0) ? 1.0 : 0; }; }) .attrTween("font", function(d) { return function() { return(xScale(d.x0) < 2 * Math.PI) && (xScale(d.x1) > 0.0) && (rScale(d.y1) > 0.0) ? "10px" : 1e-6; }; }); } |
まず、transition().duration(500)は、500ミリ秒かけて徐々に属性を線形に変化させるメソッドです。今回は”d”属性を時間経過とともに複雑に変える必要がありますのでtweenメソッドを用います。tweenでは時刻tが0.0から1.0まで変化するときの状態を設定する関数を返り値として設定します。設定後、attrTweenメソッドを使って時間変化する属性を割り当てます。
1 2 3 4 5 6 7 8 |
.tween("scale", function() { var xdomain = d3.interpolate(xScale.domain(), [d.x0, d.x1]); var ydomain = d3.interpolate(rScale.domain(), [d.y0, 1]); var yrange = d3.interpolate(rScale.range(), [d.y0 ? backCircleRadius : centerRadius, radius]); return function(t) { xScale.domain(xdomain(t)); rScale.domain(ydomain(t)).range(yrange(t)); }; |
d3.interpolateは第一引数と第二引数を補完する関数を設定するもので、設定した関数の引数に0.0から1.0を設定すると中間の値を返してくれます。d3.interpolateを使って 描画用スケールのxScaleとrScaleの設定を変更します。
1 |
d3.interpolate(xScale.domain(), [d.x0, d.x1]) |
では、現在のxScaleの値の入力範囲を、クリックした要素の範囲に変更します。つまり、クリックした要素の角度範囲が表示上の0~2πの範囲になり、それ以外のものは見えなくなります。
同様に、
1 |
d3.interpolate(rScale.domain(), [d.y0, 1]) |
ではクリックした要素の内側の半径を最小値に設定し
1 |
d3.interpolate(rScale.range(), [d.y0 ? backCircleRadius : centerRadius, radius]) |
では、d.y0が0以外の場合(Aがクリックされていない場合)は、内側の半径がbackCircleRadius、Aがクリックされた場合はcenterRadiusとなるように制御します。
tweenメソッドにより、時間経過とともに描画用スケールが変更されますので、その時刻のスケールに合わせた”d”属性をattrTweenメソッドを使って割り当てます。
1 2 3 4 5 6 |
tween.selectAll("path") .attrTween("d", function(d) { return function() { return arc(d); }; }); |
テキストも同様ですが、こちらは表示範囲から外れたものを”opacity”属性と”font”属性を変えることで見えなくします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
tween.selectAll("text") .attrTween("transform", function(d) { return function() { return "translate(" + arc.centroid(d) + ")"; }; }) .attrTween("opacity", function(d) { return function() { return(xScale(d.x0) < 2 * Math.PI) && (xScale(d.x1) > 0.0) && (rScale(d.y1) > 0.0) ? 1.0 : 0; }; }) .attrTween("font", function(d) { return function() { return(xScale(d.x0) < 2 * Math.PI) && (xScale(d.x1) > 0.0) && (rScale(d.y1) > 0.0) ? "10px" : 1e-6; }; }); |
まとめ
サンバースト図は、D3.jsの解説本の表紙にもなっており、D3らしい表現です。また、普通の円グラフと比べて多くの情報が表示できるうえに見やすく、実用性もかなり高いです。基本的なサンバースト図についてはこちらを参照ください。