D3: Resize bar chart according to window width

和自甴很熟 提交于 2019-12-08 12:38:44

问题


I have a D3 bar chart and I'm trying to resize its width according to the browser window size. Here is the chart script where the width variable is being set according to the SVG's parent div's width (#chart1), which has a percentage width set in the css:

var $chartWidth= $('#chart1').width();
var margin = {top:20, right:35, bottom:30, left:35};
var height = 180 - margin.top - margin.bottom;
var width = $chartWidth - margin.left - margin.right;
var parseDate = d3.time.format("%m/%Y").parse;

var yScaleBar = d3.scale.linear()
    .domain([0,100])
    .range([height, 0]);    

var xBarScale = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1);   

var yAxisBar = d3.svg.axis()
    .scale(yScaleBar)
    .ticks(4)
    .tickSize(-width, 0, 0)
    .orient("left");        

var xBarAxis = d3.svg.axis()
    .scale(xBarScale)
    .ticks(4)
    .orient("bottom");

var canvasBars = d3.select("#chart1").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)    
    .append("g")
    .attr("transform", "translate("+margin.left+","+margin.top+")");

var data = [
    {date:"01/2009",bar2:"50",bar:"10",q:"1Q 2009"},
    {date:"04/2009",bar2:"56",bar:"32",q:"2Q"},
    {date:"07/2009",bar2:"57",bar:"70",q:"3Q"},
    {date:"10/2009",bar2:"58",bar:"60",q:"4Q"},
    {date:"01/2010",bar2:"52",bar:"45",q:"1Q '10"}
];

  data.forEach(function(d) {
    d.date = parseDate(d.date);
    d.bar2 = +d.bar2;
    d.bar = +d.bar;
  });

xBarScale.domain(d3.range(data.length));

xBarAxis.tickValues([data[0].q,data[1].q,data[2].q,data[3].q,data[4].q]);  

canvasBars.append("g")
      .attr("class", "xaxis")
      .attr("transform", "translate(0," + height + ")")
      .call(xBarAxis);            


canvasBars.append("g")
      .attr("class", "yaxis")
      .call(yAxisBar); 


canvasBars.selectAll("rect")
                .data(data)
                .enter()
                .append("rect")
                .attr({
                "x": function(d) {return xBarScale(d.date);},  
                "y": function(d) {return yScaleBar(d.bar);},  
                "height": function(d) {return height -yScaleBar(d.bar);},
                "width": xBarScale.rangeBand(),
                "fill": "steelblue"
                }); 

When the window resizes, the width variable changes and calls resize();

$( window ).resize(function() {
  $chartWidth = $('#chart1').width();
  width = $chartWidth - margin.left - margin.right;
  resize();
});

Then my resize function should change the width attribute of the SVG, the width of the yAxisBar tick marks, and the xBarScale rangeRoundBands..... I then think I need to select all the bars (rects) and change their width attributes according the to the new xBarScale.rangeRoundBands. but I'm not sure how to correctly select these 3 things. So far, just the bar width's resize along with the window - but not the tick marks or the SVG. Here is what I've tried:

function resize(){
    canvasBars.attr("width", width + margin.left + margin.right);
        yAxisBar.tickSize(-width, 0, 0);
        xBarScale.rangeRoundBands([0, width], .1);
        canvasBars.selectAll("rect").attr("width", xBarScale.rangeBand());
}

回答1:


The most straight forward way of doing it would be to just set the width of your element to 100% in the css.




回答2:


There is a much better way to do this with the Bootstrap 3 framework. I'm already implementing that for a website so I can show you how I do it for me and maybe you can adapt it to your specific needs.

HTML code

<div class="container>
<div class="row">

<div class='col-sm-6 col-md-4' id="month-view" style="height:345px;">
<div id ="responsivetext">Something to write</div>
</div>

</div>
</div>

I have set up a fixed height because of my needs, but you can leave the size auto as well. The "col-sm-6 col-md-4" makes the div responsive. You can learn more at http://getbootstrap.com/css/#grid-example-basic

We can access the graph with the help of the id month-view which can be seen below in the following code implemented with d3.js.

doing the magic with d3:

    var visualize = function(data){

    // Initialize and select div, get the width and height that you want to use for the bar
    var width = document.getElementById('month-view').offsetWidth;

    var height = document.getElementById('month-view').offsetHeight - document.getElementById('responsivetext2').offsetHeight;



    var total_map = data.map(function (i){ return parseInt(i.total);});


    var heightScale = d3.scale.linear()
                   .domain([0, d3.max(total_map)])
                   .range([0, height-5]);


    var x = d3.scale.ordinal()
          .rangeRoundBands([0, width-30], .1);

    var canvas = d3.select("#month-view").append("svg")
        .attr("width", width - 13)
        .attr("height", height + 20)
        .append("g")
        .attr("transform","translate(5,-20)");

    var div = d3.select("body").append("div")
                      .attr("class", "tooltip")
                      .style("opacity", 0);

    // Add first bar (total)                  
    canvas.selectAll(".bar2")
        .data(data)
        .enter()
        .append('rect')
        .attr('width',  width/12 - 5)
        .attr('height', function (d){return heightScale(d.total)})
        .attr('x', function (d, i) {return i * (width/12 -3 )})
        .attr('y', function (d) {return height + 10 - heightScale(d.total)})
        .attr('class', 'bar2')
        .on("mouseover", function(d) {
          div.transition()
              .duration(200)
              .style("opacity", .95);
          div.html(monthNames(d.month) + "<br/>" + d.total.toFixed(0)+" kr total<br/> <span class='green'>"+ ( (d.total * d.eco)/100  ).toFixed(0) + " kr eko</span>")
              .style("left", (d3.event.pageX - 45) + "px")
              .style("top", (d3.event.pageY - 55) + "px");
          })
        .on("mouseout", function(d) {
            div.transition()
                .duration(500)
                .style("opacity", 0);
        });

    // Add second bar (money)
    canvas.selectAll(".bar")
        .data(data)
        .enter()
        .append('rect')
        .attr('width',  width/12 - 5)
        .attr('height', function (d){ return heightScale(( (d.total * d.eco)/100).toFixed(0))})
        .attr('x', function (d, i) {return i * (width/12 -3 )})
        .attr('y', function (d) {return height + 10 - heightScale(( (d.total * d.eco)/100).toFixed(0) )})
        .attr('class', 'bar')
        .on("mouseover", function(d) {
          div.transition()
              .duration(200)
              .style("opacity", .95);
          div.html(monthNames(d.month) + "<br/>"  + d.total.toFixed(0)+" kr total<br/> <span class='green'>"+ ( (d.total * d.eco)/100).toFixed(0) + " kr eko</span>")
              .style("left", (d3.event.pageX - 45) + "px")
              .style("top", (d3.event.pageY - 55) + "px");
          })
        .on("mouseout", function(d) {
            div.transition()
                .duration(500)
                .style("opacity", 0);
        });

    canvas.append("g")
      .attr("transform", "translate(0,0)");


    var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom");

    x.domain(data.map(function(d) { return monthNames(d.month).substring(0, 3); }));


    }

Since you already are using d3, I won't spend the time to explain everything that I've used in this code, but it's a working code at least. What you need is the part:

    var width = document.getElementById('month-view').offsetWidth;

    var height = document.getElementById('month-view').offsetHeight - document.getElementById('responsivetext2').offsetHeight;

The width is set by getting the width of the div with the id month-view.

The height in my case should not include the entire area. I also have some text above the bar so I need to calculate that area as well. That's why I identified the area of the text with the id responsivetext. For calculating the allowed height of the bar, I subtracted the height of the text from the height of the div.

This allows you to have a bar that will adopt all the different screen/div sizes.




回答3:


There is a much easier way of doing this in D3 You can make the chart resize using a combination of viewBox and preserveAspectRatio attributes on the SVG element.

  const svg = d3
  .select('div#chart-container')
  .append('svg')
  .style(
    'padding',
    marginRatio.top +
      ' ' +
      marginRatio.right +
      ' ' +
      marginRatio.bottom +
      ' ' +
      marginRatio.left +
      ' '
  )
  .attr('preserveAspectRatio', 'xMinYMin meet')
  .attr(
    'viewBox',
    '0 0 ' +
      (width + margin.left + margin.right) +
      ' ' +
      (height + margin.top + margin.bottom)
  )

See here for reference: https://eddyerburgh.me/create-responsive-bar-chart-d3-js



来源:https://stackoverflow.com/questions/23526856/d3-resize-bar-chart-according-to-window-width

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!