d3js gridlines between bars for bar charts - d3.js

I created a simple d3js vertical bar chart. I understand that typically, to create gridlines, I would use the "tickSize" call, and give them a height or width similar to that of the axis.
var yGridLine = d3.svg.axis()
.scale(yScale)
.tickSize(-width, 0 ,0)
.tickFormat("")
.orient("left");
The above code would create horizontal gridlines. So if I want to create vertical gridlines, then I would modify the orientation and reference axix (something similar to the following untested yet hypothetically correct code)
var xGridLine = d3.svg.axis()
.scale(xScale)
.tickSize(-height, 0 ,0)
.tickFormat("");
Now, the problem is, when using this approach, then the vertical gridlines are created in the middle of the vertical bars (or in the case of horizontal bar charts, then horizontal gridline are created in the middle of the horizontal bars), which is not visually pleasant and not according to requirements. What I want is to have the vertical gridlines appear between the vertical bars (i.e., at the centre points between the ticks). How do I do that?
Example
In this link, you will find a number of charts, where the vertical gridlines are between the ticks, not at the centre. This is what I like to achieve, and my question is how do I achieve that?
Thanks.
The whole code:
var data = [{
name: "Hemant",
age: 20
}, {
name: "Vinay",
age: 55
}, {
name: "Vikas",
age: 56
}, {
name: "Arun",
age: 88
}, {
name: "Varun",
age: 34
}, {
name: "Ajay",
age: 77
}],
w = 600,
h = 300,
margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = w - margin.left - margin.right,
height = h - margin.top - margin.bottom;
var mySvg = d3.select("body").append("svg").attr({
width: w,
height: h
}).append("g")
.attr("transform", 'translate(' + margin.left + ',' + margin.top + ')');
var xScale = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.name;
}))
.rangeBands([0, width]);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function(d) {
return d.age;
})])
.range([height, 0]);
var linearColorScale = d3.scale.linear()
.domain([0, data.length])
.range(["#e74c3c", "#8e44ad"]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
var yGridLine = d3.svg.axis()
.scale(yScale)
.tickSize(-width, 0, 0)
.tickFormat("")
.orient("left");
var ordinalColorScale = d3.scale.category20();
mySvg.append("g")
.classed("gridLine", true)
.attr("transform", "translate(0,0)")
.call(yGridLine);
mySvg.selectAll("rect").data(data).enter()
.append("rect")
.attr("x", function(d) {
return xScale(d.name);
})
.attr("y", function(d, i) {
return yScale(d.age);
})
.attr("width", function(d) {
return xScale.rangeBand();
})
.attr("height", function(d) {
return height - yScale(d.age)
})
.style("fill", function(d, i) {
return ordinalColorScale(i);
})
mySvg.selectAll("text").data(data)
.enter()
.append("text")
.classed("bar", true)
.attr("x", function(d) {
return xScale(d.name) + xScale.rangeBand() / 2;
})
.attr("dx", 0)
.attr("y", function(d, i) {
return yScale(d.age);
})
.attr("dy", -6)
.text(function(d, i) {
return d.age;
});
mySvg.append("g")
.classed("axis", true)
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
mySvg.append("g")
.classed("axis", true)
.attr("transform", "translate(0,0)")
.call(yAxis);
svg {
border: 1px solid #ccc;
}
svg rect {
shape-rendering: crispedges;
}
.bar {
fill: #000;
text-anchor: middle;
font-size: 20px;
}
.axis path,
.axis line {
fill: none;
shape-rendering: crispedges;
stroke: #666;
}
.gridLine path,
.gridLine line {
fill: none;
shape-rendering: crispedges;
stroke: #e4e4e4;
}
<script src="http://d3js.org/d3.v3.min.js"></script>

You could achieve this by simply translating .gridLines by xScale.rangeBand() / 2 - i.e. half the width of a bar.
var data = [{
name: "Hemant",
age: 20
}, {
name: "Vinay",
age: 55
}, {
name: "Vikas",
age: 56
}, {
name: "Arun",
age: 88
}, {
name: "Varun",
age: 34
}, {
name: "Ajay",
age: 77
}],
w = 600,
h = 300,
margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = w - margin.left - margin.right,
height = h - margin.top - margin.bottom;
var mySvg = d3.select("body").append("svg").attr({
width: w,
height: h
}).append("g")
.attr("transform", 'translate(' + margin.left + ',' + margin.top + ')');
var xScale = d3.scale.ordinal()
.domain(data.map(function(d) {
return d.name;
}))
.rangeBands([0, width]);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function(d) {
return d.age;
})])
.range([height, 0]);
var linearColorScale = d3.scale.linear()
.domain([0, data.length])
.range(["#e74c3c", "#8e44ad"]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
var yGridLine = d3.svg.axis()
.scale(xScale)
.tickSize(-height, 0, 0)
.tickFormat("");
var ordinalColorScale = d3.scale.category20();
mySvg.append("g")
.classed("gridLine", true)
.attr("transform", "translate(" + [xScale.rangeBand() / 2, height] + ")")
.call(yGridLine);
mySvg.selectAll("rect").data(data).enter()
.append("rect")
.attr("x", function(d) {
return xScale(d.name);
})
.attr("y", function(d, i) {
return yScale(d.age);
})
.attr("width", function(d) {
return xScale.rangeBand();
})
.attr("height", function(d) {
return height - yScale(d.age)
})
.style("fill", function(d, i) {
return ordinalColorScale(i);
})
mySvg.selectAll("text").data(data)
.enter()
.append("text")
.classed("bar", true)
.attr("x", function(d) {
return xScale(d.name) + xScale.rangeBand() / 2;
})
.attr("dx", 0)
.attr("y", function(d, i) {
return yScale(d.age);
})
.attr("dy", -6)
.text(function(d, i) {
return d.age;
});
mySvg.append("g")
.classed("axis", true)
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
mySvg.append("g")
.classed("axis", true)
.attr("transform", "translate(0,0)")
.call(yAxis);
svg {
border: 1px solid #ccc;
}
svg rect {
shape-rendering: crispedges;
}
.bar {
fill: #000;
text-anchor: middle;
font-size: 20px;
}
.axis path,
.axis line {
fill: none;
shape-rendering: crispedges;
stroke: #666;
}
.gridLine path,
.gridLine line {
fill: none;
shape-rendering: crispedges;
stroke: #e4e4e4;
}
<script src="http://d3js.org/d3.v3.min.js"></script>

Related

Wrapping long labels with axis transition in D3.js v4

This is the original example: https://bl.ocks.org/mbostock/7555321.
I added transition to the x-axis and made two fiddles to demonstrate the fact that wrapping long labels on an axis with transition works in version 3 but doesn't work in version 4 of D3.
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.transition()
.call(xAxis)
.selectAll(".tick text")
// .call(wrap, x.rangeBand()); (v3)
.call(wrap, x.bandwidth());
https://jsfiddle.net/cfwbh0st/
https://jsfiddle.net/Ls5ux9gv/
How do I go about this? Thanks.
I was in the middle of rewriting your logic when I found out the root cause of your problem. By calling .transition(), you change the object from a selection to a transition object. But transition objects don't have a .html() method. You didn't recognise that because you use .call(). That is executed before the transition has completed, however, and thus the ticks are overwritten with the default behaviour. I'll pose two solutions below. The first is an adaptation of your code, the second is the code I was working on. Hopefully it will give you some more pointers.
As close to your code as possible
var margin = {top: 80, right: 180, bottom: 80, left: 180},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(.1)
.paddingOuter(.3);
var y = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom()
.scale(x);
var yAxis = d3.axisLeft()
.scale(y)
.ticks(8, "%");
var svg = 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 + ")");
d3.tsv("https://gist.githubusercontent.com/mbostock/7555321/raw/1b17baa35f75336a8d9f4c8292b1d1ab850f3d86/data.tsv", type, function(error, data) {
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
svg.append("text")
.attr("class", "title")
.attr("x", x(data[0].name))
.attr("y", -26)
.text("Why Are We Leaving Facebook?");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.transition()
.call(xAxis)
.on("end", function() {
d3.select(this)
.selectAll(".tick text")
.call(wrap, x.bandwidth())
});
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
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.value); })
.attr("height", function(d) { return height - y(d.value); });
});
function wrap(textElements, width) {
textElements.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
function type(d) {
d.value = +d.value;
return d;
}
.bar {
fill: steelblue;
}
.bar:hover {
fill: brown;
}
.title {
font: bold 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
var margin = {
top: 80,
right: 180,
bottom: 80,
left: 180
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(.1)
.paddingOuter(.3);
var y = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom()
.scale(x);
var yAxis = d3.axisLeft()
.scale(y)
.ticks(8, "%");
var svg = 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 + ")");
d3.tsv("https://gist.githubusercontent.com/mbostock/7555321/raw/1b17baa35f75336a8d9f4c8292b1d1ab850f3d86/data.tsv", type, function(error, data) {
x.domain(data.map(function(d) {
return d.name;
}));
y.domain([0, d3.max(data, function(d) {
return d.value;
})]);
svg.append("text")
.attr("class", "title")
.attr("x", x(data[0].name))
.attr("y", -26)
.text("Why Are We Leaving Facebook?");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.transition()
.call(xAxis)
.on("end", function() {
d3.select(this).selectAll(".tick text")
.call(wrap, x.bandwidth())
});
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
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.value);
})
.attr("height", function(d) {
return height - y(d.value);
});
});
function wrap(textElements, width) {
textElements.each(function() {
d3.select(this).html(function(text) {
var words = text.split(/\s+/),
word,
lines = [],
currentLine = '',
lineNumber = 0,
lineHeight = 1.1
while (word = words.shift()) {
if (getTextWidth(currentLine + word, "10px sans-serif") < width) {
// We're safe to add the word
currentLine += word + ' ';
} else {
// If we add the word, we exceed the line length
// Trim to remove the last space
lines.push('<tspan x="0" y="9" dy="' + (++lineNumber * lineHeight) + 'em">' + currentLine.trim() + '</tspan>')
currentLine = word + ' ';
}
}
lines.push('<tspan x="0" y="9" dy="' + (++lineNumber * lineHeight) + 'em">' + currentLine.trim() + '</tspan>')
return lines.join('');
});
});
}
/**
* Measure the width of a text were it to be rendered using a given font.
*
* #param {string} text the text to be measured
* #param {string} font a valid css font value
*
* #returns {number} the width of the rendered text in pixels.
*/
function getTextWidth(text, font) {
const element = document.createElement("canvas");
const context = element.getContext("2d");
context.font = font;
return context.measureText(text).width;
};
function type(d) {
d.value = +d.value;
return d;
}
.bar {
fill: steelblue;
}
.bar:hover {
fill: brown;
}
.title {
font: bold 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

Understanding sorting bar charts in d3

I am reading Interactive Data Visualization for the Web book, and in the Interactivity chapter where we sort the bar chart, the author writes the following code
d3.select("body").append("button")
.text("Sort") //I modified the first two lines
.on("click", function(){
svg.selectAll("rect")//the bars were added before
.sort(function(a, b){
return d3.ascending(a.value, b.value);
})
.attr("x", function(d, i){
return xScale(i);//xScale is defined earlier
})
})
What I don't understand is, when we call the sort function after selecting the bars, what happens? Do the values in the original data set is sorted in ascending order? and why do we call the x attribute again? Suppose I have the value
dataset = [{key: 0, value: 10},
{key: 1, value: 5},
{key: 2, value: 7}]
and the data join happens on the key value. How to understand the sort function in this case? Why the bars are sorted? To me, it seems that, after sorting the dataset becomes
dataset = [{key: 1, value: 5},
{key: 2, value: 7}
{key: 0, value: 10}]
and the x attribute says that the second element to move to the first position, the third element to move to the second position, and the first element to move to the third position. Is this a correct understanding?
If you read the documentation about d3 Selection Sort
Returns a new selection that contains a copy of each group in this
selection sorted according to the compare function. After sorting,
re-inserts elements to match the resulting order (per
selection.order).
D3 binds data to nodes on your html tree. When you use selectAll('rect') then .sort(), it generates a new selection with ordered nodes, it also change the nodes position on your html dom. See also order
The nodes are rearranged but your rectangles x position are still with the old position. So you have to update the x attributes for the sorted elements.
.attr("x", function(d, i){
return xScale(i);//xScale is defined earlier
})
Is updating the x positions folowing h
const data = [10,20,25,4,30]
const xScale = d3.scaleBand()
.domain(d3.range(data.length))
.rangeRound([0, 500])
.paddingInner(0.05);
const yScale = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([0, 200]);
const svg = d3.select("body").append("svg")
.attr("width", "500px")
.attr("height","200px")
.style("width", "100%")
.style("height", "auto");
// normal
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0,20)")
svg.selectAll('.bar')
.data(data)
.enter()
.append('rect')
.attr("x", (d, i) => xScale(i))
.attr("y", (d, i) => 200 - yScale(d))
.attr("width", xScale.bandwidth())
.attr("height", d=> yScale(d));
setTimeout(() => {
console.log('Data on nodes before D3 sort')
console.log([...document.querySelectorAll('rect')].map(e => e.__data__))
svg.selectAll('rect')
.sort((a, b) => d3.ascending(a, b))
.transition()
.duration(500)
.attr("x", (d, i)=> xScale(i));
console.log('Data on nodes after D3 sort')
console.log([...document.querySelectorAll('rect')].map(e => e.__data__))
}, 2500)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
In your code selection.sort() reorders the elements, no position is changed by that. Just after the x attribute is reset based on the scale, the visualization is changed. Note that in your example the scale is called with i being the argument. So after the order of the elements change, elements will have a different index.
Some more aboout the topic:
selection.sort()
does reorder the selection elements - not the data. See also: https://github.com/d3/d3-selection#selection_sort.
Reordering elements in the DOM does not change any position of the elements (just overlaps are affected based on the order).
Sorting the elements can be useful when you transition your elements (Mike Bostocks Sortable Bar Chart example):
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 400 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var formatPercent = d3.format(".0%");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatPercent);
var svg = 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 + ")");
d3.csv("https://gist.githubusercontent.com/mbostock/81aa27912ad9b1ed577016797a780b2c/raw/3a807eb0cbb0f5904053ac2f9edf765e2f87a2f5/alphabet.csv", function(error, data) {
data.forEach(function(d) {
d.frequency = +d.frequency;
});
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
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("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); });
d3.select("input").on("change", change);
var sortTimeout = setTimeout(function() {
d3.select("input").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(sortTimeout);
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(this.checked
? function(a, b) { return b.frequency - a.frequency; }
: function(a, b) { return d3.ascending(a.letter, b.letter); })
.map(function(d) { return d.letter; }))
.copy();
svg.selectAll(".bar")
.sort(function(a, b) { return x0(a.letter) - x0(b.letter); });
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 50; };
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) { return x0(d.letter); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}
});
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
position: relative;
width: 400px;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
fill-opacity: .9;
}
.x.axis path {
display: none;
}
label {
position: absolute;
top: 10px;
right: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<label><input type="checkbox"> Sort values</label>
If you click on the sort checkbox, three things happen (see function change):
1) compute the new positions (in this case, the domain of the scale is changed here):
var x0 = x.domain(data.sort(this.checked
? function(a, b) { return b.frequency - a.frequency; }
: function(a, b) { return d3.ascending(a.letter, b.letter); })
.map(function(d) { return d.letter; }))
.copy();
2) resort the selection - just changing the order of the elements, no position has changed!
svg.selectAll(".bar")
.sort(function(a, b) { return x0(a.letter) - x0(b.letter); });
3) transitioning the selection to the new positions:
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 50; };
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) { return x0(d.letter); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
The benefit of step 2 (sorting the elements) is that the transition starts with the element which will be placed to the very left. The next transitioning element is the one which will end up next to it, ...and so forth.
See yourself when you run the code snippet und click on sort values.
To see how it looks like without this effect, see the following where I just commented out step 2:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 400 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var formatPercent = d3.format(".0%");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatPercent);
var svg = 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 + ")");
d3.csv("https://gist.githubusercontent.com/mbostock/81aa27912ad9b1ed577016797a780b2c/raw/3a807eb0cbb0f5904053ac2f9edf765e2f87a2f5/alphabet.csv", function(error, data) {
data.forEach(function(d) {
d.frequency = +d.frequency;
});
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
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("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); });
d3.select("input").on("change", change);
var sortTimeout = setTimeout(function() {
d3.select("input").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(sortTimeout);
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(this.checked
? function(a, b) { return b.frequency - a.frequency; }
: function(a, b) { return d3.ascending(a.letter, b.letter); })
.map(function(d) { return d.letter; }))
.copy();
// svg.selectAll(".bar")
// .sort(function(a, b) { return x0(a.letter) - x0(b.letter); });
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 50; };
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) { return x0(d.letter); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}
});
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
position: relative;
width: 400px;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
fill-opacity: .9;
}
.x.axis path {
display: none;
}
label {
position: absolute;
top: 10px;
right: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<label><input type="checkbox"> Sort values</label>

Finding the mean of bars in bar chart - d3

I am new to d3 and am trying to draw mean line through all the bars in the bar chart but not quite able to achieve that. Below is my code:
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>JS Bin</title>
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: #444;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script>
var data=[
{"letter": "BU", "higher": .08,"lower": .05},
{"letter": "AU", "higher": .05,"lower": .03},
{"letter": "LA", "higher": .04,"lower": .02}
]
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatPercent = d3.format(".0%");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x2 = d3.scale.ordinal()
.rangeBands([0, width], 0);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatPercent);
var svg = 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 + ")");
data.forEach(function(d) {
d.higher = +d.higher;
d.lower = +d.lower;
});
x.domain(data.map(function(d) { return d.letter; }));
x2.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.higher; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
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("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.higher); })
.attr("height", function(d) { return height - y(d.higher-d.lower); });
var dataSum = d3.mean(data, function(d) { return (d.higher + d.lower); });
var line = d3.svg.line()
.x(function(d, i) {
return x(d.letter) + i; })
.y(function(d, i) { return y(dataSum/data.length); });
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
</script>
</body>
</html>
The proper code is here'http://jsbin.com/beyakumohi/1/edit?html' . The line should pass through center of each bars but it only happens for the 3rd bar and that too it do not cross it. Is there any mistake in my code.
d3.mean will give you the mean value of the full array. Meaning, that in this case you will get the mean of:
data[0].higher + data[0].lower + data[1].higher + data[1].lower + data[2].higher + data[2].lower
In this case I would say it is more appropiate to edit your line function as following
var line = d3.svg.line()
.x(function(d, i) {
return x(d.letter); })
.y(function(d, i) { return y((d.higher + d.lower) / 2); });

d3 rangeRoundBands iternal calculation

I'm trying to add some padding between my bars, but can't control,say understand, it accurately.
Here's the code snippet I'm following from here:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.bar {
fill: steelblue;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
</style>
<svg class="chart"></svg>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 30, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var chart = 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 + ")");
d3.tsv("data.tsv", type, function(error, data) {
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.call(yAxis);
chart.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.attr("width", x.rangeBand());
});
function type(d) {
d.value = +d.value; // coerce to number
return d;
}
</script>
Data.dsv file content:
name value
A 12
B 13
C 22
D 5
Then, we you load it and query from developer's console, you know
> x.range()
[24, 258, 492, 726]
> x.rangeBand()
211
, but I wondered how these numbers be determined, the internal calcuation.Any help would be much appreciated!

Color d3 area based on computed value

I've created a line graph with d3.nest() objects, but I need the fill color for each segment to be based on another variable. I thought I could just do it based on the gradient from the data file, but it actually needs to be computed over the distance of the segment. Right now, everything is coming back as one color when it should be
if gradient < -10
color = red
if gradient < -5
color = green
if gradient < 0
color = white
if gradient < 5
color = yellow
if gradient < 10
color = black
else
color = blue
Here's a Plunk
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 12px Arial;
}
text.shadow {
stroke: #fff;
stroke-width: 2.5px;
opacity: 0.9;
}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.grid .tick {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
.area {
stroke-width: 0;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 30, right: 20, bottom: 35, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
var color = d3.scale.ordinal()
.domain([-10,-5,0,5,10])
.range(['red','green','white','yellow','black','blue']);
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var area = d3.svg.area()
.x(function(d) { return x(d.distance); })
.y0(height)
.y1(function(d) { return y(d.elevation); });
var valueline = d3.svg.line()
.x(function(d) { return x(d.distance); })
.y(function(d) { return y(d.elevation); });
var svg = 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 + ")");
// function for the x grid lines
function make_x_axis() {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
}
// function for the y grid lines
function make_y_axis() {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
}
// Get the data
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d.distance = +d.distance;
d.elevation = +d.elevation;
d.gradient = +d.gradient;
});
var dataGroup = d3.nest()
.key(function(d) {
return d.grade;
})
.entries(data);
dataGroup.forEach(function(group, i) {
if(i < dataGroup.length - 1) {
group.values.push(dataGroup[i+1].values[0])
}
})
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.distance; }));
y.domain([0, d3.max(data, function(d) { return d.elevation; })]);
dataGroup.forEach(function(d, i){
svg.append("path")
.datum(d.values)
.attr("class", "area")
.attr("d", area);
});
svg.selectAll(".area")
.style("fill", function(d) { return color(d.gradient); });
// Draw the x Grid lines
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_axis()
.tickSize(-height, 0, 0)
.tickFormat("")
)
// Draw the y Grid lines
svg.append("g")
.attr("class", "grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat("")
)
// Add the valueline path.
svg.append("path")
.attr("d", valueline(data));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// Add the text label for the X axis
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," +
(height+margin.bottom) + ")")
.style("text-anchor", "middle")
.text("Distance");
// Add the text label for the Y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("x", margin.top - (height / 2))
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("");
// Add the title
svg.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("text-decoration", "underline")
.text("Elevation Graph");
});
</script>
</body>
The issue is with this call:
svg.selectAll(".area")
.style("fill", function(d) { return color(d.gradient); });
Here, "d" is an array of objects of length one or two depending on the index. I don't know which object's gradient you wish to use, but my naive solution without understanding your data would be to use d[0]. Here's the Plunk.

Resources