d3 v4 Stacked to Grouped Bar Chart from CSV - javascript

Reference Mike Bostick's Stacked to Grouped Bar Chart example, I am modifying it to work with a CSV file. I have been working on this for a couple weeks and have looked through countless examples on Stack Overflow and elsewhere and am stumped.
The stacked bar chart works.
Stacked Bar Chart:
When I transition to a grouped bar chart I am only having issues referencing the key or series that is stacked or grouped. Right now all the rectangles display on top of each other instead of next to each other.
Grouped Bar Chart:
In the function transitionStep2() I want to multiply by a number corresponding to the series or key. I am currently multiplying by the number 1 in this function as a placeholder .attr("x", function(d) { return x(d.data.Year) + x.bandwidth() / 7 * 1; }).
<!DOCTYPE html>
<script src="https://d3js.org/d3.v4.min.js"></script>
<html><body>
<form>
<label><input type="radio" name="mode" style="margin-left: 10" value="step1" checked>1</label>
<label><input type="radio" name="mode" style="margin-left: 20" value="step2">2</label>
</form>
<svg id = "bar" width = "500" height = "300"></svg>
<script>
var svg = d3.select("#bar"),
margin = {top: 20, right: 20, bottom: 20, left: 20},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.08);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal()
.range(["#7fc97f", "#beaed4", "#fdc086", "#ffff99"]);
d3.csv("data.csv", function(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
}, function(error, data) {
if (error) throw error;
var keys = data.columns.slice(1);
x.domain(data.map(function(d) { return d.Year; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
color.domain(keys);
g.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", function(d) { return color(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth());
rect = g.selectAll("rect");
});
d3.selectAll("input")
.on("change", changed);
function changed() {
if (this.value === "step1") transitionStep1();
else if (this.value === "step2") transitionStep2();
}
function transitionStep1() {
rect.transition()
.attr("y", function(d) { return y(d[1]); })
.attr("x", function(d) { return x(d.data.Year); })
.attr("width", x.bandwidth())
.attr("stroke", "green");
}
function transitionStep2() {
rect.transition()
.attr("x", function(d) { return x(d.data.Year) + x.bandwidth() / 7 * 1; })
.attr("width", x.bandwidth() / 7)
.attr("y", function(d) { return y(d[1] - d[0]); })
.attr("stroke", "red");
}
</script></body></html>
And the csv file:
Year,A,B,C,D
1995,60,47,28,39
1996,29,56,99,0
1997,30,26,63,33
1998,37,16,48,0
1999,46,49,64,21
2000,78,88,81,57
2001,18,11,11,64
2002,91,76,79,64
2003,30,99,96,79

The example you're referring has linear values to group the chart into and so .attr("x", function(d, i) { return x(i) + x.bandwidth() / n * this.parentNode.__data__.key; }) works fine.
In your case, the columns/keys is not a linear scale but an ordinal value set that you have to set a scale for:
Refer simple d3 grouped bar chart
To do that i.e. set up a ordinal scale, here's what can be done:
var x1 = d3.scaleBand();
x1.domain(keys).rangeRound([0, x.bandwidth()]);
So this new scale would have a range of the x scale's bandwidth with the domain of ["A", "B", "C", "D"]. Using this scale to set the x attribute of the rects to group them:
.attr("x", function(d) {
return x(d.data.Year) + x1(d3.select(this.parentNode).datum().key);
})
where d3.select(this.parentNode).datum().key represent the column name.
Here's JSFIDDLE (I've used d3.csvParse to parse the data but I'm sure you'll get the point here. It's just the x attribute you need to reset.
Here's a Plunkr that uses a file.
Here's a code snippet as well:
var svg = d3.select("#bar"),
margin = {top: 20, right: 20, bottom: 20, left: 20},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.08);
var x1 = d3.scaleBand();
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal()
.range(["#7fc97f", "#beaed4", "#fdc086", "#ffff99"]);
var csv = 'Year,A,B,C,D\n1995,60,47,28,39\n1996,29,56,99,0\n1997,30,26,63,33\n1998,37,16,48,0\n1999,46,49,64,21\n2000,78,88,81,57\n2001,18,11,11,64\n2002,91,76,79,64\n2003,30,99,96,79';
var data = d3.csvParse(csv), columns = ["A", "B", "C", "D"];
data.forEach(function(d) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
});
var keys = columns;
x.domain(data.map(function(d) { return d.Year; }));
x1.domain(keys).rangeRound([0, x.bandwidth()]);
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
color.domain(keys);
g.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", function(d) { return color(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth());
rect = g.selectAll("rect");
d3.selectAll("input")
.on("change", changed);
function changed() {
if (this.value === "step1") transitionStep1();
else if (this.value === "step2") transitionStep2();
}
function transitionStep1() {
rect.transition()
.attr("y", function(d) { return y(d[1]); })
.attr("x", function(d) { return x(d.data.Year); })
.attr("width", x.bandwidth())
.attr("stroke", "green");
}
function transitionStep2() {
rect.transition()
.attr("x", function(d) {
return x(d.data.Year) + x1(d3.select(this.parentNode).datum().key);
})
.attr("width", x.bandwidth() / 7)
.attr("y", function(d) { return y(d[1] - d[0]); })
.attr("stroke", "red");
}
<!DOCTYPE html>
<script src="https://d3js.org/d3.v4.min.js"></script>
<html><body>
<form>
<label><input type="radio" name="mode" style="margin-left: 10" value="step1" checked>1</label>
<label><input type="radio" name="mode" style="margin-left: 20" value="step2">2</label>
</form>
<svg id = "bar" width = "500" height = "300"></svg>
Hope something helps. :)

I updated your examples so that it works,
see https://jsfiddle.net/mc5wdL6s/84/
function transitionStep2() {
rect.transition()
.duration(5500)
.attr("x", function(d,i) {
console.log("d",d);
console.log("i",i);
return x(d.data.Year) + x.bandwidth() / m / n * i; })
.attr("width", x.bandwidth() / n)
.attr("y", function(d) { return y(d[1] - d[0]); })
.attr("height", function(d) { return y(0) - y(d[1] - d[0]); })
.attr("stroke", "red");
}

You have to pass the index of the key to the individual rectangles and use this index to multiply with the reduced bandwith. If you also use the length of the keys array (instead of 7) you are CSV column count independent. You need to put the declaration of keys variable outside the d3.csv handler.
You forgot to stroke the initial rects green.
<script>
var svg = d3.select("#bar"),
margin = {top: 20, right: 20, bottom: 20, left: 20},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.08);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal()
.range(["#7fc97f", "#beaed4", "#fdc086", "#ffff99"]);
var keys;
d3.csv("/data.csv", function(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
}, function(error, data) {
if (error) throw error;
keys = data.columns.slice(1);
x.domain(data.map(function(d) { return d.Year; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
color.domain(keys);
var stackData = d3.stack().keys(keys)(data);
stackData.forEach(element => {
var keyIdx = keys.findIndex(e => e === element.key);
element.forEach(e2 => { e2.keyIdx = keyIdx; });
});
g.append("g")
.selectAll("g")
.data(stackData)
.enter().append("g")
.attr("fill", function(d) { return color(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.data.Year); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth())
.attr("stroke", "green");
rect = g.selectAll("rect");
});
d3.selectAll("input")
.on("change", changed);
function changed() {
if (this.value === "step1") transitionStep1();
else if (this.value === "step2") transitionStep2();
}
function transitionStep1() {
rect.transition()
.attr("y", function(d) { return y(d[1]); })
.attr("x", function(d) { return x(d.data.Year); })
.attr("width", x.bandwidth())
.attr("stroke", "green");
}
function transitionStep2() {
rect.transition()
.attr("x", function(d, i) { return x(d.data.Year) + x.bandwidth() / (keys.length+1) * d.keyIdx; })
.attr("width", x.bandwidth() / (keys.length+1))
.attr("y", function(d) { return y(d[1] - d[0]); })
.attr("stroke", "red");
}
</script>

Related

D3 graph tooltip should show nearest point on hover

I have made a graph using D3 but the problem is that I want its tooltip to appear on the graph as per nearest mouse point over in the graph. I have also seen one example on StackOverflow here on this link - D3: Get nearest value from ordinal axis on mouseover. Which is doing the exact what I want but when I implemented the same code on my graph then it is not working. Please take a look at my code and suggest me changes.
IMPORTANT!: I also want when someone double click on the graph then tooltip x'axis & y'axis coordinated stay there. Your valuable time is highly appreciated. Please Help!!!
<div id='chartdiv'></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
function getData(){
data1 = [{x:1,y:15},{x:2,y:26},{x:3,y:17},{x:4,y:21},];
return data1};
function drawChart(data) {
var coreheight = 720
var corewidth = 1280
var margin = {top: 10, right: 30, bottom: 100, left: 60}
, width = corewidth - margin.left - margin.right
, height = coreheight - margin.top - margin.bottom;
var xExtent = d3.extent(data, function(d) { return d.x; }),
xRange = xExtent[1] - xExtent[0],
yExtent = d3.extent(data, function(d) { return d.y; }).reverse(),
yRange = yExtent[1] - yExtent[0];
var xScale = d3.scaleLinear().range([50, width]).domain([xExtent[0] - (xRange * .05), xExtent[1] + (xRange * .05)]);;
var yScale = d3.scaleLinear().range([0, height]).domain([yExtent[0] - (yRange * .1), yExtent[1] + (yRange * .05)]);;
var line = d3.line()
.x(function(d, x) { return xScale(d.x); })
.y(function(d, y) { return yScale(d.y); })
.curve(d3.curveMonotoneX);
d3.select('#chartdiv')
.append('svg')
.attr('class','graph')
.style('background-color','#fff')
.attr("viewBox", "0 0 "+ corewidth +" "+ coreheight +"");
var svg = d3.select(".graph")
.append("g")
.attr("class", "dchart")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale).ticks(7))
.attr("transform", "translate(50, 0)")
.attr('font-size','25px');
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale).ticks(7))
.attr('font-size','25px');
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg.selectAll(".dot")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("cx", function(d) { return xScale(d.x) })
.attr("cy", function(d) { return yScale(d.y) })
.attr("r", 8)
.on("mouseover", mousehover)
.on("mouseout", mousehoverout)
function mousehover(d) {
svg.append("text")
.attr('class','annot')
.attr('x', xScale(d.x) - 20)
.attr('y', yScale(d.y) - 23)
.text(d.x+','+d.y)
.style('font-size','30px')
svg.append('line')
.attr('class','annot')
.style("stroke", "black")
.attr('stroke-width','3')
.attr("x1", xScale(d.x))
.attr("y1", yScale(d.y))
.attr("x2", xScale(d.x))
.attr("y2", height);
}
function mousehoverout() {
d3.selectAll('.annot').remove()
}
}drawChart(getData());
</script>
<style>
.line {fill: none;stroke: darkblue;stroke-width: 3}
.dot {fill: darkblue;stroke-width:0}
</style>
You need to track where the mouse is on the chart, you're currently only checking if the mouse is hovering on a dot.
I've added a couple of event handlers to the entire chart:
.on("mousemove", mousemove)
.on("mouseout", mousehoverout);
One you have already implemented, mousehoverout, and the other, mousemove, which tracks where the mouse is relative to the points.
var lastIndex = -1;
function mousemove()
{
let x = d3.mouse(this)[0];
let closest = data.reduce((best, value, i) =>
{
let absx = Math.abs(xScale(value.x) - x)
if(absx < best.value)
{
return {index: i, value:absx};
}
else
{
return best;
}
}, {index:0, value:Number.MAX_SAFE_INTEGER});
if(lastIndex != closest.index)
{
d3.selectAll('.annot').remove();
lastIndex = closest.index;
}
mousehover(data[closest.index]);
}
Then to add the double click functionality, you can do as you have shown in the w3schools link, but add the handler to the chart:
.on('dblclick', function(){/* your code in here */});
function getData(){
data1 = [{x:1,y:15},{x:2,y:26},{x:3,y:17},{x:4,y:21},];
return data1};
function drawChart(data) {
var coreheight = 720
var corewidth = 1280
var margin = {top: 10, right: 30, bottom: 100, left: 60}
, width = corewidth - margin.left - margin.right
, height = coreheight - margin.top - margin.bottom;
var xExtent = d3.extent(data, function(d) { return d.x; }),
xRange = xExtent[1] - xExtent[0],
yExtent = d3.extent(data, function(d) { return d.y; }).reverse(),
yRange = yExtent[1] - yExtent[0];
var xScale = d3.scaleLinear().range([50, width]).domain([xExtent[0] - (xRange * .05), xExtent[1] + (xRange * .05)]);;
var yScale = d3.scaleLinear().range([0, height]).domain([yExtent[0] - (yRange * .1), yExtent[1] + (yRange * .05)]);;
var line = d3.line()
.x(function(d, x) { return xScale(d.x); })
.y(function(d, y) { return yScale(d.y); })
.curve(d3.curveMonotoneX);
d3.select('#chartdiv')
.append('svg')
.attr('class','graph')
.style('background-color','#fff')
.attr("viewBox", "0 0 "+ corewidth +" "+ coreheight +"")
.on('dblclick', function(){if(lastIndex > -1) { createpeak(data[lastIndex]); } })
.on("mousemove", mousemove)
.on("mouseout", mousehoverout);
var svg = d3.select(".graph")
.append("g")
.attr("class", "dchart")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale).ticks(7))
.attr("transform", "translate(50, 0)")
.attr('font-size','25px');
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale).ticks(7))
.attr('font-size','25px');
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg.selectAll(".dot")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("cx", function(d) { return xScale(d.x) })
.attr("cy", function(d) { return yScale(d.y) })
.attr("r", 8);
var lastIndex = -1;
function mousemove()
{
let x = d3.mouse(this)[0];
let closest = data.reduce((best, value, i) =>
{
let absx = Math.abs(xScale(value.x) - x)
if(absx < best.value)
{
return {index: i, value:absx};
}
else
{
return best;
}
}, {index:0, value:Number.MAX_SAFE_INTEGER});
d3.selectAll('.annot').remove();
lastIndex = closest.index;
mousehover(data[closest.index]);
}
function mousehover(d) {
svg.append("text")
.attr('class','annot')
.attr('x', xScale(d.x) - 20)
.attr('y', yScale(d.y) - 23)
.text(d.x+','+d.y)
.style('font-size','30px')
svg.append('line')
.attr('class','annot')
.style("stroke", "black")
.attr('stroke-width','3')
.attr("x1", xScale(d.x))
.attr("y1", yScale(d.y))
.attr("x2", xScale(d.x))
.attr("y2", height);
}
var i = 1;
var m = 1;
function createpeak(d) {
console.log('redacted');
}
function dragstarted(d,e){
d3.select(this).raise().classed("active", true);
var current = d3.select(this);
deltaX = current.attr("x") - d3.event.x;
deltaY = current.attr("y") - d3.event.y;
}
function dragged(d,e){
d3.select(this).attr("x",d3.event.x + deltaX)
d3.select(this).attr("y",d3.event.y + deltaY)
for (var i=1; i <= 100; i++){
d3.select('.line'+i).attr("x2", function (d) {
return d3.select('.peak'+i).attr("x") - deltaX
});
d3.select('.line'+i).attr("y2", function (d) {
return d3.select('.peak'+i).attr("y") - deltaY
})}}
function dragended(d,e){}
function mousehoverout() {
}
}drawChart(getData());
.line {fill: none;stroke: darkblue;stroke-width: 3}
.dot {fill: darkblue;stroke-width:0}
<div id='chartdiv'></div>
<script src="https://d3js.org/d3.v5.min.js"></script>

D3.js v5 Line chart with circle markers

The lines on my chart are displaying with a weird black filled-in shape instead of an actual line. Any idea what i'm doing wrong? The data is parsed in from a csv. It's a multiline chart where each series is a column in the csv(columns are: date, cat1, cat2, cat3, cat4).
Aside - I got this to work in a different fashion by defining each valueline and appending to the path. But I ran into issues with scaling the axis. I eventually need to have four plots on my html page, each with different axis scales (for example log scale on the y axis). Therefore I'll need y = d3.scaleLog().domain(my domain).range(range) and then in d3.line.y return y(my value).
var margin = {top: 20, right: 20, bottom: 30, left: 50},
w = 960 - margin.left - margin.right,
h = 500 - margin.top - margin.bottom;
var padding =20;
var xScale = d3.scaleTime()
.domain([d3.min(dataset,function (d) { return d.date }),d3.max(dataset,function (d) { return d.date }) ]).range([padding, w - padding * 2])
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset,function (d) { return d.cat1 }) ]).range([h- padding, padding])
var xAxis = d3.axisBottom().scale(xScale);
var yAxis = d3.axisLeft().scale(yScale);
var svg1 = d3.select("body").append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var line = d3.line()
.x(function(d) { return xScale(d['date']); })
.y(function(d) { return yScale(d['cat1']); });
var line2 = d3.line()
.x(function(d) { return xScale(d['date']); })
.y(function(d) { return yScale(d['cat2']); });
var line3 = d3.line()
.x(function(d) { return xScale(d['date']); })
.y(function(d) { return yScale(d['cat3']); });
var line4 = d3.line()
.x(function(d) { return xScale(d['date']); })
.y(function(d) { return yScale(d['cat4']); });
var path = svg1.append('path').attr('d', line(dataset));
var path2 = svg1.append('path').attr('d', line2(dataset));
var path3 = svg1.append('path').attr('d', line3(dataset));
var path4 = svg1.append('path').attr('d', line4(dataset));
//draw points
var selectCircle = svg1.selectAll(".circle")
.data(dataset)
selectCircle.enter().append("circle")
.attr("class", "circle")
.attr("r", 10)
.attr("cx", function(d) {
return xScale(d.date)
})
.attr("cy", function(d) {
return yScale(d.cat1)
})
.attr("fill", "#FFC300")
selectCircle.enter().append("circle")
.attr("class", "circle")
.attr("r", 10)
.attr("cx", function(d) {
return xScale(d.date)
})
.attr("cy", function(d) {
return yScale(d.cat2)
})
.attr("fill", "#FF5733")
selectCircle.enter().append("circle")
.attr("class", "circle")
.attr("r", 10)
.attr("cx", function(d) {
return xScale(d.date)
})
.attr("cy", function(d) {
return yScale(d.cat3)
})
.attr("fill", "#C70039")
selectCircle.enter().append("circle")
.attr("class", "circle")
.attr("r", 10)
.attr("cx", function(d) {
return xScale(d.date)
})
.attr("cy", function(d) {
return yScale(d.cat4)
})
.attr("fill", "#900C3F")
svg1.append("g").attr("class", "axis").attr("transform", "translate(0," + (h - padding) + ")").call(xAxis);
//draw Y axis
svg1.append("g").attr("class", "axis").attr("transform", "translate(" + padding + ",0)").call(yAxis);
// add label
svg1.append("text").attr("x", (w/2)).attr("y", h+30).attr("text-anchor", "middle").text("Year");
svg1.append("text").attr("x", padding).attr("y", padding-20).attr("text-anchor", "middle").text("# of Events");
//add title
svg1.append("text").attr("x", (w/2)).attr("y", padding).attr("text-anchor", "middle").text("Events per Year by Category");

Color grouped bar chart based on comparison of numbers d3

I am building up a grouped bar chart. In the chart, I want to color one group of bar chart differently because its Missouri number is bigger than the national average number. However, my else if statement doesn't work out for the fill function. Can anyone tell me what should I do to color the chart differently based on the comparison of numbers? Thanks in advance!!
Here is my Code
<svg id="bodychart" width="900" height="500" style="display: block; margin: auto"></svg>
<script>
var svg = d3.select("#bodychart"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top +")" ); //Not quite understand (??)
var x0 = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.1);
var x1 = d3.scaleBand()
.padding(0.05);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(["#F63014", "#ABABAB"]);
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
d3.csv("number.csv", function(d, i, columns){
for (var i = 1, n = columns.length; i < n; ++i) d[columns[i]] = + d[columns[i]];
return d;
}, function(error, data) {
if (error) throw error;
var keys = data.columns.slice(1);
x0.domain(data.map(function(d) {return d.BodyParts; }));
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
y.domain([0, d3.max(data, function(d) {return d3.max(keys, function(key) {return d[key]; }); })]);
g.append("g")
.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {return "translate(" + x0(d.BodyParts) + ",0)"; })
.selectAll("rect")
.data(function(d) {return keys.map(function(key){return {key: key, value: d[key]}; }); })
.enter()
.append("rect")
.attr("x", function(d) {return x1(d.key);})
.attr("y", function(d) {return y(d.value);})
.attr("width", x1.bandwidth())
.attr("height", function(d){return height - y(d.value);})
.attr("fill", function(d, i) {
if (d.value['Missouri'] > d.value['National_Average']) {
return z(d.key);
} else (d.value['Missouri'] < d.value['National_Average']) {
return "yellow";
}
})
.on("mousemove", function(d){
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html("<span style='font-weight: bold'>"+ (d.key) +"</span>" + ":" + "<br>" + "$" + d3.format(",.0f")(d.value));
})
.on("mouseout", function(d){ tooltip.style("display", "none");});
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x0));
g.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y).ticks(10, ",.0f"))
.append("text")
.attr("x", -32)
.attr("y", y(y.ticks().pop()) + 0.5)
.attr("dy", "-3em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.text("Dollars");
});
Here is my csv file:
BodyParts,Missouri,National_Average
Arm,115100,169878
Leg,102697,153221
Hand,86821,144930
Thumb,29767,42432
Index Finger,22325,24474
Middle Finger,17364,20996
Ring Finger,17364,14660
Pinky,10915,11343
Foot,74418,91779
Big Toe,19845,23436
Eye,69457,96700
Ear,24310,38050
Testicle,0,27678
Since the complete data for each pair lies in the parent selection, you have to get it before doing any comparison.
Thus, when setting the fill for each bar, this...
var parentData = d3.select(this.parentNode).data()[0];
... will store the data for the pair, that is, Missouri and National_Average.
Then, use that object to fill the bars conditionally. This is a way to do that (there are shorter ways, of course, but I believe this verbose snippet is more didactic for you):
.attr("fill", function(d, i) {
var parentData = d3.select(this.parentNode).data()[0];
if (d.key === "Missouri") {
if (parentData.Missouri > parentData.National_Average) {
return "green"
} else {
return "red"
}
} else {
if (parentData.Missouri < parentData.National_Average) {
return "green"
} else {
return "red"
}
}
})
Here is a plunker showing it: http://plnkr.co/edit/dfjKFUpuiJvLs6wWkO99?p=preview

Day/Hour Heatmap + mySQL

I'm trying to fit my dataset into the example here: (http://bl.ocks.org/tjdecke/5558084).
Here's my d3js:
var margin = { top: 50, right: 0, bottom: 100, left: 30 },
width = 600 - margin.left - margin.right,
height = 430 - margin.top - margin.bottom,
gridSize = Math.floor(width / 24),
legendElementWidth = gridSize*2,
buckets = 9,
colors = ["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#25 3494","#081d58"], // alternatively colorbrewer.YlGnBu[9]
days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
times = ["1a", "2a", "3a", "4a", "5a", "6a", "7a", "8a", "9a", "10a", "11a", "12a", "1p", "2p", "3p", "4p", "5p", "6p", "7p", "8p", "9p", "10p", "11p", "12p"];
d3.json("/test/heatmap.php", function(error, data) {
data.forEach(function(d) {
console.log(d);
day= +d.day;
hour= +d.hour;
value= +d.value;
});
},
function(error, data) {
var colorScale = d3.scale.quantile()
.domain([0, buckets - 1, d3.max(data, function (d) { return d.value; })])
.range(colors);
console.log(d3.max(data, function (d) { return d.value; }))
;
console.log("test");
var svg = d3.select(".wpd3-8-3").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 dayLabels = svg.selectAll(".dayLabel")
.data(days)
.enter().append("text")
.text(function (d) { return d; })
.attr("x", 0)
.attr("y", function (d, i) { return i * gridSize; })
.style("text-anchor", "end")
.attr("transform", "translate(-6," + gridSize / 1.5 + ")")
.attr("class", function (d, i) { return ((i >= 0 && i <= 4) ? "dayLabel mono axis axis-workweek" : "dayLabel mono axis"); });
var timeLabels = svg.selectAll(".timeLabel")
.data(times)
.enter().append("text")
.text(function(d) { return d; })
.attr("x", function(d, i) { return i * gridSize; })
.attr("y", 0)
.style("text-anchor", "middle")
.attr("transform", "translate(" + gridSize / 2 + ", -6)")
.attr("class", function(d, i) { return ((i >= 7 && i <= 16) ? "timeLabel mono axis axis-worktime" : "timeLabel mono axis"); });
var heatMap = svg.selectAll(".hour")
.data(data)
.enter().append("rect")
.attr("x", function(d) { return (d.hour - 1) * gridSize; })
.attr("y", function(d) { return (d.day - 1) * gridSize; })
.attr("rx", 4)
.attr("ry", 4)
.attr("class", "hour bordered")
.attr("width", gridSize)
.attr("height", gridSize)
.style("fill", colors[0]);
heatMap.transition().duration(1000)
.style("fill", function(d) { return colorScale(d.value); });
heatMap.append("title").text(function(d) { return d.value; });
var legend = svg.selectAll(".legend")
.data([0].concat(colorScale.quantiles()), function(d) { return d; })
.enter().append("g")
.attr("class", "legend");
legend.append("rect")
.attr("x", function(d, i) { return legendElementWidth * i; })
.attr("y", height)
.attr("width", legendElementWidth)
.attr("height", gridSize / 2)
.style("fill", function(d, i) { return colors[i]; });
legend.append("text")
.attr("class", "mono")
.text(function(d) { return "≥ " + Math.round(d); })
.attr("x", function(d, i) { return legendElementWidth * i; })
.attr("y", height + gridSize);
});
And an example of my php echo:
[{"day":"4","hour":"0","value":"91"},{"day":"7","hour":"0","value":"93"},{"day":"3","hour":"1","value":"71"},{"day":"4","hour":"1","value":"90"},{"day":"3","hour":"2","value":"96"},{"day":"4","hour":"2","value":"89"},{"day":"5","hour":"2","value":"88"},{"day":"3","hour":"3","value":"97"},{"day":"4","hour":"3","value":"88"},{"day":"5","hour":"3","value":"88"},{"day":"6","hour":"3","value":"89"},{"day":"4","hour":"4","value":"86"},{"day":"5","hour":"4","value":"86"},{"day":"6","hour":"4","value":"88"},{"day":"7","hour":"4","value":"91"},{"day":"4","hour":"5","value":"86"},{"day":"6","hour":"5","value":"88"},{"day":"7","hour":"5","value":"91"},{"day":"6","hour":"6","value":"88"},{"day":"7","hour":"6","value":"91"},{"day":"6","hour":"7","value":"86"},{"day":"6","hour":"8","value":"86"},{"day":"6","hour":"9","value":"86"},{"day":"6","hour":"10","value":"86"},{"day":"6","hour":"11","value":"85"},{"day":"3","hour":"12","value":"91"},{"day":"4","hour":"12","value":"82"},{"day":"5","hour":"12","value":"82"},{"day":"6","hour":"12","value":"84"},{"day":"4","hour":"13","value":"82"},{"day":"6","hour":"13","value":"85"},{"day":"6","hour":"14","value":"87"},{"day":"6","hour":"15","value":"88"},{"day":"6","hour":"16","value":"88"},{"day":"6","hour":"17","value":"88"},{"day":"6","hour":"18","value":"88"},{"day":"6","hour":"19","value":"89"},{"day":"6","hour":"20","value":"90"},{"day":"6","hour":"21","value":"91"},{"day":"6","hour":"22","value":"91"},{"day":"6","hour":"23","value":"93"}]
I am stumped, because when I log the results of the d3.json, it matches the results of the example file. The thing I've noticed is in the example, the
console.log(d3.max(data, function (d) { return d.value; }))
loads with the max value. When I load my values, console.log doesn't seem to print beyond the d3.json step.
Any advice? I'm a bit stumped after a few days of reading and experimenting.
Thanks!
You have 2 functions in the json call (one for the error and one for the success?). You only need one - you first check if the error is there and do an error handling.
So you could remove this
function(error, data) {
data.forEach(function(d) {
console.log(d);
day= +d.day;
hour= +d.hour;
value= +d.value;
});
},
Apart from that, it's mostly good - just that you'll need to adjust the x attr call a bit - you don't need the -1
.attr("x", function (d) { return (d.hour) * gridSize; })

Update Bar Chart in d3

I have a question about updating a bar chart in d3.js. I've played around with the transition function but can't seem to get it working.
Here is the code for the basic bar chart:
<script>
var width = 960,
height = 500;
var y = d3.scale.linear()
.range([height, 0]);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", height);
d3.tsv("data/data.tsv", type, function(error, data) {
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
var barWidth = width / data.length;
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(" + i * barWidth + ",0)"; });
bar.append("rect")
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); })
.attr("width", barWidth - 1);
bar.append("text")
.attr("x", barWidth / 2)
.attr("y", function(d) { return y(d.frequency) + 3; })
.attr("dy", ".75em")
.text(function(d) { return d.frequency; });
});
function type(d) {
d.frequency = +d.frequency;
return d;
}
And so far, I have this for the update function:
function updateBarData(){
var width = 960,
height = 500;
var y = d3.scale.linear()
.range([height, 0])
d3.tsv("data/data3.tsv", type, function(error, data) {
y.domain([0, d3.max(data, function(d) { return d.frequency; })])
})
var barupdate = chart.selectAll("")
.data(data);
barupdate.enter()
.attr("transform", function(d, i) { return "translate(" + i * barWidth + ",0)"; })
barupdate.append("rect")
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); })
.attr("width", barWidth - 1);
barupdate.append("text")
.attr("x", barWidth / 2)
.attr("y", function(d) { return y(d.frequency) + 3; })
.attr("dy", ".75em")
.text(function(d) { return d.frequency; });
barupdate.transition()
.duration(1000)
.attr("transform", function(d, i) { return "translate(" + i * barWidth + ",0)"; })
barupdate.append("rect")
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); })
.attr("width", barWidth - 1);
barupdate.append("text")
.attr("x", barWidth / 2)
.attr("y", function(d) { return y(d.frequency) + 3; })
.attr("dy", ".75em")
.text(function(d) { return d.frequency; });
barupdate.exit().transition()
.duration(1000)
.remove();
}
I've been starting at this all day, so may be missing something blindingly obvious, but would appreciate any help.
Thanks.
Update 1:
function updateBarData(){
d3.tsv("data/data3.tsv", type, function(error, data) {
})
var updatechart = g.selectAll("body")
.data(data)
updatechart.enter().insert("rect")
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); })
.attr("width", barWidth - 1)
updatechart.transition()
.duration(1000)
.attr("y", function(d) { return y(d.frequency); })
updatechart.exit()
.remove();
}

Categories