简体   繁体   中英

D3 Chart SVG Responsive Bars and Axis

I have created a D3 chart step by step. I have made some changes to make the SVG responsive. My target now is to make the bar graph more responsive in order to make it easier to read when the screen size is smaller (width). I have pasted the snippet below and at the bottom of the page, I focus on the part that I am thinking the solution is hidden.

 var data = [ {"area": "one ", "value": 18000}, {"area": "Two ", "value": 17000}, {"area": "three ", "value": 80000}, {"area": "four ", "value": 55000}, {"area": "five ", "value": 100000}, {"area": "six", "value": 50000}, {"area": "seven", "value": 50000} ]; var margin = {top: 10, right: 10, bottom: 70, left: 30}; var width = 1900 - margin.left - margin.right; var height = 400 - margin.top - margin.bottom; //A fully-responsive chart area var svg = d3.select("#chart-div") .append("svg") .attr("width","100%") .attr("height","500px") .attr("viewBox","0 0 "+ (width+margin.left+margin.right)+ " "+ (height+margin.top+margin.bottom) ) .append("g") .attr("transform","translate("+ margin.left+","+margin.top+")"); var tooltip = d3.select("body").append("div").attr("class", "toolTip"); var x = d3.scaleLinear().range([0, width]); var y = d3.scaleBand().range([height, 0]); var g = svg.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); data.sort(function(a, b) { return a.value - b.value; }); x.domain([0, d3.max(data, function(d) { return d.value; })]); y.domain(data.map(function(d) { return d.area; })).padding(0.1); g.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(d3.axisBottom(x).ticks(5).tickFormat(function(d) { return parseInt(d / 1000); }).tickSizeInner([-height])); g.append("g") .attr("class", "y axis") .call(d3.axisLeft(y)); g.selectAll(".bar") .data(data) .enter().append("rect") .attr("class", "bar") .attr("x", 0) .attr("height", y.bandwidth()) .attr("y", function(d) { return y(d.area); }) .attr("width", function(d) { return x(d.value); }) .on("mousemove", function(d){ tooltip .style("left", d3.event.pageX - 50 + "px") .style("top", d3.event.pageY - 70 + "px") .style("display", "inline-block") .html((d.area) + "<br>" + "£" + (d.value)); }) .on("mouseout", function(d){ tooltip.style("display", "none");}); 
 @import url('https://fonts.googleapis.com/css?family=Roboto'); body { margin: 15px; background-color: #F1F3F3; font-family: 'Roboto'!important; } .bar { fill: #6F257F; } .axis path, .axis line { fill: none; stroke: #D4D8DA; stroke-width: 1px; shape-rendering: crispEdges; } .x path { display: none; } .toolTip { position: absolute; display: none; min-width: 80px; height: auto; background: none repeat scroll 0 0 #ffffff; border: 1px solid #6F257F; padding: 14px; text-align: center; } .svg-container { display: inline-block; position: relative; width: 100%; padding-bottom: 100%; /* aspect ratio */ vertical-align: top; overflow: hidden; } .svg-content-responsive { display: inline-block; position: absolute; top: 10px; left: 0; } 
 <script src="https://d3js.org/d3.v4.min.js"></script> <div id="chart-div" style="width:100%;height:100%;"></div> 

I changed a part of the code with the following:

var parentwidth = $("#chart-div").parent().width(); 
var margin = {top: 10, right: 10, bottom: 70, left: 30};
var width = parentwidth - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;

where I am actually getting the parentwidth using jQuery.

I am actually thinking whether :

a) It is possible to avoid jQuery in this case.

b) Ideally, make the bar to scale differently so that everything will be easily read by the user (small text size is an issue): 在此处输入图片说明

I am testing the function below but I am possibly getting errors related to some chrome addons to avoid the cross-origin error. I can update the question if the below is the best solution:

function resize() {
  var width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right,
  height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom;

  // Update the range of the scale with new width/height
  xScale.range([0, width]);
  yScale.rangeRoundBands([height, 0], 0.1);

  // Update the axis and text with the new scale
  svg.select(".x.axis")
    .call(xAxis)
    .attr("transform", "translate(0," + height + ")")
    .select(".label")
      .attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")");

  svg.select(".y.axis")
    .call(yAxis);

  // Update the tick marks
  xAxis.ticks(Math.max(width/75, 2), " $");

  // Force D3 to recalculate and update the line
  svg.selectAll(".bar")
    .attr("width", function(d) { return xScale(d["total"]); })
    .attr("y", function(d) { return yScale(d["Name"]); })
    .attr("height", yScale.rangeBand());
};

Your question is basically the same as this one .

The TL;DR is: give your <svg> a viewBox attribute and a preserveAspectRatio (eg xMinYMin meet ) attribute. Then wrap the <svg> in a <div> that has position: relative .

This is not the only solution, but it's probably the easiest to implement and (I think) the most used.

For an overview and discussion of several other solutions, see this article by Amelia Bellamy-Royds .

Also, for an in-depth explanation of the SVG coordinate system, read this series of articles by Sara Soueidan .

As for your doubts about jQuery and the text being too small on mobile phones:

a) you can totally avoid jQuery

b) you can avoid having a text too small by "counter-scaling" the text, namely when your entire barchart (ie bars, axis, labels) scales down, your text scales up. This is sometime called sticky text . You can see an example here .

I have altered my script to make it work. Please see the snippet below:

 var margin = {top: 20, right: 20, bottom: 50, left: 100}, width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right, height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom; var yScale = d3.scale.ordinal() .rangeRoundBands([height, 0], 0.1); var xScale = d3.scale.linear() .range([0, width]); var dollarFormatter = d3.format(",.0f") var yAxis = d3.svg.axis() .scale(yScale) .orient("left"); var xAxis = d3.svg.axis() .scale(xScale) .orient("bottom") .tickFormat(function(d) { return "$" + dollarFormatter(d);}); var svg = d3.select("#chart") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var tip = d3.tip() .attr('class', 'd3-tip') .offset([-10, 0]) .html(function(d) { return "<div><span>Name:</span> <span style='color:white'>" + d.Name + "</span></div>" + "<div><span>Sub-Category:</span> <span style='color:white'>" + d["Sub-Category"] + "</span></div>" + "<div><span>Total Sales:</span> <span style='color:white'>" + "$"+ dollarFormatter(d.total) + "</span></div>"; }) svg.call(tip); //Get CSV, JSON from URL //var url = "http://bl.ocks.org/josiahdavis/raw/7d84b2f1837eab9c24d9/top.csv"; var data = [ { "metric": "Sales", "Category": "Furniture", "Sub-Category": "Bookcases", "Name": "Tom Stivers", "total": 1889.8, "Type": "Customer" }, { "metric": "Sales", "Category": "Furniture", "Sub-Category": "Bookcases", "Name": "Keith Herrera", "total": 2020.161, "Type": "Customer" }, { "metric": "Sales", "Category": "Furniture", "Sub-Category": "Bookcases", "Name": "Jack O'Briant", "total": 2122.545, "Type": "Customer" }, { "metric": "Sales", "Category": "Furniture", "Sub-Category": "Bookcases", "Name": "Nora Paige", "total": 2154.9, "Type": "Customer" }, { "metric": "Sales", "Category": "Furniture", "Sub-Category": "Bookcases", "Name": "Anna Gayman", "total": 2396.2656, "Type": "Customer" }, { "metric": "Sales", "Category": "Furniture", "Sub-Category": "Bookcases", "Name": "Tracy Blumstein", "total": 3083.43, "Type": "Customer" }, { "metric": "Sales", "Category": "Furniture", "Sub-Category": "Bookcases", "Name": "Maribeth Schnelling", "total": 3406.664, "Type": "Customer" }, { "metric": "Sales", "Category": "Furniture", "Sub-Category": "Bookcases", "Name": "Greg Tran", "total": 4007.84, "Type": "Customer" }, { "metric": "Sales", "Category": "Furniture", "Sub-Category": "Bookcases", "Name": "Quincy Jones", "total": 4404.9, "Type": "Customer" }, { "metric": "Sales", "Category": "Furniture", "Sub-Category": "Bookcases", "Name": "Peter Fuller", "total": 6232.624, "Type": "Customer" }]; //d3.csv(url, format, function(error, data){ //if (error) throw error; // Filter to select a subset var subset = data.filter(function(el){ return (el["metric"] === "Sales") && (el["Sub-Category"] === "Bookcases") && (el["Type"] === "Customer"); }); // Sort the data so bar chart is sorted in decreasing order subset = subset.sort(function(a, b) { return a["total"] - b["total"]; }); console.log(JSON.stringify(subset, null, 2)); yScale.domain(subset.map(function(d) { return d["Name"]; })); xScale.domain([0, d3.max(subset, function(d) { return d["total"]; })]); svg.append("g") .attr("class", "y axis") .call(yAxis); svg.append("g") .attr("class", "x axis") .call(xAxis) .attr("transform", "translate(0," + height + ")") .append("text") .attr("class", "label") .attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")") .style("text-anchor", "middle") .text("Sales"); svg.selectAll(".bar") .data(subset) .enter().append("rect") .attr("class", "bar") .attr("width", function(d) { return xScale(d["total"]); }) .attr("y", function(d) { return yScale(d["Name"]); }) .attr("height", yScale.rangeBand()) .on('mouseover', tip.show) .on('mouseout', tip.hide);; //}); // Define responsive behavior function resize() { var width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right, height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom; // Update the range of the scale with new width/height xScale.range([0, width]); yScale.rangeRoundBands([height, 0], 0.1); // Update the axis and text with the new scale svg.select(".x.axis") .call(xAxis) .attr("transform", "translate(0," + height + ")") .select(".label") .attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")"); svg.select(".y.axis") .call(yAxis); // Update the tick marks xAxis.ticks(Math.max(width/75, 2), " $"); // Force D3 to recalculate and update the line svg.selectAll(".bar") .attr("width", function(d) { return xScale(d["total"]); }) .attr("y", function(d) { return yScale(d["Name"]); }) .attr("height", yScale.rangeBand()); }; // Call the resize function whenever a resize event occurs d3.select(window).on('resize', resize); // Call the resize function resize(); // Define the format function function format(d) { d.total = +d.total; return d; } 
 @import url('https://fonts.googleapis.com/css?family=Roboto'); body { margin: 5px; background-color: #F1F3F3; font-family: 'Roboto'!important; } .bar { fill: #14405F; } .bar:hover { fill: #33A1EE; } .axis { font: 10px sans-serif; } .axis path, .axis line { fill: none; stroke: #D4D8DA; stroke-width: 1px; shape-rendering: crispEdges; } .x path { display: none; } #chart { width: 100%; height: 100%; position: absolute; } .d3-tip { line-height: 1; font: 14px sans-serif; padding: 12px; background: rgba(0, 0, 0, 0.8); color: rgb(185, 185, 185); border-radius: 2px; } /* .toolTip { position: absolute; display: none; min-width: 80px; height: auto; background: none repeat scroll 0 0 #ffffff; border: 1px solid #6F257F; padding: 14px; text-align: center; } */ 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> <script src="https://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script> <svg id="chart"></svg> 

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