点の集まりで棒グラフを表現
矢印キーの左右か、下部の細長い長方形をクリックすることで点が移り変わる。
year | A | B | C | D | E | F | G | |
---|---|---|---|---|---|---|---|---|
0 | 401 | 150 | 0 | 144 | 48 | 410 | 803 | |
1 | 419 | 299 | 90 | 141 | 80 | 180 | 802 | |
2 | 468 | 440 | 97 | 95 | 48 | 42 | 860 | |
3 | 585 | 459 | 100 | 99 | 48 | 71 | 702 | |
4 | 462 | 634 | 89 | 80 | 44 | 104 | 670 | |
5 | 423 | 233 | 81 | 84 | 19 | 361 | 882 |
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<script src="//d3js.org/d3.v3.min.js"></script> | |
<body style='margin:0;padding:0;'> | |
<script> | |
var dim = {width: 960, height: 500}; | |
var margin = {top: 10, bottom: 50, left: 50, right: 10}; | |
var inputHeight = 20; | |
var numberFormat = d3.format('.0f'); | |
dim.graphWidth = dim.width - margin.left - margin.right; | |
dim.graphHeight = dim.height - margin.top - margin.bottom; | |
d3.select('body').on('keydown',function() | |
{ | |
if (d3.event.which === 39) | |
{ | |
next(); | |
} | |
if (d3.event.which === 37) | |
{ | |
prev(); | |
} | |
}); | |
var svg = d3.select('body').append('svg') | |
.attr({width: dim.width, height: dim.height}) | |
.style({margin:0,padding:0}); | |
var axisLayer = svg.append('g').attr('transform','translate(' + margin.left + ',' + margin.top + ')'); | |
var graphLayer = svg.append('g').attr('transform','translate(' + margin.left + ',' + margin.top + ')'); | |
var inputLayer = svg.append('g').attr('transform','translate(0,' + (dim.height - inputHeight) + ')'); | |
var xScale = d3.scale.ordinal().rangeBands([0,dim.graphWidth],0.05); | |
var xLocalScale = d3.scale.ordinal(); | |
var yScale = d3.scale.ordinal().rangePoints([dim.graphHeight, 0]); | |
var colorScale = d3.scale.category10(); | |
var inputScale = d3.scale.ordinal().rangeBands([0,dim.width-margin.right]); | |
var xAxis = d3.svg.axis().orient('bottom').scale(xScale); | |
var yAxis = d3.svg.axis().orient('left').scale(yScale); | |
var xAxisObj = axisLayer.append('g') | |
.attr('transform','translate('+0+','+dim.graphHeight+')') | |
.attr('class','axis') | |
.call(xAxis); | |
var yAxisObj = axisLayer.append('g') | |
.attr('transform','translate('+0 +','+0+')') | |
.attr('class','axis') | |
.call(yAxis); | |
axisLayer.selectAll('.axis text').style('font','14px "Lucida Grande", Helvetica, Arial, sans-serif'); | |
axisLayer.selectAll('.axis path.domain').style({fill:'none',stroke:'#000000','shape-rendering':'crispEdges'}); | |
axisLayer.selectAll('.axis line').style({fill:'none',stroke:'#000000','shape-rendering':'crispEdges'}); | |
var time = 0; | |
var yearLabel = 'year'; | |
var radius = 3; | |
var mar = 0.6; | |
var barWidth = 16; | |
var displaydata = []; | |
var years = []; | |
var auto = true; | |
var duration = 2000; | |
var delayMax = 1000; | |
trans = function(to) | |
{ | |
if ( to === time || to < 0 || to >= years.length) | |
{ | |
return; | |
} | |
var current = time; | |
time = to; | |
yearTarget = years[time]; | |
var votes = graphLayer.selectAll('.vote') | |
.filter(function(d){return d[current].label!=d[time].label || d[current].idx!=d[time].idx;}) | |
.transition() | |
.duration(duration) | |
.delay(function(d){return Math.random()*delayMax;}) | |
.attr('cx',function(d){return ((d[time].label!=null)?(xScale(d[time].label)+xLocalScale(d[time].idx%barWidth)+radius+mar):(dim.graphWidth/2));}) | |
.attr('cy',function(d){return ((d[time].label!=null)?(yScale(Math.floor((d[time].idx+0.1)/barWidth))-radius-mar):0);}) | |
.style('opacity',function(d){return (d[time].label!=null)?0.8:0.0;}) | |
.style('fill',function(d){return colorScale(d[time].label);}); | |
inputLayer.select('.cursor').transition().duration(duration/2) | |
.attr('x',function(d){return inputScale(years[time]);}); | |
inputLayer.selectAll('.button text').transition().duration(duration/2) | |
.style('fill',function(d,i){return (i===time)?'#FFF':'#000';}) | |
} | |
prev = function() | |
{ | |
trans(time-1); | |
} | |
next = function() | |
{ | |
trans(time+1); | |
} | |
d3.csv('data.csv', function(error,raw) | |
{ | |
if (error != null) | |
{ | |
console.log(error); | |
return; | |
} | |
years = d3.set(raw.map(function(d){return d[yearLabel];})).values(); | |
yearTarget = years[0]; | |
var parties = d3.keys(raw[0]).filter(function(d){return d !== yearLabel;}); | |
var partDict = {}; | |
parties.forEach(function(d,i) | |
{ | |
partDict[d] = i; | |
}); | |
var sums = {}; | |
var data = {}; | |
years.forEach(function(year) | |
{ | |
data[year] = parties.map(function(party) | |
{ | |
return +raw.filter(function(d){return d[yearLabel] === year;})[0][party]||0; | |
}); | |
sums[year] = d3.sum(data[year]); | |
}); | |
var max = d3.max(years.map(function(d){return d3.max(data[d]);})); | |
var nrow = Math.ceil(dim.graphHeight/(2*(radius+mar))); | |
barWidth = Math.ceil(max/nrow); | |
yScale.domain(d3.range(nrow)); | |
yAxis.tickValues(d3.range(nrow).filter(function(d){return d%10===0;})); | |
yAxis.tickFormat(function(d){return (d*barWidth);}); | |
xScale.domain(parties.map(function(d,i){return i;})); | |
xAxis.tickFormat(function(d){return parties[d];}); | |
xAxisObj.call(xAxis); | |
yAxisObj.call(yAxis); | |
xLocalScale.rangeBands([0,xScale.rangeBand()]).domain(d3.range(barWidth)); | |
colorScale.domain(d3.range(parties.length)); | |
inputScale.domain(years); | |
var currentButton = inputLayer.append('rect') | |
.attr('class','cursor') | |
.attr({x:0,y:0,height:inputHeight,width:inputScale.rangeBand()}) | |
.style('stroke','#FFF') | |
.style('stroke-width',2) | |
.style('fill','#000'); | |
var buttons = inputLayer.selectAll('.button').data(years).enter().append('g').attr('class','button') | |
.attr('transform',function(d){return 'translate(' + inputScale(d) + ',' + 0 +')';}) | |
.on('click',function() | |
{ | |
var s = d3.select(this); | |
trans(years.indexOf(s.datum())); | |
}); | |
buttons.append('rect') | |
.attr({x:0,y:0,height:inputHeight,width:inputScale.rangeBand()}) | |
.style('stroke','#FFF') | |
.style('stroke-width',2) | |
.style('fill','rgba(0,0,0,0.1)'); | |
buttons.append('text') | |
.text(function(d){return d;}) | |
.attr('x',function(d){return inputScale.rangeBand()/2;}) | |
.attr('y',0) | |
.style('fill',function(d,i){return (i===0)?'#FFF':'#000';}) | |
.style('text-anchor','middle') | |
.style('font',inputHeight+'px "Lucida Grande", Helvetica, Arial, sans-serif').style('dominant-baseline','text-before-edge'); | |
var summax = d3.max(years.map(function(d){return sums[d];})); | |
var displaydata = d3.range(summax).map(function(d){return [];}); | |
var indexMargin = 0; | |
parties.forEach(function(party,partyidx) | |
{ | |
for (var i=0;i<data[years[0]][partyidx];++i) | |
{ | |
displaydata[indexMargin+i].push({label:partyidx,idx:i}); | |
} | |
indexMargin += data[years[0]][partyidx]; | |
}); | |
for (var i=indexMargin;i<summax;++i) | |
{ | |
displaydata[i].push({label:null,idx:null}); | |
} | |
d3.range(1,years.length).forEach(function(idx) | |
{ | |
var year = years[idx]; | |
var lastyear = years[idx-1]; | |
var yearidx = idx; | |
var pool = []; | |
var unused = []; | |
var keep = []; | |
displaydata.forEach(function(d,i) | |
{ | |
var copy = {label:d[yearidx-1].label,idx:d[yearidx-1].idx}; | |
d.push(copy); | |
if ( d[yearidx].label == null) | |
{ | |
unused.push(i); | |
} | |
else | |
{ | |
if(data[year][d[yearidx].label] <= d[yearidx].idx) | |
{ | |
pool.push(i); | |
} | |
else | |
{ | |
keep.push(i); | |
} | |
} | |
}); | |
d3.shuffle(pool); | |
if ( sums[year] - sums[lastyear] > 0 ) | |
{ | |
pool = pool.concat(unused.splice(0,sums[year]-sums[lastyear])); | |
d3.shuffle(pool); | |
} | |
else | |
{ | |
pool.splice(sums[year]-keep.length).forEach(function(d) | |
{ | |
displaydata[d][yearidx] = {label:null,idx:null}; | |
}); | |
pool = pool.splice(0,sums[year]-keep.length); | |
} | |
var poolmargin = 0; | |
parties.forEach(function(party) | |
{ | |
if (data[year][partDict[party]] - data[lastyear][partDict[party]] > 0) | |
{ | |
for(var i=0;i<(data[year][partDict[party]]-data[lastyear][partDict[party]]);++i) | |
{ | |
displaydata[pool[poolmargin+i]][yearidx] = {label:partDict[party],idx:i+data[lastyear][partDict[party]]}; | |
}; | |
poolmargin += data[year][partDict[party]]-data[lastyear][partDict[party]]; | |
} | |
}); | |
}); | |
var votes = graphLayer.selectAll('.vote').data(displaydata).enter().append('circle') | |
.attr('class','vote') | |
.attr('r',radius) | |
.attr('cx',function(d){return ((d[time].label!=null)?(xScale(d[time].label)+xLocalScale(d[time].idx%barWidth)+radius+mar):(dim.graphWidth/2));}) | |
.attr('cy',function(d){return ((d[time].label!=null)?(yScale(Math.floor((d[time].idx+0.1)/barWidth))-radius-mar):0);}) | |
.style('opacity',function(d){return (d[time].label!=null)?0.8:0.0;}) | |
.style('fill',function(d){return colorScale(d[time].label);}); | |
}); | |
</script> |