d3 を使用して問題を解決するのは、シンプルかつエレガントです。今朝、少し時間をかけて、自分のニーズに適応できるフィドルを作成しました。
http://jsfiddle.net/CelloG/47nxxhfu/
d3 を使用するには、データを html 要素に結合する方法を理解する必要があります。著者による簡単な説明については、http://bost.ocks.org/mike/join/を参照してください。
フィドルのコードは次のとおりです。
var table = d3.select('#data')
// set up the table header
table.append('thead')
.append('tr')
.selectAll('th')
.data(['Title', 'Visits', 'Sold', 'Conversion Rate'])
.enter()
.append('th')
.text(function (d) { return d })
table.append('tbody')
// set up the data
// note that both the creation of the table AND the update is
// handled by the same code. The code must be run on each time
// the data is changed.
function setupData(data) {
// first, select the table and join the data to its rows
// just in case we have unsorted data, use the item's title
// as a key for mapping data on update
var rows = d3.select('tbody')
.selectAll('tr')
.data(data, function(d) { return d.title })
// if you do end up having variable-length data,
// uncomment this line to remove the old ones.
// rows.exit().remove()
// For new data, we create rows of <tr> containing
// a <td> for each item.
// d3.map().values() converts an object into an array of
// its values
var entertd = rows.enter()
.append('tr')
.selectAll('td')
.data(function(d) { return d3.map(d).values() })
.enter()
.append('td')
entertd.append('div')
entertd.append('span')
// now that all the placeholder tr/td have been created
// and mapped to their data, we populate the <td> with the data.
// First, we split off the individual data for each td.
// d3.map().entries() returns each key: value as an object
// { key: "key", value: value}
// to get a different color for each column, we set a
// class using the attr() function.
// then, we add a div with a fixed height and width
// proportional to the relative size of the value compared
// to all values in the input set.
// This is accomplished with a linear scale (d3.scale.linear)
// that maps the extremes of values to the width of the td,
// which is 100px
// finally, we display the value. For the title entry, the div
// is 0px wide
var td = rows.selectAll('td')
.data(function(d) { return d3.map(d).entries() })
.attr('class', function (d) { return d.key })
// the simple addition of the transition() makes the
// bars update smoothly when the data changes
td.select('div')
.transition()
.duration(800)
.style('width', function(d) {
switch (d.key) {
case 'conversion_rate' :
// percentage scale is static
scale = d3.scale.linear()
.domain([0, 1])
.range([0, 100])
break;
case 'today_visits':
case 'sold_today' :
scale = d3.scale.linear()
.domain(d3.extent(data, function(d1) { return d1[d.key] }))
.range([0, 100])
break;
default:
return '0px'
}
return scale(d.value) + 'px'
})
td.select('span')
.text(function(d) {
if (d.key == 'conversion_rate') {
return Math.round(100*d.value) + '%'
}
return d.value
})
}
setupData(randomizeData())
d3.select('#update')
.on('click', function() {
setupData(randomizeData())
})
// dummy randomized data: use this function for the socketio data
// instead
//
// socket.on('sellers', function(msg){
// setupData(JSON.parse(msg).items)
// })
function randomizeData() {
var ret = []
for (var i = 0; i < 1000; i++) {
ret.push({
title: "Item " + i,
today_visits: Math.round(Math.random() * 300),
sold_today: Math.round(Math.random() * 200),
conversion_rate: Math.random()
})
}
return ret
}