d3js - Sortable Group Bar Chart - javascript

Full disclosure: I'm not new to programming, but I'm pretty new to d3 and javascript.
I am trying to combine the Grouped Bar Chart Example and the Sortable Bar Chart Example. I have a total of 51 groups of 3 variables. Here is a truncated form of my dataset you can use to run the code if you want:
State,Response,Predicted,Difference
1,0.0526,0.0983,0.0456
2,0.1161,0.1093,0.0068
5,0.0967,0.1035,0.0067
4,0.0998,0.0942,0.0055
6,0.0888,0.0957,0.0069
I want to be able to order the data by the Response variable by checking a box. Right now I can get the x-axis labels to move accordingly, but I can't get the bars to move with them. To get to this point I renamed the variables in the change() function according to my data. I tried saving the transition.selectAll(".state") function as state2 and then using state2.selectAll(".rect") to modify the x-coordinates of the rectangles, but I realized that wasn't going to get me anywhere.
Here is my code right now (mostly copied from the examples linked above). The relevant function is at the end.
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 1000 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
code = "";
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".0%"));
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("data.csv", function(error, data) {
var ageNames = d3.keys(data[0]).filter(function(key) { return key !== "State"; });
data.forEach(function(d) {
d.ages = ageNames.map(function(name) { return {name: name, value: +d[name]}; });
});
x0.domain(data.map(function(d) { return d.State; }));
x1.domain(ageNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) { return d3.max(d.ages, function(d) { return d.value; }); })]);
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("Prevalence");
var state = svg.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.State) + ",0)"; });
state.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.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) { return color(d.name); });
d3.select("input").on("change", change);
var sortTimeout = setTimeout(function() {
d3.select("input").property("checked", true).each(change);
}, 2000);
var legend = svg.selectAll(".legend")
.data(ageNames.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("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
function change() {
clearTimeout(sortTimeout);
// Copy-on-write since tweens are evaluated after a delay.
var x2 = x0.domain(data.sort(this.checked
? function(a, b) { return b.Response - a.Response; }
: function(a, b) { return d3.ascending(a.State, b.State); })
.map(function(d) { return d.State; }))
.copy();
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 50; };
var state2 = transition.selectAll(".state")
.delay(delay)
.attr("x", function(d) { return x2(d.State); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}
})
Any help would be greatly appreciated. I've found nothing so far searching SO and Google.

I assume that you want to keep the grouping when sorting. Your groups are contained in g elements, so all you need to do is adjust the coordinates of the groups. That is, the code to move the groups would look something like
svg.selectAll("g.g")
.transition().duration(750)
.delay(delay)
.attr("transform", function(d) { return "translate(" + x2(d.State) + ",0)"; });

Am tried with the stacked bar chart. To sort the stacked chart, please find the
Stacked Bar Chart
function change() {
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(this.checked
? function(a, b) { return b.noncomplete - a.noncomplete; }
: function(a, b) { return d3.ascending(a.moduleName, b.moduleName); })
.map(function(d) { return d.moduleName; }))
.copy();
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 60; };
transition.selectAll(".moduleName")
.delay(delay)
.attr("transform",function(d, i) { return "translate(" + (x0(d.moduleName)) + ",0)"; } );
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}

Related

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

bar chart transition, strange behavior d3.js

I am curious if someone can point out what I am doing wrong here? I was hoping my code would generate a smooth transition on click events, instead, first click only shifts the x-axis labels (why? how do i fix that?), then second/third do the sorting. Is there a way to prevent that from happening? I want to sort on first click and sure it would be nice if my labels didn't jump around. What am I missing here?
http://jsbin.com/cohaziqugo/edit?js,output
var data = [{"name":"A","value":0.08167},{"name":"B","value":0.01492},{"name":"C","value":0.0278},{"name":"D","value":0.04253},{"name":"E","value":0.12702},{"name":"F","value":0.02288},{"name":"G","value":0.02022},{"name":"H","value":0.06094},{"name":"I","value":0.06973},{"name":"J","value":0.00153},{"name":"K","value":0.00747},{"name":"L","value":0.04025},{"name":"M","value":0.02517},{"name":"N","value":0.06749},{"name":"O","value":0.07507},{"name":"P","value":0.01929},{"name":"Q","value":0.00098},{"name":"R","value":0.05987},{"name":"S","value":0.06333},{"name":"T","value":0.09056},{"name":"U","value":0.02758},{"name":"V","value":0.01037},{"name":"W","value":0.02465},{"name":"X","value":0.0015},{"name":"Y","value":0.01971},{"name":"Z","value":0.00074}];
var tickValues = data.map(function (d){return d.name;});
var step = Math.floor(tickValues.length / 24);
var indexes = d3.range(0,tickValues.length, step);
if (indexes.indexOf(tickValues.length - 1) == -1){
indexes.push(tickValues.length - 1);
}
var tickArray = d3.permute(tickValues, indexes);
var margin = { top: 40, right: 20, bottom: 30, left: 40 },
width = 1500 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(data.map(function (d) { return d.name; }))
.rangeBands([0, width], 0.1, 0.35);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickValues(tickArray);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var barChart = d3.select("#barbarchart1").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 + ")");
y.domain([0, 0.2]);
barChart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-0.8em")
.attr("dy", "0.15em")
.attr("transform", function(d){
return "rotate(-65)"
});
barChart.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("Test");
barChart.selectAll("#bar")
.data(data)
.enter().append("rect")
.attr("id", "bar")
.attr("x", function (d) { return x(d.name); })
.attr("width", x.rangeBand())
.attr("y", function (d) { return y(d.value); })
.attr("fill", "grey")
.attr("height", function (d) { return height - y(d.value); })
.on("click", function() {sortBars();})
.on("mouseover", function(d){
var xPos = parseFloat(d3.select(this).attr("x"));
var yPos = parseFloat(d3.select(this).attr("y"));
var height = parseFloat(d3.select(this).attr("height"));
var width = parseFloat(d3.select(this).attr("width"));
d3.select(this).attr("fill", "red");
barChart.append("text")
.attr("x",xPos)
.attr("y", yPos - 3)
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("font-weight", "bold")
.attr("fill", "black")
.attr("text-anchor", "middle")
.attr("id", "tooltip")
.attr("transform", "translate(" + width/2 + ")")
.text(d.name +": "+ d.value);
})
.on("mouseout", function(){
barChart.selectAll("#tooltip").remove();
d3.select(this).attr("fill", "grey");
});
var sortOrder = true;
var sortBars = function() {
//Flip value of sortOrder
sortOrder = !sortOrder;
var x0 = x.domain(data.sort(sortOrder
? function(a, b) { return b.value - a.value; }
: function(a, b) { return d3.ascending(a.name, b.name); })
.map(function(d) { return d.name; }))
.copy();
barChart.selectAll("#bar")
.sort(function(a, b) { return x0(a.name) - x0(b.name); });
var transition = barChart.transition().duration(750),
delay = function(d, i) { return i * 50; };
transition.selectAll("#bar")
.delay(delay)
.attr("x", function(d) { return x0(d.name); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("text")
.selectAll("g")
.delay(delay)
;};
function type(d) {
d.value = +d.value;
return d;
}
My solution: http://jsbin.com/cequrabuqi/edit?js,output
first click only shifts the x-axis labels
data's default sort is ascending. On first click, you flip sortOrder from true to false, causing your ternary condition to choose
function(a, b) { return d3.ascending(a.name, b.name); }) as the sort order, which is still ascending. While the code is correct, it looks broken because the sort order never changes on the first click, thus nothing happens to the bars on screen.
would be nice if my labels didn't jump around.
I don't fully understand D3, but what seems to happen is when you call transition.select(".x.axis").call(xAxis), it removes any styles you have applied. So in my solution, i reapplied the styles each time the sort function is called.

D3.js with update button adds new data to old instead of replacing old data

I've got an update button working that loads new .csv data into a D3.s stacked bar chart. My problem is that on update it seems to be using both data sets instead of just the new one. I think this is related to some confusion I have about update and selectAll, but I haven't been able to figure out how to replace instead of append. Here's my current code:
<!DOCTYPE html>
<meta charset="utf-8">
<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;
}
</style>
<body>
<!-- <label><input type="checkbox"> Sort values</label> -->
<div id="option">
<input name="updateButton"
type="button"
value="Update"
onclick="updateData()" />
</div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 200, left: 40},
width = 960 - margin.left - margin.right,
height = 650 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
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("FinalData4.csv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "State"; }));
data.forEach(function(d) {
var y0 = 0;
d.ages = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; });
d.total = d.ages[d.ages.length - 1].y1;
});
//data.sort(function(a, b) { return b.total - a.total; });
x.domain(data.map(function(d) { return d.State; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]);
// x-axis label
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function(d) {
return "rotate(-65)"
});
// y-axis label
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("USD in Millions");
// adds bars
var state = svg.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x(d.State) + ",0)"; });
state.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
// set up the legend
var legend = svg.selectAll(".legend")
.data(color.domain().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("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
});
// ** Update data section (Called from the onclick)
function updateData() {
// Get the data again
d3.csv("FinalData2015.csv", function(error, data2) {
color.domain(d3.keys(data2[0]).filter(function(key) { return key !== "State"; }));
data2.forEach(function(d) {
var y0 = 0;
d.ages = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; });
d.total = d.ages[d.ages.length - 1].y1;
});
x.domain(data2.map(function(d) { return d.State; }));
y.domain([0, d3.max(data2, function(d) { return d.total; })]);
var tran = d3.select("body").transition();
// change x-axis label
tran.select(".x.axis")
.duration(1000)
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function(d) {
return "rotate(-65)"
});
// change the y-axis label
tran.select(".y.axis")
.duration(1000)
.call(yAxis);
// adds bars
var state = svg.selectAll(".State")
.data(data2)
.enter().append("g")
//.transition()
//.duration(1000)
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x(d.State) + ",0)"; });
var test = state.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.transition()
.duration(1000)
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
});
}
</script>
</body>
UPDATE: I changed the functionUpdateData() code to what I have currently because I realized it had some junk in there. New code has working transitions for x and y axis labels, but still the same problem described above with the actual bars.
It is because you are not using update and exiting joins in the update function (read this article carefully http://bost.ocks.org/mike/join/). As far I understand, you want to update the existing csv data with the new one, for that you need to update the current set of data. I'll give you an example with a simpler code:
function updateData() {
//this is the new data that you are binding to some circles on the canvas
var circle = svg.selectAll("circle").data([200, 100, 50,10,5]);
//if there is new data, you get those new circles with the enter() method
var circleEnter = circle.enter().append("circle");
circleEnter.attr("cy", 60);
circleEnter.attr("cx", function(d, i) { return i * 100 + 30; });
circleEnter.attr("r", function(d) { return Math.sqrt(d); });
//------THIS IS THE PART YOU ARE MISSING------
//but the circles that already exist need an update. (check there is no enter() method here)
circle.attr("r", function(d) { return Math.sqrt(d); });
//also you need to remove the circles that donĀ“t have a data binding.
circle.exit().remove();
}
so I think that in your code you have to write something like:
var state = svg.selectAll(".state").data(data);
//the update data
state.attr("transform", function(d) { return "translate(" + x(d.State) + ",0)"; });

Controlling bar position with tickValues in d3.js

I'm trying to build a multi series bar chart using d3 but running into problems due to the sparse nature of the dataset.
I want to force the x-axis to have a tick for every day, even if there is no data. The test data I have can have data points that are weeks apart so I'm expecting wide areas with no bars - which is fine.
I thought I could force the xAxis to use a set of predefined ticks using the tickValues array, but these leads to very strange display of overlaying the text for each day on top of days that do have some data.
I've included a screenshot of what I mean.
I get the feeling I'm supposed to do something when calculating the width of the bars but can't figure out what that might be.
Code:
var data = [];
var tickValues = [];
var max = _.max(chartData.tabular, function(assessment) { return assessment.dateUTC; });
var min = _.min(chartData.tabular, function(assessment) { return assessment.dateUTC; });
var iter = moment.twix(min.dateUTC, max.dateUTC).iterate("days");
while(iter.hasNext()){
var momentObj = iter.next();
var assessment = _.find(chartData.tabular, {'date': momentObj.format('DD/MM/YYYY')});
tickValues.push(momentObj.valueOf());
if(assessment != null){
if(assessment.type == 'calculated'){
data.push({date: momentObj.valueOf(), calculated: assessment.score, manual: null});
}
if(assessment.type == 'manual'){
data.push({date: momentObj.valueOf(), calculated: null, manual: assessment.score});
}
}
}
log(data);
var margin = {top: 20, right: 55, bottom: 30, left: 40},
width = $('#cahai-chart').width() - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickValues(tickValues)
.tickFormat(function(d){return d3.time.format('%d/%m/%y')(new Date(d))});
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var color = d3.scale.ordinal()
.range(["#001c9c","#101b4d","#475003","#9c8305","#d3c47c"]);
var svg = d3.select("#cahai-chart 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 labelVar = 'date';
var varNames = d3.keys(data[0]).filter(function (key) { return key !== labelVar;});
color.domain(varNames);
data.forEach(function (d) {
var y0 = 0;
d.mapping = varNames.map(function (name) {
return {
name: name,
label: d[labelVar],
y0: y0,
y1: y0 += +d[name]
};
});
d.total = d.mapping[d.mapping.length - 1].y1;
});
x.domain(data.map(function (d) { return d.date; }));
y.domain([0, d3.max(data, function (d) { return d.total; })]);
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("Score");
var selection = svg.selectAll(".series")
.data(data)
.enter().append("g")
.attr("class", "series")
.attr("transform", function (d) { return "translate(" + x(d.date) + ",0)"; });
selection.selectAll("rect")
.data(function (d) { return d.mapping; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function (d) { return y(d.y1); })
.attr("height", function (d) { return y(d.y0) - y(d.y1); })
.style("fill", function (d) { return color(d.name); })
.style("stroke", "grey")
.on("mouseover", function (d) { showPopover.call(this, d); })
.on("mouseout", function (d) { removePopovers(); })
var legend = svg.selectAll(".legend")
.data(varNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function (d, i) { return "translate(55," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 10)
.attr("width", 10)
.attr("height", 10)
.style("fill", color)
.style("stroke", "grey");
legend.append("text")
.attr("x", width - 12)
.attr("y", 6)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function (d) { return d; });
function removePopovers () {
$('.popover').each(function() {
$(this).remove();
});
}
function showPopover (d) {
$(this).popover({
title: d.name,
placement: 'auto top',
container: 'body',
trigger: 'manual',
html : true,
content: function() {
return "Date: " + d3.time.format('%d/%m/%y')(new Date(d.label)) +
"<br/>Score: " + d3.format(",")(d.value ? d.value: d.y1 - d.y0); }
});
$(this).popover('show')
}
An ordinal scale will always show as many ticks are there are values in the domain. You just need to pass the full array of dates as the domain.
Replace this line
x.domain(data.map(function (d) { return d.date; }));
with this
x.domain(tickValues);
It looks like you have everything else set up correctly, so this will space the bars out along the axis and make them slimmer.

D3JS Axis orientation changes on data refresh

I have an issue on my bar/line chart whereby when data is refreshed, the left hand side Y axis has its tick marks change orientation from left to right. I am sure I have muddled up something simple as I am 4 days into D3, but I cannot see the problem.
My code is in JFIDDLE here. I have also added it to this post. Thank you for any assistance!
var data;
var margin = {
top: 20,
right: 80,
bottom: 30,
left: 50
},
width = 838 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], 0.05);
var yL = d3.scale.linear()
.range([height, 0]);
var yR = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxisL = d3.svg.axis()
.scale(yL)
.orient("left")
.ticks(10);
var yAxisR = d3.svg.axis()
.scale(yR)
.orient("right")
.ticks(10);
var EfficiencyLine = d3.svg.line()
.interpolate("basis")
.x(function (d) {
return x(d.xaxis);
})
.y(function (d) {
return yR(d.max_efficiency);
});
var svg = d3.select("#daychart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var which_data = Math.floor(Math.random() * 3) + 1
switch (which_data) {
case 1:
data = data1;
break;
case 2:
data = data2;
break;
case 3:
data = data3;
break;
default:
};
//d3.json("http://10.0.0.13/sma/sma-php/inverterdata.php?var=CDAY&id=C1200031", function (error, data) {
data.forEach(function (d) {
d.max_energy = +d.max_energy;
d.max_efficiency = +d.max_efficiency;
});
x.domain(data.map(function (d) {
return d.xaxis;
}));
yL.domain([0, d3.max(data, function (d) {
return d.max_energy;
})]);
yR.domain([0, d3.max(data, function (d) {
return d.max_efficiency;
})]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("transform", "rotate(0)")
.attr("y", 23)
.attr("x", 340)
.attr("dy", ".71em")
.style("text-anchor", "bottom")
.text("Timeline");
svg.append("g")
.attr("class", "y axis")
.call(yAxisL)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -50)
.attr("x", -145)
.attr("dy", ".71em")
.style("text-anchor", "top")
.text("Energy - KWh");
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + " ,0)")
.call(yAxisR)
.append("text")
.attr("y", 50)
.attr("x", -160)
.attr("transform", "translate(" + width + " ,0)")
.attr("transform", "rotate(-90)")
.attr("dy", ".71em")
.style("text-anchor", "top")
.text("Efficiency - KWh/KW");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function (d) {
return x(d.xaxis);
})
.attr("width", x.rangeBand())
.attr("y", function (d) {
return yL(d.max_energy);
})
.transition().delay(function (d, i) {
return i * 10;
}).duration(10)
.attr("height", function (d) {
return height - yL(d.max_energy);
});
svg.append("path")
.attr("d", EfficiencyLine(data))
.attr("class", "EfficiencyLine");
//Create labels
svg.selectAll("text.label")
.data(data)
.enter()
.append("text")
.attr("class", "label")
.text(function (d) {
if (d.max_energy == 0) {
return "";
} else {
return parseFloat(Math.round(d.max_energy * 100) / 100).toFixed(1);
};
})
.attr("x", function (d) {
return x(d.xaxis) + x.rangeBand() / 2;
})
.attr("y", function (d) {
return yL(d.max_energy) - 2;
})
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "black");
//});
//On click, update with new data
d3.select("p").on("click", function () {
var which_data = Math.floor(Math.random() * 3) + 1
switch (which_data) {
case 1:
data = data1;
break;
case 2:
data = data2;
break;
case 3:
data = data3;
break;
default:
};
// Get the data again
// d3.json("http://10.0.0.13/sma/sma-php/inverterdata.php?var=PDAY&id=P100023", function (error, data) {
data.forEach(function (d) {
d.max_energy = +d.max_energy;
d.max_efficiency = +d.max_efficiency;
});
// Scale the range of the data again
x.domain(data.map(function (d) {
return d.xaxis;
}));
yL.domain([0, d3.max(data, function (d) {
return d.max_energy;
})]);
yR.domain([0, d3.max(data, function (d) {
return d.max_efficiency;
})]);
svg.select("g.x").call(xAxis);
svg.select("g.y").call(yAxisL); <---- PROBLEM HERE IS SUSPECT?!
svg.select("g.y").call(yAxisR);
// Make the changes
svg.selectAll(".bar") // change the bar
.data(data) // Update the data within.
.transition().delay(function (d, i) {
return i / data.length * 1000;
})
.duration(500)
.attr("x", function (d) {
return x(d.xaxis);
})
.attr("y", function (d) {
return yL(d.max_energy);
})
.attr("width", x.rangeBand())
.attr("height", function (d) {
return height - yL(d.max_energy);
});
svg.selectAll("path.EfficiencyLine") // change the EfficiencyLine
.data(data) // Update the data within.
.transition().delay(function (d, i) {
return i / data.length * 1000;
})
.duration(500)
.attr("d", EfficiencyLine(data));
svg.selectAll("text.label")
.data(data)
.transition().delay(function (d, i) {
return i / data.length * 1000;
})
.duration(500)
.text(function (d) {
if (d.max_energy == 0) {
return "";
} else {
return parseFloat(Math.round(d.max_energy * 100) / 100).toFixed(1);
};
})
.attr("x", function (d) {
return x(d.xaxis) + x.rangeBand() / 2;
})
.attr("y", function (d) {
return yL(d.max_energy) - 2;
})
//});
});
Your problem is in following two lines: (line number 492-493)
svg.select("g.y").call(yAxisL);
svg.select("g.y").call(yAxisR);
Your solution is to have two y axes two different classes, and you will achieve that by changing above lines to:
svg.select("g.y-l").call(yAxisL);
svg.select("g.y-r").call(yAxisR);
and also change
line 386 to:
.attr("class", "y-l axis")
line 397 to:
.attr("class", "y-r axis")
You need class names that are consistent.
Updated working fiddle is here
Let me know if you need any additional clarification, have a dilemma etc.

Categories