Created by Masayuki Shimizu / @_shimizu
D3.jsを使っても公式サイトのGalleryに掲載されているような複雑なデータビジュアライゼーションが簡単に作れるわけではありません。
var data = [{id:0, value:100}, {id:1, value:200}, {id:2, value:300}]
//選択した要素にデータを束縛する
const div = d3.selectAll("div").data(data, (d) => d.id)
//要素が足りない時は追加
const appendDiv = selector.enter().append("div")
//要素が多すぎる時は削除
const removeDiv = selector.exit().remove()
//アトリビュートの内容をアップデートする
div.merge(appendDiv).attr("width", d=> d.value )
//データを元にフィルタリング
div.filter((d) => d.value >= 100)
//データを元にエレメントを並べ替える
div.sort((a, b) => b.value - a.value )
//トランジション
div.merge(appendDiv).transition().style("opacity", 0 )
const projection = d3.geoMercator()
.fitExtent([[0, 0], [1240, 800]], geojson)
const desc = d3.geoPath().projection(projection)
//path要素にデータを束縛する
const path = svg.selectAll("path").data(geojson.features)
//path要素が足りなければ追加する
const appendPath = path.enter().append("path")
//path要素が多すぎるなら削除する
const removePath = path.exit().remove()
//アトリビュートを更新して地図を描画する
const maps = path.merge(appendPath).attr("d", d => desc(d))
//人口が2万人以上・以下で塗り分ける
maps.attr("fill", d => (d.properties.pop > 20000) ? "green" : "blue" )
//群馬県に所属する市区町村だけ赤く塗る
maps.filter(d => d.properties.pref === "群馬県").attr("fill", "red")
D3.jsはデータを基にエレメントを操作する機能と、トランジションや計算処理など最低限の機能のみを提供する。
自由度は高いが作り手側でやらなくてはならないことも多い。
また、ブラウザ間の差異を埋めるような機能はない。
最初にレイヤーを作っておき、手前に出したいエレメントをコピーして表示する
<svg>
<g class="axisLayer"></g>
<g class="plotLayer">
<circle class="orgin">
</g>
<g class="overlay">
</g>
</svg>
<svg>
<g class="axisLayer"></g>
<g class="plotLayer">
<circle class="orgin">
</g>
<g class="overlay">
<circle class="copy"> // <!-- 他のレイヤーより手前に表示される
</g>
</svg>
var plotLayer = d3.select(".plotLayer")
var ovarlay = d3.select(".overlay")
plotLayer
.selectAll("rect")
.on("mouseover", copy(overlay))
function copy(layer){
return function(){
var node = d3.select(this).node()
var nodeName = node.nodeName
var nodeAttr = node.attributes
var data = d3.select(this).data()
layer.append(nodeName).call((selection) => {
selection.data(data) // data bind
Object.keys(nodeAttr).forEach((key) => {
selection.attr([nodeAttr[key].name], nodeAttr[key].value)
})
})
}
}
function geo2square(coordinates, width, height) {
var centroid = d3.polygonCentroid(coordinates)
width = (width) ? width : 0 ;
height = (height) ? height : 0 ;
var p = []
var i = 0
var length = coordinates.length
var qtr = ~~(length/4)
var nScale = d3.scaleLinear().domain([0, qtr]).range([0, width])
var sScale = d3.scaleLinear().domain([0, qtr]).range([width, 0])
var wScale = d3.scaleLinear().domain([0, qtr]).range([0, height])
var eScale = d3.scaleLinear().domain([0, qtr]).range([height, 0])
while (i < length) {
if (i <= qtr){
p.push([ centroid[0]+nScale(i), centroid[1] ])
}
else if (i <= qtr*2){
p.push([ centroid[0]+width, centroid[1] + wScale(i-qtr) ])
}
else if (i <= qtr*3){
p.push([ centroid[0]+sScale(i-qtr*2), centroid[1]+height ])
}
else if (i <= qtr*4){
p.push([ centroid[0], centroid[1]+eScale(i-qtr*3) ])
}
i++
}
return p
};
SVGには折り返すという機能がないため、レスポンシブに対応するためにはリサイズのたびにすべての座標を再計算して更新する必要がある。
text要素も折り返しが効かないので、地味に困る。
var textArray = ["1行目、あ","2行目", "3行目あああああ", "4行目あ"]
d3.select("svg").append("text")
.datum(textArray)
.attr("transform", "translate(100, 100)")
.each(leftLinebreak)
function leftLinebreak(array){
d3.select(this).selectAll("tspan")
.data(array)
.enter()
.append("tspan")
.attr("x", "0em")
.attr("y", function(d,i){ return i + "em"})
.text(function(d){ return d })
}
<text>
<tspan x="0em" y="0em">1行目、あ</tspan>
<tspan x="0em" y="1em">2行目</tspan>
<tspan x="0em" y="2em">3行目あああああ</tspan>
<tspan x="0em" y="3em">4行目あ</tspan>
</text>
<svg>
<foreignObject width="200" height="200">
<html>
<div style="width:100px">ああああああああああああああああああああああああああああああああ</div>
</html>
</foreignObject>
</svg>
※ IE11未対応
データの収集・整形なども含め、データビジュアライゼーションを作成するには地味で泥臭い作業がとても重要です。