D3v4でカテゴリカルなスケールのヒストグラムを書くベストプラクティスは何か考えてみた
最近、Riotxをある程度使えるようになった(気がする) ので、Riotxを使って複数のコンポーネントに跨ったチャートの状態を同期した拡張版crossfilterのようなものが書けないか検討しています。
試しに簡単なサンプルを作って見ようと、d3.histogramを使って書いて見ようとしたのですが、 d3.scaleOrdinalなデータをhistogramに変換できない。。 ということで、D3.jsでカテゴリー的スケールのヒストグラムを書く良い方法を検討してみました。
オブジェクトの配列を度数に変換する
ヒストグラム用のデータを作るなら当たり前の処理ですが、この処理をモジュール的に作っておこうと思います。
d3category.js
const d3category = {
count: function (data, k) {
const nestedData = d3.nest()
.key(function(d) {return d[k]})
.entries(data);
const l = [];
nestedData.forEach((d) => {
const item = {}
l.push({key: d.key, count: d.values.length})
})
return l;
},
filter: function (data, s) {
// crossfitlter用に準備
const res = data.filter(d => {return d[s[0]] === s[1]});
return res
}
};
変換した度数のデータで普通にbarcharを描画する。
データはこんな感じ。普通はd3.requestsを使って読み込むと思います。
const data = [
{age: 1, breed: "Chihuahua"},
{age: 2, breed: "Chihuahua"},
{age: 2, breed: "Chihuahua"},
{age: 2, breed: "Poodle"},
{age: 3, breed: "Chihuahua"},
{age: 3, breed: "Shiba"},,,
]
これを上記の関数で度数に変換し、そのデータで
const width = 600,
height = 300;
const cats = d3category.count(data, "breed");
const svg = d3.select('svg'),
xScale = d3.scaleBand()
.range([0, width])
.domain(cats.map(d => d.key))
.padding(0.05),
yScale = d3.scaleLinear()
.domain([0, d3.max(cats.map(x => x.count))])
.range([0, height]);
svg.selectAll('rect')
.data(cats)
.enter()
.append('rect')
.attr('width', xScale.bandwidth())
.attr('height', d => yScale(d.count))
.attr('x', d => xScale(d.key))
.attr('y', d => height - yScale(d.count));
svg .append("g")
.attr("transform", "translate(0, " + height + ")")
.call(d3.axisBottom(xScale));
svg.append("g")
.call(d3.axisLeft(yScale));
結果、極々普通の書き方に。
実行結果はこちら。