Update multi-line graph with D3 - javascript

I am trying to play around with D3 and do some visualization using COVID-19 data, but I cannot seem to figure out how to update the multi-line graph with a button. This is what I currently have:
<!DOCTYPE html>
<meta charset="utf-8" />
<html lang="en">
<style>
.line1 {
fill: none;
stroke: darkcyan;
stroke-width: 2.5px;
}
.line2 {
fill: none;
stroke: red;
stroke-width: 2.5px;
}
.line3 {
fill: none;
stroke: green;
stroke-width: 2.5px;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
.legend rect {
fill: white;
stroke: black;
opacity: 0.8;
}
</style>
<head>
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<h1>Total Confirmed Coronavirus Cases in Germany</h1>
<div id="option">
<input name="updateButton" type="button" value="Linear" onclick="linear()" />
<input name="updateButton" type="button" value="Logarithmic" onclick="logarithmic()" />
</div>
<script>
// write your d3 code here..
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 1000 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.utcParse("%Y-%m-%dT%H:%M:%S%Z");
var formatDate = d3.timeFormat("%m-%d");
// set the ranges
var x = d3.scaleUtc().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var logy = d3.scaleLog().range([height, 0]);
// Define the axes
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var logyAxis = d3.axisLeft(logy);
// define the 1st line
var valueline1 = d3
.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.confirmed);
});
// define the 2nd line
var valueline2 = d3.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.deaths);
});
// define the 3rd line
var valueline3 = d3.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.recovered);
});
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
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 + ")");
// gridlines in x axis function
function x_gridlines() {
return d3.axisBottom(x)
}
// gridlines in y axis function
function y_gridlines() {
return d3.axisLeft(y)
}
// gridlines in y axis function
function logy_gridlines() {
return d3.axisLeft(logy)
}
d3.json(
"https://api.covid19api.com/total/dayone/country/germany"
).then(function(data) {
data.forEach(function(d) {
d.country = d.Country
d.date = parseTime(d.Date);
d.confirmed = d.Confirmed;
d.deaths = d.Deaths;
d.recovered = d.Recovered;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return Math.max(d.confirmed, d.deaths, d.recovered);
})]);
// Add the valueline path.
svg.append("path")
.data([data])
.attr("class", "line1")
.attr("d", valueline1);
// Add the valueline2 path.
svg.append("path")
.data([data])
.attr("class", "line2")
.attr("d", valueline2);
// Add the valueline3 path.
svg.append("path")
.data([data])
.attr("class", "line3")
.attr("d", valueline3);
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// add the X gridlines
svg.append("g")
.attr("class", "x grid")
.attr("transform", "translate(0," + height + ")")
.call(x_gridlines()
.tickSize(-height)
.tickFormat("")
)
// add the Y gridlines
svg.append("g")
.attr("class", "y grid")
.call(y_gridlines()
.tickSize(-width)
.tickFormat("")
)
legend = svg.append("g")
.attr("class", "legend")
.attr("transform", "translate(50,30)")
.style("font-size", "12px")
.call(d3.legend)
});
// ** Update data section (Called from the onclick)
function logarithmic() {
// Get the data again
d3.json(
"https://api.covid19api.com/total/dayone/country/germany"
).then(function(data) {
data.forEach(function(d) {
d.country = d.Country
d.date = parseTime(d.Date);
d.confirmedlog = Math.log(d.Confirmed);
d.deathslog = Math.log(d.Deaths);
d.recoveredlog = Math.log(d.Recovered);
});
// Scale the range of the data again
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0,
d3.max(data, function(d) {
return Math.max(d.confirmedlog, d.deathslog, d.recoveredlog);
})
]);
// Select the section we want to apply our changes to
var svg = d3.select("body").data(data).transition();
// Make the changes
svg.select(".line1") // change the line
.duration(750)
.attr("d", function(d) {
return valueline1(d.confirmedlog);
})
svg.select(".line2") // change the line
.duration(750)
.attr("d", function(d) {
return valueline1(d.deathslog);
});
svg.select(".line3") // change the line
.duration(750)
.attr("d", function(d) {
return valueline1(d.recoveredlog);
});
svg.select(".y.axis") // change the y axis
.duration(750)
.call(logyAxis);
svg.select(".y.grid")
.duration(750)
.call(logy_gridlines()
.tickSize(-width)
.tickFormat(""))
});
}
// ** Update data section (Called from the onclick)
function linear() {
// Get the data again
d3.json(
"https://api.covid19api.com/total/dayone/country/germany"
).then(function(data) {
data.forEach(function(d) {
d.country = d.Country
d.date = parseTime(d.Date);
d.confirmed = d.Confirmed;
d.deaths = d.Deaths;
d.recovered = d.Recovered;
});
// Scale the range of the data again
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return d.confirmed, d.deaths, d.recovered;
})]);
// Select the section we want to apply our changes to
var svg = d3.select("body").transition();
// Make the changes
svg.select(".line1") // change the line
.duration(750)
.attr("d", valueline1(data));
svg.select(".line2") // change the line
.duration(750)
.attr("d", valueline2(data));
svg.select(".line3") // change the line
.duration(750)
.attr("d", valueline3(data));
svg.select(".x.axis") // change the x axis
.duration(750)
.call(xAxis);
svg.select(".y.axis") // change the y axis
.duration(750)
.call(yAxis);
svg.select(".y.grid") // change the y gridlines
.duration(750)
.call(y_gridlines()
.tickSize(-width)
.tickFormat("")
);
});
}
</script>
</body>
</html>
What I'm having issues with is toggling from the default to the Logarithmic view. Whenever I click on the button to toggle, the lines all disappear. I have a function called: logarithmic() and the code within the function is as below:
function logarithmic() {
// Get the data again
d3.json(
"https://api.covid19api.com/total/dayone/country/germany"
).then(function (data) {
data.forEach(function (d) {
d.country = d.Country
d.date = parseTime(d.Date);
d.confirmedlog = Math.log(d.Confirmed);
d.deathslog = Math.log(d.Deaths);
d.recoveredlog = Math.log(d.Recovered);
});
// Scale the range of the data again
x.domain(d3.extent(data, function (d) { return d.date; }));
y.domain([0,
d3.max(data, function (d) {return Math.max(d.confirmedlog, d.deathslog, d.recoveredlog);
})]);
// Select the section we want to apply our changes to
var svg = d3.select("body").data(data).transition();
// Make the changes
svg.select(".line1") // change the line
.duration(750)
.attr("d", function(d) { return valueline1(d.confirmedlog); })
svg.select(".line2")// change the line
.duration(750)
.attr("d", function(d) { return valueline1(d.deathslog); });
svg.select(".line3") // change the line
.duration(750)
.attr("d", function(d) { return valueline1(d.recoveredlog); });
svg.select(".y.axis") // change the y axis
.duration(750)
.call(logyAxis);
svg.select(".y.grid")
.duration(750)
.call(logy_gridlines()
.tickSize(-width)
.tickFormat(""))
});
}
Any help would be greatly appreciated! Or if there are any best practices for updating multi-line graphs, any tips would be greatly appreciated as well. Thank you very much

Well, unfortunately your code has several issues and right now it's far from the best D3 (or JavaScript) practices.
Besides binding data to the body and reassigning selection names, among other things, the very first issue that caught my eye was downloading the (same) data again for the updates. That's completely unnecessary.
In this very quick refactor I'm simply calculating the log values already. Pay attention to the fact that logarithm of zero in JS is minus infinity. So, just check it:
d.confirmedlog = d.Confirmed ? Math.log(d.Confirmed) : 0;
d.deathslog = d.Deaths ? Math.log(d.Deaths) : 0;
d.recoveredlog = d.Recovered ? Math.log(d.Recovered) : 0;
Then, for each line, change the y method of the line generator. For instance:
svg.select(".line1") // change the line
.duration(750)
.attr("d", function(d) {
return valueline1.y(function(e) {
return y(e.confirmedlog);
})(d);
})
Here is the code:
<!DOCTYPE html>
<meta charset="utf-8" />
<html lang="en">
<style>
.line1 {
fill: none;
stroke: darkcyan;
stroke-width: 2.5px;
}
.line2 {
fill: none;
stroke: red;
stroke-width: 2.5px;
}
.line3 {
fill: none;
stroke: green;
stroke-width: 2.5px;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
.legend rect {
fill: white;
stroke: black;
opacity: 0.8;
}
</style>
<head>
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<h1>Total Confirmed Coronavirus Cases in Germany</h1>
<div id="option">
<input name="updateButton" type="button" id="option1" value="Linear" />
<input name="updateButton" type="button" id="option2" value="Logarithmic" />
</div>
<script>
// write your d3 code here..
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 1000 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.utcParse("%Y-%m-%dT%H:%M:%S%Z");
var formatDate = d3.timeFormat("%m-%d");
// set the ranges
var x = d3.scaleUtc().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var logy = d3.scaleLog().range([height, 0]);
// Define the axes
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var logyAxis = d3.axisLeft(logy);
// define the 1st line
var valueline1 = d3
.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.confirmed);
});
// define the 2nd line
var valueline2 = d3.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.deaths);
});
// define the 3rd line
var valueline3 = d3.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.recovered);
});
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
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 + ")");
// gridlines in x axis function
function x_gridlines() {
return d3.axisBottom(x)
}
// gridlines in y axis function
function y_gridlines() {
return d3.axisLeft(y)
}
// gridlines in y axis function
function logy_gridlines() {
return d3.axisLeft(logy)
}
d3.json(
"https://api.covid19api.com/total/dayone/country/germany"
).then(function(data) {
data.forEach(function(d) {
d.country = d.Country
d.date = parseTime(d.Date);
d.confirmed = d.Confirmed;
d.deaths = d.Deaths;
d.recovered = d.Recovered;
d.confirmedlog = d.Confirmed ? Math.log(d.Confirmed) : 0;
d.deathslog = d.Deaths ? Math.log(d.Deaths) : 0;
d.recoveredlog = d.Recovered ? Math.log(d.Recovered) : 0;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([0, d3.max(data, function(d) {
return Math.max(d.confirmed, d.deaths, d.recovered);
})]);
// Add the valueline path.
svg.append("path")
.data([data])
.attr("class", "line1")
.attr("d", valueline1);
// Add the valueline2 path.
svg.append("path")
.data([data])
.attr("class", "line2")
.attr("d", valueline2);
// Add the valueline3 path.
svg.append("path")
.data([data])
.attr("class", "line3")
.attr("d", valueline3);
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// add the X gridlines
svg.append("g")
.attr("class", "x grid")
.attr("transform", "translate(0," + height + ")")
.call(x_gridlines()
.tickSize(-height)
.tickFormat("")
)
// add the Y gridlines
svg.append("g")
.attr("class", "y grid")
.call(y_gridlines()
.tickSize(-width)
.tickFormat("")
)
d3.select("#option1").on("click", linear);
d3.select("#option2").on("click", logarithmic);
// ** Update data section (Called from the onclick)
function logarithmic() {
y.domain([0,
d3.max(data, function(d) {
return Math.max(d.confirmedlog, d.deathslog, d.recoveredlog);
})
]);
// Select the section we want to apply our changes to
var svg = d3.select("body").transition();
// Make the changes
svg.select(".line1") // change the line
.duration(750)
.attr("d", function(d) {
return valueline1.y(function(e) {
return y(e.confirmedlog);
})(d);
})
svg.select(".line2") // change the line
.duration(750)
.attr("d", function(d) {
return valueline1.y(function(e) {
return y(e.deathslog);
})(d);
});
svg.select(".line3") // change the line
.duration(750)
.attr("d", function(d) {
return valueline1.y(function(e) {
return y(e.recoveredlog);
})(d);
});
svg.select(".y.axis") // change the y axis
.duration(750)
.call(logyAxis);
svg.select(".y.grid")
.duration(750)
.call(logy_gridlines()
.tickSize(-width)
.tickFormat(""))
}
// ** Update data section (Called from the onclick)
function linear() {
y.domain([0, d3.max(data, function(d) {
return d.confirmed, d.deaths, d.recovered;
})]);
// Select the section we want to apply our changes to
var svg = d3.select("body").transition();
// Make the changes
svg.select(".line1") // change the line
.duration(750)
.attr("d", valueline1);
svg.select(".line2") // change the line
.duration(750)
.attr("d", valueline2);
svg.select(".line3") // change the line
.duration(750)
.attr("d", valueline3);
svg.select(".x.axis") // change the x axis
.duration(750)
.call(xAxis);
svg.select(".y.axis") // change the y axis
.duration(750)
.call(yAxis);
svg.select(".y.grid") // change the y gridlines
.duration(750)
.call(y_gridlines()
.tickSize(-width)
.tickFormat("")
);
};
});
</script>
</body>
</html>
However, I'm afraid that your code has so many issues that it should be completely refactored (please don't take it personally). Now that it's a (kind of) working code I believe you can ask for help on Code Review. But, if you do, make sure before posting that you read their help section, since Code Review is quite different from Stack Overflow.

Related

D3.js add circle points to line graph

I'm brand new to D3.js and I'm trying to add a circle point on the graph where each data point is.
This is an example of the raw data:
TIME,GEO,CITIZEN,GENDER,VALUE
2011M01,Germany (until 1990 former territory of the FRG),Belgium,Males,0
But in the code, I format this into a variable called nestedData, and then using this plot two line graphs UK.values and germany.values.
How can I add circle points onto each of the two line graphs?
Thanks.
This is what I have currently:
<html>
<head>
<script src="https://d3js.org/d3.v5.js"></script>
<title> D3 Tutorial </title>
<style>
body {
margin: 20px;
}
svg {
padding: 50px;
}
.line {
fill: none;
stroke: black;
}
#UKLine {
fill: none;
stroke: red;
}
#germanyLine {
fill: none;
stroke: blue;
}
</style>
</head>
<body>
<h2>D3 Linegraph</h2>
<script>
var dataPath = "data/migration.csv";
var width = 800; //specifies the width, height and margins of our SVG element
var height = 600;
var margin = 100;
var rowConverter = function (d) {
var timeData = d.TIME;
var datum = timeData.split("M");
return {
date: new Date(datum[0], datum[1] - 1),
geo: d.GEO,
value: d.VALUE
}
};
d3.csv(dataPath, rowConverter)
.then(function (data) {
console.log(data);
// console.table(data); //loads table in a nice format - just to try it out (probably not super practical for this tutorial)
var nestedData = d3.nest()
.key(function (d) {
return d.geo;
})
.key(function (d) {
return d.date;
})
.rollup(function (leaves) {
return d3.sum(leaves, function (d) {
return parseInt(d.value);
});
})
.entries(data);
console.log(nestedData);
//work out time extent of ALL THE DATA NOT JUST THE NESTED ONES
//work with unnested data
var timeExtent = d3.extent(data, function (d) {
return d.date;
});
console.log(timeExtent);
var xScale = d3.scaleTime().domain(timeExtent).range([0, width]);
var migrantCounts = new Array();
nestedData.forEach(function (d) {
(d.values).forEach(function (e) {
migrantCounts.push(e.value);
});
});
console.log(migrantCounts);
var migrantExtent = d3.extent(migrantCounts, function (d) {
return parseInt(d);
});
console.log(migrantExtent);
var yScale = d3.scaleLinear().domain(migrantExtent).range([height, 0]);
var x_axis = d3.axisBottom(xScale);
var y_axis = d3.axisLeft(yScale);
var svg = d3.select("body") //creates an SVG element in the body
.append("svg")
.attr("width", width + margin)
.attr("height", height + margin);
d3.select("svg")
.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + margin + "," + height + ")")
.call(x_axis
.tickFormat(d3.timeFormat("%y-%m-%d")))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-0.8em")
.attr("dy", "0.15em")
.attr("transform", "rotate(-65)");
d3.select("svg")
.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin + ",0)")
.call(y_axis);
var germany = nestedData[0];
var UK = nestedData[1];
var lineGenerator = d3.line()
.x(function (d) {
return margin + xScale(new Date(d.key));
})
.y(function (d) {
return yScale(parseInt(d.value));
});
svg.append("path")
.datum(germany.values)
.attr("class", "line")
.attr("id", "germanyLine")
.attr("d", lineGenerator);
svg.append("path")
.datum(UK.values)
.attr("class", "line")
.attr("id", "UKLine")
.attr("d", lineGenerator);
});
</script>
</body>
</html>
you probably need to add two more steps after you create the paths
// these two selections are adding paths
svg.append("path")
.datum(germany.values)
.attr("class","line")
.attr("id","germanyLine")
.attr("d",lineGenerator);
svg.append("path")
.datum(UK.values)
.attr("class","line")
.attr("id", "UKLine")
.attr("d", lineGenerator);
//you want to add circles
svg.selectAll(".circle-germany")
.data(germany.values)
.join("circle") // enter append
.attr("class", "circle-germany")
.attr("r", "1") // radius
.attr("cx", d=> margin + xScale(new Date(d.key))) // center x passing through your xScale
.attr("cy", d=> yScale(parseInt(d.value))) // center y through your yScale
svg.selectAll(".circle-uk")
.data(UK.values)
.join("circle") // enter append
.attr("class", "circle-uk")
.attr("r", "1") // radius
.attr("cx", d=> margin + xScale(new Date(d.key))) // center x passing through your xScale
.attr("cy", d=> yScale(parseInt(d.value))) // center y through your yScale

Multiples with x/y axis and mouseover example - line path shows wrong values

I am trying to combine severeal D3.js examples based on example. I managed to get mouseover for each multiples chart in part working (values are not displayed at mouse pointer yet but via console.log). By checking those values I realized that my line paths at the upper two charts are off in relation to the Y-Axis, also causing the mouseover focus to be in the wrong place. I am new to D3, so I am still having trouble to pin down the problem beeing caused by domain/scale/axis etc. You can see the example here
This is my code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
margin: 0;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
//shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.area {
//fill: #e7e7e7;
fill: transparent;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: none;
stroke: steelblue;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
var margin = {top: 8, right: 10, bottom: 20, left: 30},
width = 960 - margin.left - margin.right,
height = 138 - margin.top - margin.bottom;
var parseDate = d3.time.format("%b %Y").parse,
bisectDate = d3.bisector(function(d) { return d.date; }).left,
formatValue = d3.format(",.2f"),
formatCurrency = function(d) { return formatValue(d); };
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var area = d3.svg.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.price); });
var line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.price); });
var xAxis = d3.svg.axis()
.scale(x) // x is the d3.time.scale()
.orient("bottom") // the ticks go below the graph
.ticks(4); // specify the number of ticks
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(4);
d3.csv("stocks_chart2.csv", type, function(error, data) {
// Nest data by symbol.
var symbols = d3.nest()
.key(function(d) { return d.symbol; })
.entries(data);
// Compute the maximum price per symbol, needed for the y-domain.
symbols.forEach(function(s) {
s.maxPrice = d3.max(s.values, function(d) { return d.price; });
});
// Compute the minimum and maximum date across symbols.
// We assume values are sorted by date.
x.domain([
d3.min(symbols, function(s) { return s.values[0].date; }),
d3.max(symbols, function(s) { return s.values[s.values.length - 1].date; })
]);
// Add an SVG element for each symbol, with the desired dimensions and margin.
var svg = d3.select("body").selectAll("svg")
.data(symbols)
.enter().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 the area path elements. Note: the y-domain is set per element.
svg.append("path")
.attr("class", "area")
.attr("d", function(d) { y.domain([0, d.maxPrice]); return area(d.values); });
// Add the line path elements. Note: the y-domain is set per element.
svg.append("path")
.attr("class", "line")
.attr("d", function(d) { y.domain([0, d.maxPrice]); return line(d.values); });
// Add a small label for the symbol name.
svg.append("text")
.attr("x", width - 6)
.attr("y", height - 6)
.style("text-anchor", "end")
.text(function(d) { return d.key; });
svg.append('g') // create a <g> element
.attr('class', 'x axis') // specify classes
.attr("transform", "translate(0," + height + ")")
.call(xAxis); // let the axis do its thing
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("Value");
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
function mousemove() {
var date, index;
date = x.invert(d3.mouse(this)[0]);
index = 0;
var focus = svg.selectAll(".focus");
focus.attr("transform", function(d) {
index = bisectDate(d.values, date, 0, d.values.length - 1);
console.log(index, d.values[index].symbol, d.values[index].date, d.values[index].price);
return "translate(" + x(d.values[index].date) + "," + y(d.values[index].price) + ")"
});
focus.selectAll("text", function(d) {
return formatCurrency(d.values[index].price);
});
}
});
function type(d) {
d.price = +d.price;
d.date = parseDate(d.date);
return d;
}
</script>
How do I assign the correct Y-Axis to each individual multiples chart causing the line path and mouseover values to be at the correct position? Any help would be greatly appreciated! Thank you!
This is an interesting problem. The example you link to uses a single y scale and yAxis for all 4 sub-plots. In your situation, though, your data has a very different domain for each sub-plot and when you add the dynamic mouse over a shared scale just won't work. So, my solution would be to create a different y scale and yAxis for each subplot.
...
// variable to hold our scales
var ys = {};
var area = d3.svg.area()
.x(function(d) {
return x(d.date);
})
.y0(height)
.y1(function(d) {
return ys[d.symbol](d.price); //<-- call the y function matched to our symbol
});
var line = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d, i) {
return ys[d.symbol](d.price); //<-- call the y scale function matched to our symbol
});
...
// for each symbol create our scale
symbols.forEach(function(s) {
var maxPrice = d3.max(s.values, function(d) {
return d.price;
});
ys[s.key] = d3.scale.linear() //<-- create a scale for each "symbol" (ie Sensor 1, etc...)
.range([height, 0])
.domain([0, maxPrice]);
});
...
// build 4 y axis
var axisGs = svg.append("g"); //<-- create a collection of axisGs
axisGs
.attr("class", "y axis")
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value");
axisGs.each(function(d, i) { //<-- for each axisG create an axis with it's scale
var self = d3.select(this);
self.call(
d3.svg.axis()
.scale(ys[d.key])
.orient("left")
.ticks(4)
);
});
...
// adjust mouseover to use appropriate scale
focus.attr("transform", function(d) {
index = bisectDate(d.values, date, 0, d.values.length - 1);
console.log(index, d.values[index].symbol, d.values[index].date, d.values[index].price);
return "translate(" + x(d.values[index].date) + "," + ys[d.key](d.values[index].price) + ")"; //<-- finally in our mouse move use the appropriate scale
});
Fully working code here.
As far as best practices are concerned when you are dealing with n number of datasets you have to go for n number of y scales and their corresponding y axis. It is good for seperation of concern and keeps the visulization intact. here you can see the example.
http://grafitome.github.io/advanced-charts.html#(first chart)

Formatting issue in D3

I am having a formatting issue with a d3 graphical representation.
The X axis is entered in years, which I thought would be easier to treat as any other number (instead of as dateFormat... %Y )
The output is automatically (?) adds the comma for the thousandths place- my assumption.
Is there a way to keep this the 4 dig int that it is?
(this is my first foray into HTML, CSS, JS and d3-- thank you for empathy/comments)
Thanks.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body { font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 30, right: 40, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
var x = d3.scale.linear().range([0, width]);
var y0 = d3.scale.linear().range([height, 0]);
var y1 = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxisLeft = d3.svg.axis().scale(y0)
.orient("left").ticks(5);
var yAxisRight = d3.svg.axis().scale(y1)
.orient("right").ticks(5);
var valueline = d3.svg.line()
.x(function(d) { return x(d.year); })
.y(function(d) { return y0(d.freq); });
var valueline2 = d3.svg.line()
.x(function(d) { return x(d.year); })
.y(function(d) { return y1(d.fn); });
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 + ")");
// Get the data
d3.csv("duallines2.csv", function(error, data) {
data.forEach(function(d) {
d.year = +(d.year);
d.freq = +d.freq;
d.fn = +d.fn;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.year; }));
y0.domain([0, d3.max(data, function(d) {
return Math.max(d.freq); })]);
y1.domain([0, d3.max(data, function(d) {
return Math.max(d.fn); })]);
svg.append("path") // Add the valueline path.
.attr("d", valueline(data));
svg.append("path") // Add the valueline2 path.
.style("stroke", "red")
.attr("d", valueline2(data));
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.style("fill", "steelblue")
.call(yAxisLeft);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + " ,0)")
.style("fill", "red")
.call(yAxisRight);
});
</script>
</body>
The datafile looks like this:
"duallines2.csv"
year,freq,fn
1950,58.13,3.41
1951,53.98,4.55
1952,67.00,6.78
1953,89.70,7.85
1954,99.00,8.92
1955,130.28,9.92
1956,166.70,10.13
1957,234.98,12.23
1958,345.44,13.45
1959,443.34,16.04
1960,543.70,18.03
1961,580.13,21.02
1962,605.23,22.34
1963,622.77,20.15
1964,626.20,21.26
1965,628.44,31.04
1966,636.23,35.04
1967,633.68,41.02
1968,624.31,43.05
1969,629.32,46.03
1970,618.63,51.03
1971,599.55,53.42
1973,609.86,57.82
1974,617.62,59.01
1975,614.48,56.03
1976,606.98,58.01
You can define your own format -- in this case making each xAxis tick label an integer -- and then tell the xAxis to use that format:
var format = d3.format("d");
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
.tickFormat(format); // <----- tell the xAxis to use your custom formatter
And here's the updated output you'll get:
See the docs on formatting numbers here.

Line graph dynamic data updates in D3

I have been working through the "simple graph" example in the D3 Tips and Tricks book here: https://leanpub.com/D3-Tips-and-Tricks/read
And I am working on the Update Data Dynamically section, but its not working and I cannot figure out why. The alternate data is being read, the X and Y axis are being adjusted, but the value line itself is never updated. I am using v3.4.6 of D3 and tested with the latest Firefox and Chrome. Can someone help me spot the error?
Example HTML:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body { font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<body>
<script type="text/javascript" src="d3/d3.js"></script>
<script>
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
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 + ")");
// Get the data
d3.csv("simple.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
svg.append("path") // Add the valueline path.
.attr("d", valueline(data));
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
});
function updateData() {
// Get the data again
d3.csv("simple-alt.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// Scale the range of the data again
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
// Select the section we want to apply our changes to
var svg = d3.select("body").transition();
// Make the changes
svg.select(".line") // change the line
.duration(750)
.attr("d", valueline(data));
svg.select(".x.axis") // change the x axis
.duration(750)
.call(xAxis);
svg.select(".y.axis") // change the y axis
.duration(750)
.call(yAxis);
});
}
</script>
<div id="option">
<input name="updateButton"
type="button"
value="Update"
onclick="updateData()"
/>
</div>
</body>
Example Data:
date,close
1-May-12,58.13
30-Apr-12,53.98
27-Apr-12,67.00
26-Apr-12,89.70
25-Apr-12,99.00
24-Apr-12,130.28
23-Apr-12,166.70
20-Apr-12,234.98
19-Apr-12,345.44
18-Apr-12,443.34
17-Apr-12,543.70
16-Apr-12,580.13
13-Apr-12,605.23
12-Apr-12,622.77
11-Apr-12,626.20
10-Apr-12,628.44
9-Apr-12,636.23
5-Apr-12,633.68
4-Apr-12,624.31
3-Apr-12,629.32
2-Apr-12,618.63
30-Mar-12,599.55
29-Mar-12,609.86
28-Mar-12,617.62
27-Mar-12,614.48
26-Mar-12,606.98
Example Alt Data:
date,close
10-May-12,99.55
8-May-12,76.86
6-May-12,67.62
4-May-12,64.48
2-May-12,60.98
1-May-12,58.13
30-Apr-12,53.98
27-Apr-12,67.00
26-Apr-12,89.70
25-Apr-12,99.00
24-Apr-12,90.28
23-Apr-12,106.70
20-Apr-12,94.98
19-Apr-12,85.44
18-Apr-12,73.34
17-Apr-12,53.70
16-Apr-12,50.13
13-Apr-12,65.23
12-Apr-12,62.77
11-Apr-12,66.20
10-Apr-12,68.44
9-Apr-12,66.23
5-Apr-12,63.68
4-Apr-12,64.31
3-Apr-12,69.32
2-Apr-12,61.63
You haven't declared the path to be of class line. So your later selection svg.select(".line") is empty. Working demo here.

Series labels in Multi-line chart in d3.js

I'm trying to do a multi-series line chart like the example here http://bl.ocks.org/mbostock/3884955
the only difference is i'm trying to do a transition between two sets of data. The transition works absolutely fine but I have no idea how to begin to go about getting the series labels to align with each line as is the case in that example.
Does anyone have any thoughts about how to go about it? My code is set up in a slightly different manner than the above example so I'm not able to follow it strictly. But even when I do, the data labels dont come through...
Here's my code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke-width: 1.5px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
var width = 550;
var height = 600;
var padding=60;
var parseDate = d3.time.format("%Y").parse;
var x = d3.time.scale().range([padding, width-padding]);
var y = d3.scale.linear().range([height-padding, padding]);
var z = d3.scale.category10();
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left");
var svg = d3.select("body").append("svg").attr("width", width).attr("height", height)
//load CSV
d3.csv("diverge-nonscaled1950.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.India=+d.India;
d.China=+d.China;
});
//compute column names
var seriesNames = d3.keys(data[0]).filter(function(d) { return d !== "date"; })
.sort();
//this is dataset one: data is scaled to column name INdia
var series = seriesNames.map(function(series) {
return data.map(function(d) {
return {x: d.date, y: ((+d[series]/d.India)*100)};
});
});
//this is dataset two: data is scaled to column name China
var series1 = seriesNames.map(function(series1) {
return data.map(function(d) {
return {x: d.date, y: ((+d[series1]/d.China)*100)};
});
});
var line = d3.svg.line()
.interpolate("basis")
// .defined(function(d) { return d.country>0; })
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); });
//domains
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0,2300]);
z.domain(seriesNames)
//axes
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height-padding) + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("x",0-(height/2.5))
.attr("y", -50)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style("font-size",11)
.text("Relative per Capita Incomes");;
//draw path
svg.selectAll(".line")
.data(series)
.enter().append("path")
.attr("class", "line")
.attr("d", line)
.attr("stroke",function(d, i) { return z(i); })
//first transition from dataset one to two
d3.select("svg")
.on("click",file1);
function file1(){
svg.selectAll(".line").data(series1)
.transition().duration(750)
.attr("d", line)
.attr("stroke",function(d, i) { return z(i); })
d3.select("svg")
.on("click",file2);
}
//second transition back to one
function file2(){
svg.selectAll(".line").data(series)
.transition().duration(750)
.attr("d", line)
.attr("stroke",function(d, i) { return z(i); })
d3.select("svg")
.on("click",file1);
}
});
</script>
</body>
Thanks very much for any help whatsoever...
Avin
I had trouble with this a while ago as well. As in the example you posted, you have to use d3's selection.datum() method, which snags data for individual elements. You will then select the final data point in each series using thing[thing.length - 1] and use the x and y values of that point to perform a transform/translate on your text. The relevant part of the code is:
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) + ")";
})
Then the animation is just a matter of updating the transformation, with something like:
city.selectAll('text').transition()
.attr("transform", function(d) {
return "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")";
})
You can also view a chart I made a while ago that seems to be similar to what you're trying to do.

Categories

Resources