I'm trying to do a chart with the help of the d3.js library,my data come from two sources and have the same format(tsv),I use a form to try to change the chart displayed according to which button has been clicked.My problem actually is that when I change the chart choice, the labels are duplicated as well as my charts.
My form look like this:
<form>
<label><input name="radio" type="radio" value="Incoming"/> Incoming</label>
<label><input name="radio" type="radio" value="Outgoing" checked="checked"/> Outgoing</label>
</form>
And the javascript part to retrieve values of the checked radio input:
draw("data.tsv");//on load load the default checked
d3.selectAll("input").on("change", change);
function change(){
var radios = document.getElementsByName('radio');
for (var i = 0, length = radios.length; i < length; i++) {
if (radios[i].checked) {
if(radios[i].value=="Outgoing"){
draw("data.tsv");
console.log(radios[i].value);
}
else{
draw("datasecond.tsv");
console.log(radios[i].value);
}
}
}
}
Thank you to give some advices!!!
EDIT:
the draw function:
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 draw(data){
d3.tsv(data, function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
//data.forEach(function(d) {
// d.date = parseDate(d.date);
//});
var cities="";
cities = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, temperature: +d[name]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(cities, function(c) { return d3.min(c.values, function(v) { return v.temperature; }); }),
d3.max(cities, function(c) { return d3.max(c.values, function(v) { return v.temperature; }); })
]);
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("Number");
var city = svg.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");
city.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
city.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")"; })
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
});
}
it is hard to say without seeing the code of your draw function, but I guess that the explanation is that you are creating new axis() objects each time you call draw, instead of reusing the existing ones.
be sure that you keep references to your axis objects. create them once, and in your draw function reuse those instances.
Related
I'm working on creating a graph that updates when a button is clicked, however when clicking the button, it seems only the axis are updating, and not the data itself.
The current version is in this plunker, I've also attached the code below:
http://plnkr.co/edit/85H6i25YPbTB0MRKtpZn?p=preview
I'm still quite new to D3 and have used a a few books and a lot of reading to get me to an ok level, but am struggling to find an answer to this specific question after trawling through many pages of the internet.
It would be amazing if anyone could give me some guidance on where I'm going wrong.
<body>
<svg width="960" height="500"></svg>
<div id="option">
<input name="updateButton" type="button" value="Click here to update the chart with results after the snap election" onclick="updateData()" />
</div>
<script type="text/javascript">
//graph 1
var svg = d3.select("svg"),
margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
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 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(["#0087dc", "#d50000", "#FDBB30"]);
d3.csv("data.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.Year;
}));
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
y.domain([0, d3.max(data, function(d) {
return d3.max(keys, function(key) {
return d[key];
});
})]).nice();
g.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d) {
return "translate(" + x0(d.Year) + ",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) {
return z(d.key);
});
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(null, "s"))
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks().pop()) + 0.5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.text("Seats before snap election");
var legend = g.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "end")
.selectAll("g")
.data(keys.slice().reverse())
.enter().append("g")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
legend.append("rect")
.attr("x", width - 19)
.attr("width", 19)
.attr("height", 19)
.attr("fill", z);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9.5)
.attr("dy", "0.32em")
.text(function(d) {
return d;
});
});
// ** Update data section (Called from the onclick)
function updateData() {
//call data
d3.csv("data_copy.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);
//scale range of data again
x0.domain(data.map(function(d) {
return d.Year;
}));
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
y.domain([0, d3.max(data, function(d) {
return d3.max(keys, function(key) {
return d[key];
});
})]).nice();
var sel = svg.selectAll("g")
.data(data);
//remove
sel.exit().remove("g");
sel.enter().append("g")
.attr("transform", function(d) {
return "translate(" + x0(d.Year) + ",0)";
})
.selectAll("rect")
.data(function(d) {
return keys.map(function(key) {
return {
key: key,
value: d[key]
};
});
})
//remove
svg.selectAll("rect");
sel.exit().remove("rect");
sel.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) {
return z(d.key);
});
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(null, "s"))
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks().pop()) + 0.5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.text("Seats after snap election");
});
}
#thedude's answer is right, but doesn't correct everything: it updates the bars, but the heights are wrong, for instance. Something to do with the inner .data join and the subsequent secondary formatting, I guess.
I checked at the same time and came up with the solution below. The core change that makes the update button update is this:
var sel = svg.selectAll("g.chartarea").selectAll("g.year").data(data);
sel.exit().remove();
sel.enter().append("g").classed("year", true);
// continuing with sel didn't update the just appended elements
// so I repeated the selection to get the new elements as well
sel = svg.selectAll("g.chartarea").selectAll("g.year");
sel.attr( // and so on
Complete script:
var svg = d3.select("svg"),
margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
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 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(["#0087dc", "#d50000", "#FDBB30"]);
// added class to enable precise selection
g.append("g").classed("chartarea", true);
// added classes to enable precise selection
g.append("g")
.classed("axis", true)
.classed("x-axis", true);
// added classes to enable precise selection
g.append("g")
.classed("axis", true)
.classed("y-axis", true);
updateGraph("data.csv");
// ** Update data section (Called from the onclick)
function updateData() {
updateGraph("data_copy.csv");
}
function updateGraph(file) {
//call data
d3.csv(file, 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);
//scale range of data again
x0.domain(data.map(function(d) {
return d.Year;
}));
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
y.domain([0, d3.max(data, function(d) {
return d3.max(keys, function(key) {
return d[key];
});
})]).nice();
var sel = svg.selectAll("g.chartarea").selectAll("g.year")
.data(data);
//remove
sel.exit().remove();
// added classes to enable precise selection
sel.enter().append("g").classed("year", true);
sel = svg.selectAll("g.chartarea").selectAll("g.year");
sel.attr("transform", function(d) {
return "translate(" + x0(d.Year) + ",0)";
})
.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) {
return z(d.key);
});
var parties =
sel.selectAll("rect.party")
.data(function(d) {
return keys.map(function(key) {
return {
key: key,
value: d[key]
};
});
});
parties.exit().remove();
// added classes to enable precise selection
parties.enter().append("rect").classed("party", true);
parties = sel.selectAll("rect.party");
parties.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) {
return z(d.key);
});
// select the axes instead of appending them here
g.selectAll("g.x-axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x0));
g.selectAll("g.y-axis")
.call(d3.axisLeft(y).ticks(null, "s"))
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks().pop()) + 0.5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.text("Seats after snap election");
});
}
Added some further changes that may be worth a look:
Don't differentiate between initialization and update. This is exactly what D3 excels at: doing everything with the same code. In my refactored version, the code is reduced to a single updateGraph function that does both.
Use classes or identifiers to differentiate your graphical elements. There are several places where you select too much with selectAll("g") which will select nearly all elements in your chart.
Don't add stuff multiple times. For example, the axes should be added only once. In the original code, they were added twice, overlaying each other. Instead, add them once, leave them uninitialized, then later select them and set their attributes correctly.
You need to update your rect selection in your click handler like this:
...
//remove
sel = svg.selectAll("rect");
sel.exit().remove("rect");
sel.enter().append("rect")
sel.attr("x", function(d) {
return x1(d.key);
})
.attr("y", function(d) {
return y(d.value);
})
...
You can see it in action here:
http://plnkr.co/edit/3R9lauiQQIB0IgrAk3X2?p=preview
Edit - I've updated the plunker with a working example that addresses several other issues
I have created group bar chart by using D3.js. Each group has 2 bars. When any bar is clicked it must show some data using custom alert box. Now the bar can click and it shows data.
var state = svg.selectAll(".TestSuite")
.data(data)
.enter().append("g")
.attr("class", "TestSuite")
.on("click", function(d,i) {
if(i==0){
Alert.render(d3.select(this).data()[0].FalseStatements);
}else{
Alert.render(d3.select(this).data()[0].TrueStatements);
}
})
.attr("transform", function (d) {
return "translate(" + x0(d.TestSuite) + ",0)";
});
But data is vary according to clicked bars. So how to identify each single bar within a single group uniquely.
Here "if condition" that I used does not do the thing I want.How do I correct it?
Thank you.
(Suppose one group of bar consists two bars, one shows true count and other shows false count for a particular scenario. When we click the bar which shows true count then it should appear "TrueStatements" which is already have in data.using d3.select(this).data()[0].TrueStatements can do this. And also when someone click the bar which shows false count then it should appear "FalseStatements" which is already have in data.using d3.select(this).data()[0].FalseStatements can do this. My question is how do we identify the bar which shows true count and the bar which shows false count uniquely for do this task.)
EDITED:
How I get the data for bar chart(This is inside a for loop)
originalDataSetForBarChart.push({
TestSuite: "TS"+treeIndex,
Pass: trueAppear,
Fail: falseAppear,
FalseStatements : falseStatement,
TrueStatements : trueStatement
});
Bar chart code
var margin = {
top: 20,
right: 10,
bottom: 30,
left: 40
},
width = 890 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .5);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.ordinal()
.range(["#4169E1", "#800080"]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(""));
var w = width + margin.left + margin.right;
var h = height + margin.top + margin.bottom;
var svg = d3.select(".chart1").append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//svg.call(tip);
var xg = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
var yg = svg.append("g")
.attr("class", "y axis");
yg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Count");
I append bars to this chart inside a setInterval function using following method.
function update() {
startTime_barChart = new Date().getTime();
for (var i = 0; i < data.length; i++) {
var testSuite = d3.keys(data[i]).filter(function (key) {
return key !== "TestSuite";
});
}
data.forEach(function (d) {
d.trueFalseCount = testSuite.map(function (name) {
return {
name: name,
value: +d[name]
};
});
});
x0.domain(data.map(function (d) {
return d.TestSuite;
}));
x1.domain(testSuite).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function (d) {
return d3.max(d.trueFalseCount, function (d) {
return d.value;
});
})]);
//making the x axis/y axis
xg.call(xAxis);
yg.call(yAxis);
//removing all the rectangles
svg.selectAll(".TestSuite").remove();
var tip_word;
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
tip_word= "<strong style='color:white'>"+
"Pass count :"+
"</strong>"+
" <span style='color:white'>" + d.True +
"</span></br>"+
"<strong style='color:white'>"+
"Fail count :"+
"</strong>"+
" <span style='color:white'>" + d.False +
"</span>";
return word;
});
var state = svg.selectAll(".TestSuite")
.data(data)
.enter().append("g")
.attr("class", "TestSuite")
.on("click", function(d,i) {
if(i%2 == 0){//How to set this condition
Alert.render(d3.select(this).data()[0].FalseStatements);
}else{
Alert.render(d3.select(this).data()[0].TrueStatements);
}
})
.attr("transform", function (d) {
return "translate(" + x0(d.TestSuite) + ",0)";
});
svg.call(tip);
state.selectAll("rect")
.data(function (d) {
return d.trueFalseCount;})
.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);
});
if(barChartLegentController==1){
var legend = svg.selectAll(".legend")
.data(testSuite.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; });
barChartLegentController=2;
}
endTime_barChart = new Date().getTime();
var totalbar = (endTime_barChart-startTime_barChart)/1000;
//alert('Total bar time : '+ totalbar+' seconds');
}
I'm not sure I fully understand what you are asking yet but the best way to identifying any element/entity is with an id, something like the following:
d3.select(this).attr(id, function(d, i) {return 'bar_' + i});
Add this inside the iterative function where you are creating your bars. In this way you will be able to select them from anywhere in your code with a d3.select('#bar_1).
If you only want to identify each bar it would be something like this:
var state = svg.selectAll(".TestSuite")
.data(data)
.enter().append("g")
.attr("id", function(d,i) {return 'bar_' + i})
.attr("class", "TestSuite")
.on("click", function(d,i) {
if(i==0){
Alert.render(d3.select(this).data()[0].FalseStatements);
}else{
Alert.render(d3.select(this).data()[0].TrueStatements);
}
})
.attr("transform", function (d) {
return "translate(" + x0(d.TestSuite) + ",0)";
});
In the case that you would like to identify each bar with an Id related to its contents (true or false statements) I would suggest something like the following:
var state = svg.selectAll(".TestSuite")
.data(data)
.enter().append("g")
.attr("class", "TestSuite")
.on("click", function(d,i) {
var barId;
if(i==0){
barId = 'falseBar_' + i;
Alert.render(d3.select(this).data()[0].FalseStatements);
}else{
barId = 'trueBar_' + i;
Alert.render(d3.select(this).data()[0].TrueStatements);
}
d3.select(this).attr('id', barId);
})
.attr("transform", function (d) {
return "translate(" + x0(d.TestSuite) + ",0)";
});
In any case, this will assign an unique Id to every bar (i.e. "bar_25" or "falseBar_14") to each bar, giving you an ideal way to identify each bar.
EDIT: After OP showed me the actual code they are working with, the following are my suggestions for a solution (which are actually on the same lines as the code above).
The code you should actually be tinkering with is the one below the code you posted. It is where the actual bars are rendered:
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); });
My suggestion to add an id attribute to each bar would be the following:
state.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("id", function(d, i) {return 'bar_' + i}) // <-- Edited line
.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); });
It is important that you understand why this, and not the code block you provided initially, is the pertinent one. As you well said, the first block renders each group of bars (hence the append("g") which stands for svg group). The second block starts with a append("rect") which means svg rectangle. This and other lines (i.e. style("fill")..., attr("x")... and attr("y")...) clearly give away that this block is the one dealing with the actual bars and not the groups.
I'm trying to build a linechart based off of the example at http://bl.ocks.org/mbostock/3884955. I'm not using temperatures but rather value levels from 0-1 that I call sentiments.
I'm not using a tsv file either, but rather rendering JSON in real time through an AJAX call where my data object is an array with objects in it:
[{date: '20140716', ESPN.com: 0.4, SI.com: 0.5})]
There are typically multiple dates in an array and at least 6-7 websites (they're included in every object).
The code looks like this:
function renderHistoricalData(array) {
var data = array;
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 1200 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%m%d").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("basis")
.defined(function(d) { return d.y!=0; })
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.sentiment); });
var svg = d3.select("#historical_chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
data.forEach(function(d) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
d.date = parseDate(d.date);
var sites = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, sentiment: +d[name]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(sites, function(c) { return d3.min(c.values, function(v) { return v.sentiment; }); }),
d3.max(sites, function(c) { return d3.max(c.values, function(v) { return v.sentiment; }); })
]);
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("Sentiment (%)");
var site = svg.selectAll(".site")
.data(sites)
.enter().append("g")
.attr("class", "site");
site.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
site.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.sentiment) + ")"; })
.attr("x", 3)
.attr("dy", ".35em")
// .style("text-anchor", "inherit")
.text(function(d) { return d.name; });
});
}
This results in something that looks like:
http://imgur.com/hFTItGp
Any ideas? I'd greatly appreciate your help. Thanks!
The root cause of the problem is that in the code
{name: d.name, value: d.values[d.values.length - 1]}
you're assuming that values are sorted by date, latest date last. In your data, the opposite is the case -- the latest date is first. Therefore, your text appears at the beginning of the x axis. To fix, simply sort your data.
I've fixed this and a few other things here.
I am trying to replicate the Multi-Series Line Chart example but I am unable to get the labels to show up at the end of the line or at all for that matter. The code is basically all the same as the example but a few words changed. The dataset is of county population and is formatted the same as in the example, earliest date at the top of the list and most recent date at the bottom of the list.
Anyone see anything I'm missing??
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%m%d").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.population); });
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("historicalpopulationTest.csv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var counties = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, population: +d[name]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(counties, function(c) { return d3.min(c.values, function(v) { return v.population; }); }),
d3.max(counties, function(c) { return d3.max(c.values, function(v) { return v.population; }); })
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("x", width)
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Year");
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("Population");
var county = svg.selectAll(".county")
.data(counties)
.enter().append("g")
.attr("class", "county");
county.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
county.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.population) + ")"; })
.attr("x", width)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
});
Graph looks like this
I think your problem is in the following code
county.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.population) + ")"; })
.attr("x", width)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
I haven't run your code, but it looks like you are translating the text to the end of the line and then moving the x position an additional 'width' number of pixels.
Try changing .attr("x", width) to .attr("x", 3)
I'm working from this code sample on Chained Transitions http://bl.ocks.org/mbostock/3903818
I've set up my own code to parse a csv file instead of a tsv. However when I switch from the New to Existing view I get the error Error parsing D3 d=""repeated two times in the Javascript console. Not sure if it is the data for each function that is causing the errors. Code is below:
var margin = {top: 20, right: 50, bottom: 30, left: 60},
width = 878 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%d%m").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.price); });
var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.text("a simple tooltip");
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
//Add grid lines
function make_X_axis() {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(6)
}
function make_Y_axis() {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(9)
}
//Parse data
d3.csv("data/newdata2.csv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
data.forEach(function(d) {
d.date = parseDate(d.date);
d["New"] = +d["New"];
d["Existing"] = +d["Existing"];
});
var homes = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, price: +d[name]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(homes, function(c) { return d3.min(c.values, function(v) { return v.price; }); }),
d3.max(homes, function(c) { return d3.max(c.values, function(v) { return v.price; }); })
]);
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(make_X_axis()
.tickSize(-height, 0, 0)
.tickFormat("")
)
svg.append("g")
.attr("class", "grid")
.call(make_Y_axis()
.tickSize(-width, 0, 0)
.tickFormat("")
)
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Thousands($)");
var city = svg.selectAll(".city")
.data(homes)
.enter().append("g")
.attr("class", "city");
city.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
city.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.price) + ")"; })
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
d3.selectAll("input").on("change", change);
var timeout = setTimeout(function() {
d3.select("input[value=\"Existing\"]").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(timeout);
city = this.value;
// First transition the line & label to the new city.
var t0 = svg.transition().duration(750);
t0.selectAll(".line").attr("d", line);
t0.selectAll(".label").attr("transform", transform).text(city);
// Then transition the y-axis.
y.domain(d3.extent(data, function(d) { return d[city]; }));
var t1 = t0.transition();
t1.selectAll(".line").attr("d", line);
t1.selectAll(".label").attr("transform", transform);
t1.selectAll(".y.axis").call(yAxis);
}
function transform(d) {
return "translate(" + x(d.date) + "," + y(d[city]) + ")";
}
});
In the code where you create the line, you are referencing d.values as the data to draw the path with:
city.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
However, in the change handler, you simply call (twice)
t0.selectAll(".line").attr("d", line);
Making the two bits of code consistent should fix the issue, i.e. change the two calls in the change function to
t0.selectAll(".line").attr("d", function(d) { return line(d.values); });