d3.js - chain box with arrow lines - d3.js

I would like to draw few text box and chain it with arrow lines. I use below code to draw the text box few issues there:
text box is black and no text show there.
One box is missing, it should be 5 box but only 4 can be seen.
how can I add a arrow line to connect each other!
test()
function test() {
var data = ["a","b","c","d","e"]
width = 800
height = 600
margin = 10
//var svg = d3.select("svg");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom);
svg.style("border","5px solid red");
svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var group = svg.selectAll('g')
.data(data).enter()
.append('g')
.attr('transform',function(d,i) {
//console.log(i,d);
return 'translate('+(100*i)+',0)';
});
var box = group.selectAll('rect')
.data(function(d) {
return d;
});
box.enter()
.append("rect")
.attr("width", 30)
.attr("height", 30)
.attr('font-size',2)
.attr("x", function(d, i) {
//console.log(d);
return 60 + 2*d;
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

text box is black and no text shown.
You aren't appending any text. Text also can't be appended to a rectangle, so there is no need to apply font properties to a rectangle. Text can be appended to a g though. So we can use a parent g to hold both rectangle and text. Something like:
group.append("rect")...
group.append("text")...
The boxes are black because you haven't applied a fill. The default fill is black.
One box is missing, it should be 5 box but only 4 can be seen.
This is because when you enter the parent g elements, you select all g elements. This includes the one you've already appended (svg.append("g")). The enter selection is intended to create elements such that every item in the data array is paired with an element in the DOM. Since you already have a g in your selection, the enter selection will only create 4 new ones (representing data array items with indexes 1-4 but not 0).
Instead of selectAll("g") you could specify a class name or, in the event you simply want to enter everything and there isn't a need to ever update a selection: selectAll(null). The latter option will always return an empty selection, which will result in the enter selection containing one element per item in the data array.
Note, that the parent's datum is passed to appended children automagically, there is no need to use the .data method to pass this onward unless you are handling nested data.
Here's a snippet addressing issues in one and two:
test()
function test() {
var data = ["a","b","c","d","e"]
width = 800
height = 600
margin = 10
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.style("border","5px solid red");
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var group = svg.selectAll(null)
.data(data).enter()
.append('g')
.attr('transform',function(d,i) {
return 'translate('+(40*i)+',0)';
});
group
.append("rect")
.attr("width", 30)
.attr("height", 30)
.attr("fill","yellow")
group.append("text")
.text(function(d) { return d; })
.attr("y", 20)
.attr("x", 15)
.attr("text-anchor","middle");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
I also changed svg to refer to the parent g, the one with the margin applied. Before the g with the margin remained unused, along with the margin. I also modified the spacing to keep everything in view.
how can I add a arrow line to connect each other!
This can be done in many ways and really is a separate issue from the others, so I'll only quickly demonstrate one of many options. I'll modify your data structure a bit so that each datum has positional data and then add arrows using SVG markers:
test()
function test() {
var data = [{name:"a"},{name:"b"},{name:"c"},{name:"d"},{name:"e"}]
width = 800
height = 600
margin = 10
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.style("border","5px solid red")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs")
.append("marker")
.attr("id","pointer")
.attr("markerWidth", 10)
.attr("markerHeight", 10)
.attr("orient","auto")
.attr("refY", 5)
.append("path")
.attr("d", "M 0 0 L 10 5 L 0 10 z")
var group = svg.selectAll(null)
.data(data).enter()
.append('g')
.attr('transform',function(d,i) {
d.x = 40*i+15, d.y=30;
return 'translate('+(40*i)+',0)';
});
group
.append("rect")
.attr("width", 30)
.attr("height", 30)
.attr("fill","yellow")
group.append("text")
.text(function(d) { return d.name; })
.attr("y", 20)
.attr("x", 15)
.attr("text-anchor","middle");
links = [
{source: data[0], target: data[1]},
{source: data[0], target: data[2]}
]
svg.selectAll(null)
.data(links)
.enter()
.append("path")
.attr("d", function(d) {
var midX = (d.source.x+d.target.x)/2;
return "M"+d.source.x+" "+d.source.y+"Q"+midX+" "+200+" "+d.target.x+" "+(d.target.y+6);
})
.attr("fill","none")
.attr("stroke","black")
.attr("stroke-width",1)
.attr("marker-end","url(#pointer)");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

Related

d3 v4 x-axis long labels are half hidden

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

D3 Scatterplot legend overlapping

I have a scatterplot that works fine, but the legend I add to it is overlapping the chart. My current approach is to make the chart DIV be 70% of the width and have the legend take up the remaining 30%. For some reason, the legend isn't showing up on the screen, even though the HTML is there.
This is the link to my initial problem: http://jsfiddle.net/chp5a09e/373/
Here is the link to what I'm currently trying: http://jsfiddle.net/chp5a09e/372/
var legend = d3.select("#legend").append("svg")
.attr("width", $("#legend").width())
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
legend.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
legend.append("rect")
.attr("x", width - 12)
.attr("width", 12)
.attr("height", 12)
.style("fill", function(d) {
return color(d);
})
.on("click", function(d) {
d3.selectAll(".symbol").style("opacity", 1)
if (clicked !== d) {
d3.selectAll(".symbol")
.filter(function(e) {
return e.items[columns.indexOf("Channel")] !== d;
})
.style("opacity", 0.1)
clicked = d
} else {
clicked = ""
}
});
legend.append("text")
.attr("x", width - 16)
.attr("y", 6)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});
HTML is there
Only group (g) elements are there, and they are never visible themselves. In your original code
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
...
legend.append("rect")
the legend here is a selection of multiple g.legend elements, and thus a rect gets appended to each of them, as well as gets access to the datum bound to the parent g. However in you new code
var legend = d3.select("#legend").append("svg")
...
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
legend.selectAll(".legend")
.data(color.domain())
.enter().append("g")
...;
legend.append("rect")
The legend here refers only to the single g element that contains your whole legend. Your legend.selectAll(".legend") isn't saved into variable, so while inside the chain you set class and transform attributes properly, you don't use it to get rect appended to it -- again, legend at that point refers to the outter single g container.
Potential solution:
var legendCnt = d3.select("#legend").append("svg")
.attr("width", $("#legend").width())
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var legend = legendCnt.selectAll(".legend")
.data(color.domain())
...
You'll notice you'll need to remove or lower x attribute for text and rect, since 0 is now at the beginning of the legend instead of chart

d3.js - place text below the circle

I am displaying different radius circles with different color.
I am trying to place the text(radius value) below each circle but not getting displayed though i could see the text in the browser inspector.
following is the code:
var width=960,height=500;
var margin = {top: 29.5, right: 29.5, bottom: 29.5, left: 59.5};
radiusScale = d3.scale.sqrt().domain([1, 100]).range([10, 39]),
colorScale = d3.scale.category10();
// Create the SVG container and set the origin.
var 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 + ")");
var i =0;
while (i<=50)
{
console.log("i value is " + i + " value of scale " +i+ " is " + radiusScale(i));
var circle = svg.append("g").append("circle")
.attr("id","circle" + i)
.attr("cx", i*12 )
.attr("cy", 20)
.attr("fill",colorScale(i))
.attr("r", radiusScale(i))
.append("text").attr("dx",i*12).text(function(d){return radiusScale(i)});
i=i+10;
}
should i be adding the text in svg instead of circle to display below the corresponding circles.
SVG will not display text appended to circle elements. You append to the g element:
var g = svg.append("g");
g.append("circle")
.attr("id","circle" + i)
.attr("cx", i*12 )
.attr("cy", 20)
.attr("fill",colorScale(i))
.attr("r", radiusScale(i));
g.append("text").attr("dx",i*12).text(function(d){return radiusScale(i)});
Also note that your function(d) in .text() isn't necessary, you can do just
g.append("text").attr("dx",i*12).text(radiusScale(i));

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