我试图从一些分组数据中制作一个条形图.这是虚拟数据,但结构基本相同.数据:
election results包括一群候选人,组织到他们所在的地区,以及总投票数:
district,candidate,Votes dist 1,Leticia Putte,3580 dist 2,David Barron,1620 dist 2,John Higginson,339 dist 2,Walter Bannister,2866 [...]
我想创建一个条形图或柱形图(老实说,虽然我的最终目标是水平的),按地区划分候选人.
迈克博斯托克有一个excellent demo但我无法根据我的目的智能地翻译它.我开始在https://jsfiddle.net/97ur6cwt/6/取笑它,但我的数据组织有所不同 – 而不是行,按组,我有一个列设置类别.可能只有一名候选人或可能会有一些候选人.
如果组的大小不同,我可以对项目进行分组吗?
解决方法
我的回答类似于@GerardoFurtado,但我使用d3.nest来为每个区域构建一个域.这消除了对硬编码值的需要并将其清理一下:
y0.domain(data.map(function(d) { return d.district; })); var districtD = d3.nest() .key(function(d) { return d.district; }) .rollup(function(d){ return d3.scale.ordinal() .domain(d.map(function(c){return c.candidate})) .rangeRoundBands([0,y0.rangeBand()],pad); }).map(data);
areaD成为您放置rects时使用的y轴域的映射:
svg.selectAll("bar") .data(data) .enter().append("rect") .style("fill",function(d,i) { return color(d.district); }) .attr("x",0) .attr("y",function(d) { return y0(d.district) + districtD[d.district](d.candidate); }) .attr("height",function(d){ return districtD[d.district].rangeBand(); }) .attr("width",function(d) { return x(d.Votes); });
我要开会了,但下一步是清理轴并在那里找到候选人名字.
完整运行代码:
var url = "https://gist.githubusercontent.com/amandabee/edf73bc0bbe131435c952f5ed47524a6/raw/99febb9971f76e36af06f1b99913fcaa645ecb3e/election.csv" var m = {top: 10,right: 10,bottom: 50,left: 110},w = 800 - m.left - m.right,h = 500 - m.top - m.bottom,pad = .1; var x = d3.scale.linear().range([0,w]); y0 = d3.scale.ordinal().rangeRoundBands([0,h],pad); var color = d3.scale.category20c(); var yAxis = d3.svg.axis() .scale(y0) .orient("left"); var xAxis = d3.svg.axis() .scale(x) .orient("bottom") .ticks(5) .tickFormat(d3.format("$,.0f")); var svg = d3.select("#chart").append("svg") .attr("width",w + m.right + m.left + 100) .attr("height",h + m.top + m.bottom) .append("g") .attr("transform","translate(" + m.left + "," + m.top + ")"); // This moves the SVG over by m.left(110) // and down by m.top (10) d3.csv(url,function(error,data) { data.forEach(function(d) { d.Votes = +d.Votes; }); y0.domain(data.map(function(d) { return d.district; })); districtD = d3.nest() .key(function(d) { return d.district; }) .rollup(function(d){ console.log(d); return d3.scale.ordinal() .domain(d.map(function(c){return c.candidate})) .rangeRoundBands([0,pad); }) .map(data); x.domain([0,d3.max(data,function(d) { return d.Votes; })]); svg.append("g") .attr("class","x axis") .attr("transform","translate(0," + h + ")") .call(xAxis) .selectAll("text") .style("text-anchor","middle"); svg.append("g") .attr("class","y axis") .call(yAxis) .append("text"); svg.selectAll("bar") .data(data) .enter().append("rect") .style("fill",i) { return color(d.district); }) .attr("x",0) .attr("y",function(d) { return y0(d.district) + districtD[d.district](d.candidate); }) .attr("height",function(d){ return districtD[d.district].rangeBand(); }) .attr("width",function(d) { return x(d.Votes); }); svg.selectAll(".label") .data(data) .enter().append("text") .text(function(d) { return (d.Votes); }) .attr("text-anchor","start") .attr("x",function(d) { return x(d.Votes)}) .attr("y",function(d) { return y0(d.district) + districtD[d.district](d.candidate) + districtD[d.district].rangeBand()/2;}) .attr("class","axis"); });
.axis { font: 10px sans-serif; } .axis path,.axis line { fill: none; stroke: black; shape-rendering: crispEdges; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> <div id="chart"></div>
一个替代版本,它使条形大小相同并适当地缩放外部域:
<!DOCTYPE html> <html> <head> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> <style> .label { font: 10px sans-serif; } .axis { font: 11px sans-serif; font-weight: bold; } .axis path,.axis line { fill: none; stroke: black; shape-rendering: crispEdges; } </style> </head> <body> <div id="chart"></div> <script> var url = "https://gist.githubusercontent.com/amandabee/edf73bc0bbe131435c952f5ed47524a6/raw/99febb9971f76e36af06f1b99913fcaa645ecb3e/election.csv" var m = { top: 10,left: 110 },pad = .1,padPixel = 5; var x = d3.scale.linear().range([0,w]); var y0 = d3.scale.ordinal(); var color = d3.scale.category20c(); var yAxis = d3.svg.axis() .scale(y0) .orient("left"); var xAxis = d3.svg.axis() .scale(x) .orient("bottom") .ticks(5) .tickFormat(d3.format("$,.0f")); var svg = d3.select("#chart").append("svg") .attr("width",w + m.right + m.left + 100) .attr("height",h + m.top + m.bottom) .append("g") .attr("transform"," + m.top + ")"); // This moves the SVG over by m.left(110) // and down by m.top (10) d3.csv(url,data) { data.forEach(function(d) { d.Votes = +d.Votes; }); var barHeight = h / data.length; y0.domain(data.map(function(d) { return d.district; })); var y0Range = [0]; districtD = d3.nest() .key(function(d) { return d.district; }) .rollup(function(d) { var barSpace = (barHeight * d.length); y0Range.push(y0Range[y0Range.length - 1] + barSpace); return d3.scale.ordinal() .domain(d.map(function(c) { return c.candidate })) .rangeRoundBands([0,barSpace],pad); }) .map(data); y0.range(y0Range); x.domain([0,function(d) { return d.Votes; })]); svg.append("g") .attr("class","x axis") .attr("transform"," + h + ")") .call(xAxis) .selectAll("text") .style("text-anchor","middle"); svg.append("g") .attr("class","y axis") .call(yAxis) .append("text"); svg.selectAll("bar") .data(data) .enter().append("rect") .style("fill",i) { return color(d.district); }) .attr("x",0) .attr("y",function(d) { return y0(d.district) + districtD[d.district](d.candidate); }) .attr("height",function(d) { return districtD[d.district].rangeBand(); }) .attr("width",function(d) { return x(d.Votes); }); var ls = svg.selectAll(".labels") .data(data) .enter().append("g"); ls.append("text") .text(function(d) { return (d.Votes); }) .attr("text-anchor","start") .attr("x",function(d) { return x(d.Votes) }) .attr("y",function(d) { return y0(d.district) + districtD[d.district](d.candidate) + districtD[d.district].rangeBand() / 2; }) .attr("class","label"); ls.append("text") .text(function(d) { return (d.candidate); }) .attr("text-anchor","end") .attr("x",-2) .attr("y",function(d) { return y0(d.district) + districtD[d.district](d.candidate) + districtD[d.district].rangeBand() / 2; }) .style("alignment-baseline","middle") .attr("class","label"); }); </script> </body> </html>