简体   繁体   中英

d3 Responsive bar chart

Ive been at this for hours and cant seem to get my grouped bar chart to behave. Specifically trying to obtain a proper width for the 'g' translate property around each bar.

I have tried multiple methods and this seems to be the most elegant although im open to other solutions. The goal is something like this: http://www.cagrimmett.com/til/2016/04/26/responsive-d3-bar-chart.html

 var data = [{"category":"Securily Provisions","values":[{"value":50,"rate":"Work Performed"},{"value":40,"rate":"Knowledge, Skills, and Abilities"}]},{"category":"Investigate","values":[{"value":25,"rate":"Work Performed"},{"value":21,"rate":"Knowledge, Skills, and Abilities"}]},{"category":"Operate and Maintain","values":[{"value":3,"rate":"Work Performed"},{"value":22,"rate":"Knowledge, Skills, and Abilities"}]},{"category":"Oversee and Govern","values":[{"value":12,"rate":"Work Performed"},{"value":7,"rate":"Knowledge, Skills, and Abilities"}]},{"category":"Protect and Defend","values":[{"value":6,"rate":"Work Performed"},{"value":15,"rate":"Knowledge, Skills, and Abilities"}]},{"category":"Collect and Operate","values":[{"value":92,"rate":"Work Performed"},{"value":85,"rate":"Knowledge, Skills, and Abilities"}]}] var margin = {top: 20, right: 20, bottom: 30, left: 40}, width = 960 - margin.left - margin.right, height = 500 ; var x0 = d3.scale.ordinal().rangeRoundBands([0, width], .5); var x1 = d3.scale.ordinal(); var y = d3.scale.linear().range([height, 0]); var xAxis = d3.svg.axis() .scale(x0) .orient("bottom"); var yAxis = d3.svg.axis() .scale(y) .orient("left"); var color = d3.scale.ordinal() .range(["#02bfe7","#fdb81e"]); var svg = d3.select('#chart-area').append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .attr("preserveAspectRatio", "xMinYMin meet") .append("g").attr("class","container") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); //d3.json("data.json", function(error, data) { var categoriesNames = data.map(function(d) { return d.category; }); var rateNames = data[0].values.map(function(d) { return d.rate; }); x0.domain(categoriesNames); x1.domain(rateNames).rangeRoundBands([0, x0.rangeBand()]); y.domain([0, d3.max(data, function(categorie) { return d3.max(categorie.values, function(d) { return d.value; }); })]); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); svg.append("g") .attr("class", "y axis") .style('opacity','0') .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .style('font-weight','bold') .text("Value"); svg.select('.y').transition().duration(500).delay(1300).style('opacity','1'); var slice = svg.selectAll(".slice") .data(data) .enter().append("g") .attr("class", "g") .attr("transform",function(d) { return "translate(" + x0(d.category) + ",0)"; }); slice.selectAll("rect") .data(function(d) { return d.values; }) .enter().append("rect") .attr("width", x1.rangeBand()) .attr("x", function(d) { return x1(d.rate); }) .style("fill", function(d) { return color(d.rate) }) .attr("y", function(d) { return y(0); }) .attr("height", function(d) { return height - y(0); }) .on("mouseover", function(d) { d3.select(this).style("fill", d3.rgb(color(d.rate)).darker(2)); }) .on("mouseout", function(d) { d3.select(this).style("fill", color(d.rate)); }); slice.selectAll("rect") .transition() .delay(function (d) {return Math.random()*1000;}) .duration(1000) .attr("class","bar") .attr("y", function(d) { return y(d.value); }) .attr("height", function(d) { return height - y(d.value); }); //Legend var legend = svg.selectAll(".legend") .data(data[0].values.map(function(d) { return d.rate; }).reverse()) .enter().append("g") .attr("class", "legend") .attr("transform", function(d,i) { return "translate(0," + i * 20 + ")"; }) .style("opacity","0"); legend.append("rect") .attr("x", width - 18) .attr("width", 18) .attr("height", 18) .style("fill", function(d) { return color(d); }); legend.append("text") .attr("x", width - 24) .attr("y", 9) .attr("dy", ".35em") .style("text-anchor", "end") .text(function(d) {return d; }); legend.transition().duration(500).delay(function(d,i){ return 1300 + 100 * i; }).style("opacity","1"); document.addEventListener("DOMContentLoaded", resize); d3.select(window).on('resize', resize); function resize() { console.log('----resize function----'); // update width width = parseInt(d3.select('#chart-area').style('width'), 10); width = width - margin.left - margin.right; height = parseInt(d3.select("#chart-area").style("height")); height = height - margin.top - margin.bottom; console.log('----resiz width----'+width); console.log('----resiz height----'+height); // resize the chart x0.range([0, width]); x0.rangeRoundBands([0, width], .03); y.range([height, 0]); yAxis.ticks(Math.max(height/50, 2)); xAxis.ticks(Math.max(width/50, 2)); d3.select(svg.node().parentNode) .style('width', (width + margin.left + margin.right) + 'px'); svg.selectAll('.g') //.attr("x", function(d) { return x0(categoriesNames); }) //.attr("x", function(d) { return x1(d.rate); }) // Problem here applying new width within translate .attr("transform", "translate(10,0)") .attr("width", x1.rangeBand()); svg.selectAll("text") // .attr("x", function(d) { return x0(categoriesNames); }) .attr("x", (function(d) { return x0(categoriesNames ) + x0.rangeBand() / 2 ; } )) .attr("y", function(d) { return y(rateNames) + 1; }) .attr("dy", ".75em"); svg.select('.x.axis').call(xAxis.orient('bottom')).selectAll("text").attr("x",55); } //});
 .bar{ fill: steelblue; } .bar:hover{ fill: brown; } .axis { font: 10px sans-serif; } .axis path, .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; } #chart-area {width: 100%;}
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> <div id="chart-area"></div>

Here's a solution. Is this the desired output? Try resizing the window.

JS FIDDLE

Resize function:

function resize() {
console.log('----resize function----');
// update width
width = parseInt(d3.select('#chart-area').style('width'), 10);
width = width - margin.left - margin.right;

height = parseInt(d3.select("#chart-area").style("height"));
height = height - margin.top - margin.bottom;
console.log('----resiz width----'+width);
console.log('----resiz height----'+height);
// resize the chart

    x0.rangeRoundBands([0, width], .5);
x1.domain(rateNames).rangeRoundBands([0, x0.rangeBand()]);
y.range([height, 0]);

yAxis.ticks(Math.max(height/50, 2));
xAxis.ticks(Math.max(width/50, 2));

d3.select(svg.node().parentNode)
  .style('width', (width + margin.left + margin.right) + 'px');

svg.selectAll('.g')
  .attr("transform",function(d) { 
    return "translate(" + x0(d.category) + ",0)"; 
    });
    svg.selectAll('.g').selectAll("rect").attr("width", x1.rangeBand())
    .attr("x", function(d) { return x1(d.rate); })
svg.selectAll(".legend rect")
  .attr("x", width - 18);
svg.selectAll('.legend text')
    .attr("x", width - 24)  
svg.select('.x.axis').call(xAxis.orient('bottom')); 
}

I made a few changes to the resize function. Here's why:

  1. x0 and x1 ranges (both) have to be reset on resize:
     x0.rangeRoundBands([0,width],.5); x1.domain(rateNames).rangeRoundBands([0,x0.rangeBand()]); y.range([height, 0]);
  2. Translate of (10,0) was being force set in the resize function and you cannot apply width to a (group). Basically, you just need to call all the code from the original render that includes width and height changes. Take a look at the resize function.

  3. Re-rendering X-axis at the bottom included a static value for the x ticks:

svg.select('.x.axis').call(xAxis.orient('bottom')).selectAll("text").attr("x",55);

Just removed the attr("x", 55)

Hope this helps. :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM