d3 v4 x-axis long labels are half hidden - d3.js

I am working on sample responsive d3 v4 bar chart, here the x-axis labels are bit long so it is not fully visible in the chart. Please check the Fiddle code: http://jsfiddle.net/NayanaDas/w13y5kts/4/
JavaScript code:
// set the dimensions and margins of the graph
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = 550 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// set the ranges
var x = d3.scaleBand()
.range([0, width])
.padding(0.1);
var y = d3.scaleLinear()
.range([height, 0]);
//define tooltip
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([20, 0])
.html(function(d) {
return "<strong>Sales:</strong> <span style='font-weight:normal;color:red'>" + d.sales + "</span>";
});
// append the svg object to the body of the page
// append a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("#container").append("svg")
//.attr("width", width + margin.left + margin.right)
//.attr("height", height + margin.top + margin.bottom)
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 550 300")
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")")
.call(tip);
// Add background color to the chart
svg.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr("class","backbar");
// get the data
//d3.csv("sales.csv", function(error, data) {
// if (error) throw error;
var data = d3.csvParse(d3.select('#data_csv').text());
console.log(data);
// format the data
data.forEach(function(d) {
d.sales = +d.sales;
});
// Scale the range of the data in the domains
x.domain(data.map(function(d) {
return d.name;
}));
y.domain([0, d3.max(data, function(d) {
return d.sales;
})]);
// append the rectangles for the bar chart
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return x(d.name);
})
.attr("width", x.bandwidth())
.attr("y", function(d) {
return y(d.sales);
})
.attr("height", function(d) {
return height - y(d.sales);
})
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
// add the x Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.style("text-anchor", "end")
.style("fill", "#000")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-50)" );
// add the y Axis
svg.append("g")
.call(d3.axisLeft(y));
// add y-axis label
svg.append("text")
.attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor
.attr("transform", "translate("+ (-margin.left/2) +","+(height/2)+")rotate(-90)") // text is drawn off the screen top left, move down and out and rotate
.text("Hours");
//});
$('#expandbtn').click(function (e) {
$("#container").css("height","100%");
$('#box').addClass('panel-fullscreen show');
$('#compressbtn').removeClass("hide").addClass("show");
$('#expandbtn').removeClass("show").addClass("hide");
});
$('#compressbtn').click(function (e) {
$("#container").css("height","480px");
$('#box').removeClass('panel-fullscreen');
$('#expandbtn').removeClass("hide").addClass("show");
$('#compressbtn').removeClass("show").addClass("hide");
});
I have also added two buttons, on clicking expand button the chart will be displayed in full screen mode and on clicking compress button, chart will be back in normal size. Don't know if that has affected the display of x-axis labels. How can I make the long labels view-able?

Change your svg viewBox attribuite to 0 0 550 550.
The first two values are the X and Y coordinates of the upper left corner of the displayed area, the last two are the width and height. viewBox is set only by attribute.
How it works
Also check what is preserveAspectRatio values and how they work

Related

Mouseover event for barchart in D3

I am having trouble creating a mouseOver event for my D3 visualization for a class. I have a bar chart I created and want to make it so when I mouse over each bar, it displays a small div with the actual values of the bar inside. I have created the barchart I want and am trying integrate a section of code from one of our earlier labs in class, where we added this hover functionality to the barchart visualization but I am just not able to get anything to work.
Here is the code for my index.html with a working graph
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<div style ="float:right; padding-right:300px" id="tooltip"></div>
<script>
// set the dimensions and margins of the graph
var margin = {top: 30, right: 30, bottom: 70, left: 60},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
// Parse the Data
d3.csv("Embiid3pt.csv", function(data) {
// X axis
var x = d3.scaleBand()
.range([ 0, width ])
.domain(data.map(function(d) { return d.player; }))
.padding(0.2);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," +
(height + margin.top + 25) + ")")
.style("text-anchor", "middle")
.text("Player Name");
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 0.7])
.range([ height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// text label for the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Three Point Percentage");
// Bars
svg.selectAll("mybar")
.data(data)
.enter()
.append("rect")
.attr("x", function(d) { return x(d.player); })
.attr("y", function(d) { return y(d.percentage); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.percentage); })
.attr("fill", "#69b3a2")
})
</script>
And here is the CSV data I'm loading in:
player,percentage
Joel Embiid,0.377
Bam Adebayo,0.143
Clint Capela,0
Anthony Davis,0.26
Nikola Vucevic,0.339
Deandre Ayton,0.250
Jarrett Allen,0.189
Kristaps Porzingis,0.353
Finally, here is the section of code that we used earlier in the course to give the mouseover event to the bars of the bar chart:
let bars = chart.append('g')
.selectAll("rect")
.data(data)
.join("rect")
.attr("x", function (d) { return x(d.name); } )
.attr("y", function (d) { return y(d.value); } )
.attr("fill", function(d) { return ordinal(d.name) })
.attr("width", x.bandwidth()) //use the bandwidth returned from our X scale
.attr("height", function(d) { return height - y(+d.value); }) //full height - scaled y length
.style("opacity", 0.75)
bars //let's attach an event listener to points (all svg circles)
.on('mouseover', (event,d) => { //when mouse is over point
d3.select(event.currentTarget) //add a stroke to highlighted point
.style("stroke", "black");
d3.select('#tooltip2') // add text inside the tooltip div
.style('display', 'block') //make it visible
.html(`
<h1 class="tooltip-title">${d.name}</h1>
<div>Highway (HWY) MPG: ${d.value}</div>
`);
})
.on('mouseleave', (event) => { //when mouse isnt over point
d3.select('#tooltip2').style('display', 'none'); // hide tooltip
d3.select(event.currentTarget) //remove the stroke from point
.style("stroke", "none");
});
How do I integrate this final section of code into my index.html to get this mouseover event to work? I already created the tooltip div at the top of the index which will display the values once you mouse over.

D3 v5: Line not being drawn for line graph, and ticks not showing up on y-axis

Update, here is error message in console:
Error: attribute d: Expected number, "M0,NaNL21.654801022…".
I am sure this is a fairly simple d3 question, and I have looked at other answers but nothing posted seems to help, the ticks don't show up on the y-axis, but they work perfectly on x-axis. Additionally, this is supposed to be a line graph but the line is not being drawn.
Here is the code:
var margin = {top: 10, right: 40, bottom: 150, left: 70},
width = 760 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var w = width + margin.left + margin.right;
var h = height + margin.top + margin.bottom;
var svg = d3.select("body").append("svg") // this appends a new SVG element to body
.attr("width", w) // set the width
.attr("height", h) // set the height
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x scale will handle time
var xScale = d3.scaleTime().range([0,width]);
// y scale will handle energy consumption values
var yScale = d3.scaleLinear().range([height,0]);
// Define X and Y AXIS
var yAxis = d3.axisLeft(yScale);
var xAxis = d3.axisBottom(xScale);
var parseTime = d3.timeParse("%Y");
function rowConverter(data) {
return {
year : parseTime(data.year),
value : +data.average // the + operator parses strings into numbers
};
}
// line generator function
var line = d3.line()
.curve(d3.curveBasis)
.x(function(d) { return xScale(d.year); })
.y(function(d) { return yScale(d.average); })
d3.csv("moreDummyData.csv",rowConverter).then(function(data){
yScale.domain([0,d3.max(data, function(d) {return d.average; })]);
xScale.domain(d3.extent(data, function(d) { return d.year; }));
// Draw xAxis
svg.append("g") // add a new svg group element
.attr("class", "x axis")
.attr("transform", "translate(0, " + height + ")")
.call(xAxis)
.selectAll("text")
.attr("dx", "-.8em")
.attr("dy", ".25em")
.attr("text-anchor", "end");
// Draw yAxis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll("text")
.attr("dx", "-.8em")
.attr("dy", ".25em")
.attr("text-anchor", "end");
// add a title for the yAxis
svg.append("text") // add a new svg "text" element
.attr("text-anchor", "end")
.attr("transform", "rotate(-90)") // turn it on its side
// position the title in space
.attr("y", -margin.left+20)
.attr("x", -margin.top-75)
// give it text and style
.text("ADD TITLE")
.attr("font-family", "Times")
.attr("font-size", "16px");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
});
And this is what the output looks like with this code:
code output
Here's what the data looks like:
year,average
1971,30
1972,34
1973,29
1974,28
1975,31
1976,35

vertical bars not aligned with the x-axis label and last bar rendered outside the x-axis range - D3 js - simple bar with fixed width

I'm having challenges in setting the bar (fixed width) position aligned correctly width the x-axis label.
The bars and the x-ticks are not aligned correctly and also the last bar is rendered after the max xscale range.
Appreciate any help in fixing this issue.
Please check the sample here - https://jsfiddle.net/sjselvan/wsy5frh2/29/ - updated and fixed version
function generateChart(){
const data = [{
label: 100,
value: 10
},
{
label: 200,
value: 20
},
{
label: 300,
value: 30
},
{
label: 400,
value: 40
},
{
label: 500,
value: 50
}];
let margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
const xScale = d3.scaleLinear().domain([0,500]).range([0,width]);
const yScale = d3.scaleLinear().domain([0,50]).range([height,0]);
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale);
console.log(d3.select('#chart'));
let svg = d3.select('#chart')
.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 + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" );
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value ($)");
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", "steelblue")
.attr("x", function(d) { return xScale(d.label); })
.attr("width", 15)
.attr("y", function(d) { return yScale(d.value); })
.attr("height", function(d) { return height - yScale(d.value); });
}
generateChart();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="chart"></div>
You're setting the x value of the bars, which sets the left-hand edge of the bars, using the same scale as the x axis. So, it makes sense that the left-hand edge of the bar representing 100 is lined up with the 100 tick in the axis.
In order to line up the bars, you need to move them to the left by half of their width. You would need to make the bar width an even number so that the bars fit nicely.
const barWidth = 16;
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", "steelblue")
.attr("x", function(d) { return xScale(d.label) - (barWidth / 2); })
.attr("width", barWidth)
.attr("y", function(d) { return yScale(d.value); })
.attr("height", function(d) { return height - yScale(d.value); });
However, I would say that it doesn't really make sense for this chart to be a bar chart. Bars usually represent nominal (Banana, Apple, Pear), or ordinal values. Whereas your chart seems to more suit a line, or a scattergraph.
But if you do mean to use the numbers as labels, you will be better off using a band scale which will line up the bars above the labels nicely.

Reading tsv file with a filter

I am trying to plot a simple bar graph from a tsv file and would appreciate some help.
First of all, here is a sample input file:
ind count
0 1228
1 0
2 238
3 9
I need to plot number of records (y-axis) vs. "count" (x-axis).
The user has the option via the HTML page to decide at what "count" value to
suppress the data. For example, suppress all records where count=0. Changing the number of records and with that the range of data affects the scales of both axes.
I have no problem getting all the data and setting up the scales for the axes. I do that as the first step right after reading in the data from the file (d3.tsv...). But how do I do that when I only want to use partial data of the input file? I use
d3.max(data, function(d){ return +d.indx}
to get the number of records in my input file.
I did play with the filter() method a bit but (besides being unsuccessful so far) I still think that the right place to filter data out would be right away - when reading the input file.
Chances are this is so obvious that the answer will make me blush but
my brain has given up. With that, thank you for responding!
And now, here is some of my code:
function drawBarGraph( minCountNum){
d3.tsv("../../data/test.tsv", function( error, data){ //get the number of rows and the largest number of the "total" column for scaling purposes
//y-axis: use number of lines in input file
var bar_num = d3.max(data, function(d) { return +d.indx;} ); //use plus sign (+) in front of a variable to ensure conversion to a numeric value.
//x-axis: use largest value of "total reads" from input file;
var data_max = d3.max(data, function(d) { return +d.total;} );
//set up canvas size
var margin = { top: 30, right: 20, bottom: 40, left: 50},
width = 800 - margin.left - margin.right,
height = 2 * bar_num + 2; //each bar is 1px high and spaced by 1px; add 2px to the bottom to allow a little space between the xAxis and the first data point
var bar_height = 1; //yes, draw the bars 1px high
//set up the x and y scales
var xScale = d3.scale.linear()
.domain([0, data_max]) //define original range with min and max
.range([0, width]); //define what resulting range should be
var yScale = d3.scale.linear()
.domain([0, bar_num*2])
.range([0, height]);
//set up the x and y axes
var xAxis = d3.svg.axis()
.scale( xScale)
.orient("bottom")
//.ticks( 5); //let d3 decide on the number of ticks used or calculate based on longest x-value
var yAxis = d3.svg.axis()
.scale( yScale)
.orient("left")
.ticks( 0); //no ticks or make them the index or node id's
var canvas = d3.select("body")
.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 div = d3.select("body").append("div")
.attr("class", "tooltip") //add the CSS tooltip
.style("opacity", 0);
canvas.selectAll("rect")
.data(data)
.enter()
//.filter(function(d) { return +d.total > 0; }) //this returns 'undefined'
.append("svg:a")
.attr("xlink:show", "new")
.attr("xlink:href", function(d){return ncbi_url + d.taxon_id;})
.append("rect")
.attr("width", function(d) { return xScale(d.total);})
.attr("height", bar_height)
.attr("y", function(d,i){ return i * (bar_height + 1);})
.attr("fill", function(d) { return d.color;})
.on("mouseover", function(d){
div.transition()
.duration(200)
.style("opacity", .9);
div.html(
"Node ID: " + d.id + "<br/>Tot. Reads: " + d.total)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d){
div.transition()
.duration(500)
.style("opacity", 0);
});
canvas.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
canvas.append("text") //add the x-axis label
.attr("x", (width / 2))
.attr("y", height + margin.bottom)
.style("text-anchor", "middle")
.text("Total Reads");
canvas.append("g")
.call(yAxis);
canvas.append("text") //add the y-axis label
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left + 20)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Node #");
canvas.append("text") //add a title
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "20px")
.style("text-decoration", "underline")
.text("Node Plot");
})
}
Yes, the best way to do this would be right after getting the data to make sure that all subsequent operations consider only the subset that you want. You can use the .filter() function for this.
var threshold = ...;
var newdata = data.filter(function(d) { return +d.total > threshold; });
(I've taken total as the field to filter on like in the code you've posted).

Maximum width for column in bar chart

I'm looking for a way of limiting the column width in a chart, I'm sure this ought to be relatively easy but I cant find a way of doing it.
I'm populating a chart from some dynamic data, where the number of columns can vary quite dramatically - between 1 and 20.
e.g: sample of csv
Location,Col1
"Your house",20
Location,Col1,Col2,Col3,Col4,Col5
"My House",12,5,23,1,5
This is working fine, and the col widths are dynamic, however when there is only one column in the data, I end up with one bar of width 756 (the whole chart), and I dont like the way this looks.
What I'd like to do is only ever have a maximum column of width 100px irrespective of the number of columns of data.
Below is my script for the chart
Many thanks,
<script>
var margin = {
top : 40,
right : 80,
bottom : 80,
left : 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear().range([ 0, width ]);
var y = d3.scale.linear().range([ height, 0 ]);
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .05);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var chart = d3.select("body").append("svg")
.attr("class","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 legendChart = d3.select("body").append("svg")
.attr("class","chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("/sampledata.csv.txt", function(error, data) {
// Use the first row of csv for header names
var reasonNames = d3.keys(data[0]).filter(function(key) {
return key !== "Location";
});
//console.log(reasonNames);
data.forEach(function(d) {
d.reasons = reasonNames.map(function(name) {
return {
name : name,
value : +d[name]
};
});
//console.log(d.reasons);
});
x0.domain(data.map(function(d) {return d.Location; }));
x1.domain(reasonNames).rangeRoundBands([0, x0.rangeBand()]);
console.log(x0.rangeBand());
y.domain([0, d3.max(data, function(d) { return d3.max(d.reasons, function(d) { return d.value; }); })]);
var maxVal = d3.max(data, function(d) { return d3.max(d.reasons, function(d) { return d.value; }); });
//console.log(maxVal);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
//.tickFormat(d3.format(".2s"));
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.call(yAxis);
var location = chart.selectAll(".name")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.Location) + ",0)"; });
location.selectAll("rect")
.data(function(d) { return d.reasons; })
.enter().append("rect")
.attr("width", x1.rangeBand()-2)
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d,i) { return "#"+3+"9"+i; /*color(d.name);*/ });
chart.selectAll("text")
.data(data)
.enter().append("text")
.attr("x", function(d) { return x1(d.name)+ x.rangeBand() / 2; })
.attr("y", function(d) { return y(d.value); })
.attr("dx", -3) // padding-right
.attr("dy", ".35em") // vertical-align: middle
.attr("text-anchor", "end") // text-align: right
.text("String");
var legend = legendChart.selectAll(".legend")
.data(reasonNames.slice().reverse())
.enter()
.append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")";
});
legend.append("rect")
//.attr("x", width - 18)
.attr("x", 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i) {/*console.log(i);*/return "#" + 3 + "9" + i;
});
legend.append("text")
//.attr("x", width - 24)
.attr("x", 48)
.attr("y", 9).attr("dy",".35em")
//.style("text-anchor", "end")
//.text(function(d,i) { return String.fromCharCode((65+i))+i; });
.text(function(d) { return d; });
});
</script>
The easiest way to achieve this is by changing the line
.attr("width", x1.rangeBand()-2)
to
.attr("width", Math.min(x1.rangeBand()-2, 100))
You might also want to adjust the starting position and/or padding.
Code for adjusting starting position if anyone is stuck on it:
.attr("x", function(d, i) { return x1(d.seriesName) + (x1.rangeBand() - 100)/2 ;})
P.S. : referring answer from Lars.
Setting an absolute maximum width for the columns doesn't allow proper rendering for different screen resolutions, div sizes, etc.
In my case, I just wanted the columns not to look so large when the number of columns itself is small
I found it easier and more straight-forward to play with the scale definition, by changing the maximum width (where all columns will fit), their inner and outer paddings.
var w = 600
// var w = parseInt(d3.select(parentID).style('width'), 10) // retrieve the div width dynamically
var inner_padding = 0.1
var outer_padding = 0.8
var xScale = d3.scale.ordinal().rangeRoundBands([0, w], inner_padding, outer_padding)
When rendering the plot, I just ran a switch/if-else statement, which assigns different padding values. The lower the number of columns to plot, the greater the outer_padding (and eventually inner-padding) values I use.
This way, I keep the plots responsive.
I am able to change the width of the bar using the above answer. But unfortunately, my X Axis labels are not aligned when there is a single bar in the chart and it uses the max width set.
var tradeGroup = svg.selectAll("g.trade")
.data(trades)
.enter()
.append("svg:g")
.attr("class", "trade")
.style("fill", function (d, i) {
return self.color(self.color.domain()[i]);
})
.style("stroke", function (d, i) {
return d3.rgb(self.color(self.color.domain()[i])).darker();
});
var aWidth = Math.min.apply(null, [x.rangeBand(), 100]);
// Add a rect for each date.
var rect = tradeGroup.selectAll("rect")
.data(Object)
.enter()
.append("svg:rect")
.attr("x", function (d) {
return x(d.x);
})
.attr("y", function (d) { return y( (d.y || 0) + (d.y0 || 0)); })
.attr("height", function (d) { return y(d.y0 || 0) - y((d.y || 0) + (d.y0 || 0)); })
.attr("width", Math.min.apply(null, [x.rangeBand(), 100]));
For completeness the full answer would look like this:
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", (d) -> x1(d.name) + (x1.rangeBand() - d3.min([x1.rangeBand(), 100]))/2)
.attr("width", d3.min([x1.rangeBand(), 100]))
.attr("y", (d) -> y(d.grade) )
.attr("height", (d)-> height - y(d.value) )
(coffeescript syntax)
Note this include the full answer, the 'width' and the 'x' settings. Also 'x' settings is accounting for a when 100 width is not the min value.
Thought I'd share that I came up with a slightly different answer to this. I didn't want to hard code in a maximum bar width because 1) it wasn't responsive to different screen sizes and 2) it also required playing with the x-coordinate attribute or accepting some irregular spacing.
Instead, I just set a minimum number of bars, based on the point where the bars became too wide (in my case, I found that less than 12 bars made my chart look weird). I then adjusted the scaleBand's range attribute, proportionately, if there were less than that number of bars. So, for example, if the minimum was set to 12 and there were only 5 items in the data, rather than rendering each of them at 1/5th of the full width, I scaled the range down to 5/12ths of the original width.
Basically, something like this:
// x is a scaleBand() that was previously defined, and this would run on update
var minBarSlots = 12;
if (data.length < minBarSlots) {
x.range([0, width*(data.length/minBarSlots)])
}
else {
x.range([0, width])
}`

Resources