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
Related
I've got a line chart that needs circles on every other point for column A and C (not for column B). I've struggled to figure out how to do it. This is my line chart without the circles:
date,A=count,A=rank,B=count,B=rank,C=count,C=rank
2016-11-01,60588,213,51915,46,41200,10
2016-12-01,73344,216,58536,47,41230,10
2017-01-01,64164,219,53203,50,51220,12
2017-02-01,85295,224,34047,52,61000,15
2017-03-01,86089,226,44636,54,71200,16
2017-04-01,96871,230,55281,55,71000,10
2017-05-01,97622,234,85879,55,67900,10
I've tried dozens of solutions and I'm very stuck! Here is one of the things I've tried:
linesAndDots.selectAll("line-circle")
.data(data)
.enter().append("circle")
.attr("class", "data-circle")
.attr("r", 5)
.attr("cx", function(d) { return xScale(d.date); })
.attr("cy", function(d) { return yScale(d.measurement); });
But that is giving back NaN for the cx and cy values.
My full code looks like this:
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<style>
path.line-0 {
fill: none;
stroke: #1F77B4;
}
path.line-1 {
fill: none;
stroke: #FF7F0E;
}
</style>
</head>
<!-- Body tag is where we will append our SVG and SVG objects-->
<body>
</body>
<!-- Load in the d3 library -->
<script src="lib/d3.v5.min.js"></script>
<div id="svgdiv"></div>
<script>
//------------------------1. PREPARATION------------------------//
//-----------------------------SVG------------------------------//
var columns=['A=count','B=count'];
var columnsB=['A=rank','B=rank'];
var width = 960;
var height = 500;
var margin = 5;
var padding = 5;
var adj = 75;
// we are appending SVG first
var svg = d3.select("body").append("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "-"
+ adj + " -"
+ adj + " "
+ (width + adj *3) + " "
+ (height + adj*3))
.style("padding", padding)
.style("margin", margin)
.classed("svg-content", true);
//-----------------------------DATA-----------------------------//
var timeConv = d3.timeParse("%Y-%m-%d");
var formatTime = d3.timeFormat("%b %y")
var dataset = d3.csv("toShare.csv");
dataset.then(function(data) {
console.log(data.columns.slice(1))
var slices = columns.map(function(id) {
return {
id: id,
values: data.map(function(d){
return {
date: timeConv(d.date),
measurement: +d[id]
};
})
};
})
//----------------------------SCALES----------------------------//
var xScale = d3.scaleTime().range([0,width]);
var yScale = d3.scaleLinear().rangeRound([height, 0]);
xScale.domain(d3.extent(data, function(d){
return timeConv(d.date)}));
yScale.domain([(0), d3.max(slices, function(c) {
return d3.max(c.values, function(d) {
return d.measurement + 4; });
})
]);
//-----------------------------AXES-----------------------------//
var yaxis = d3.axisLeft()
.ticks(9)
.scale(yScale);
var xaxis = d3.axisBottom()
.ticks(7)
.scale(xScale);
//----------------------------LINES-----------------------------//
var line = d3.line()
.x(function(d) { return xScale(d.date); })
.y(function(d) { return yScale(d.measurement); });
let id = 0;
var ids = function () {
return "line-"+id++;
}
//-------------------------2. DRAWING---------------------------//
//-----------------------------AXES-----------------------------//
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(xaxis)
.append("text")
.attr("transform",
"translate(" + (width/2) + " ," +
50 + ")")
.style("text-anchor", "middle")
.text("Month");
svg.append("g")
.attr("class", "axis")
.call(yaxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - adj)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Running Total");
//----------------------------LINES-----------------------------//
var linesAndDots = svg.selectAll("lines")
.data(slices)
.enter()
.append("g");
linesAndDots.append("path")
.attr("class", ids)
.attr("d", function(d) { return line(d.values); });
linesAndDots.selectAll("line-circle")
.data(data)
.enter().append("circle")
.attr("class", "data-circle")
.attr("r", 5)
.attr("cx", function(d) {
console.log("id", id)
return 5; })
.attr("cy", function(d) { return 40; });
//Add the label on the right
linesAndDots.append("text")
.attr("class", ids)
.datum(function(d) {
return {
id: d.id,
value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) {
return "translate(" + (xScale(d.value.date) + 10)
+ "," + (yScale(d.value.measurement) + 5 ) + ")"; })
.attr("x", 5)
.text(function(d) { return d.id.replace("=count", ""); });
});
</script>
</body>
Thanks for the help!
The problem can be inspected by seeing what d really is in the cx and cy function.
cx's problem: you didnt parse the date string into a Date object like you did for the slices data; cy's problem: the data item has no measurement key.
You have used both data and slices. To make the code more consistent, I fix the code of circle drawing using slices too.
linesAndDots
.selectAll(".data-circle")
.data(d=>d.values) // `d` now is the one of the two entries of `slices`,
//and we want to use the `values` array of each entry.
.enter()
.append("circle")
.attr("class", "data-circle")
.attr("r", 5)
.attr("cx", function(d) {
return xScale(d.date);
})
.attr("cy", function(d) {
return yScale(d.measurement)
});
A demo here
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.
I have drawn the following chart with D3 Charting tool v4. I have attached the full code at the bottom of this post.
The red line is the target goal to be achieved. The following code block is drawing this line:
var targetGoalArr = [7];
svg.selectAll(".targetgoal")
.data(targetGoalArr)
.enter().append("line")
.attr("class", "targetgoal")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", y)
.attr("y2", y)
.style("stroke", "#cc0000");
Now I need to label this line with the text Growth Target (7) to the right of it and in two lines. The label has to be broken in two lines as well!
The following screenshot shows the desired output.
How can I achieve the above?
One more thing I am not able to draw is the Y-Axis baseline. In my chart (with red line) I am creating the horizontal lines using a custom tick array. Here is the code:
function draw_yAxis_gridlines() {
return d3.axisLeft(y)
.tickValues(yTicks);
}
svg.append("g")
.attr("class", "grid axis")
.call(draw_yAxis_gridlines()
.tickSize(-width)
);
However, if I do not use custom ticks for Y-Axis, the baseline appears but I am missing the horizontal grid lines. I have to display both at the same time.
Here is my full code:
public function evd_unitary_growth_plan_chart( $data_str ){
ob_start(); ?>
<style> /* set the CSS */
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.5;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
.axis {
font-size: 13px;
font-family: 'Roboto';
color: #808888;
}
</style>
<script type="text/javascript">
var h = 300;
var w = 750;
var barPadding = 2;
function barColor(data_month, current_month) {
if( parseInt(data_month) >= current_month)
return "#008600";
else
return "#c4c4c4";
}
// set the dimensions and margins of the graph
var margin = {top: 30, right: 20, bottom: 30, left: 40},
width = w - margin.left - margin.right,
height = h - margin.top - margin.bottom;
var data = <?php echo $data_str ?>;
// set the ranges
var x = d3.scaleBand().range([0, width]).padding(0.2);
var y = d3.scaleLinear().range([height, 0]);
var svg = d3.select("#ecbg_unitary").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 + ")");
// Scale the range of the data in the domains
x.domain(data.map(function(d) { return d.month; }));
var y_domain_upperBound = d3.max(data, function(d) { return d.points; });
y_domain_upperBound = Math.round(y_domain_upperBound / 10) * 10 + 10;
y.domain([0, y_domain_upperBound]);
// Create Y-Axis tick array to draw grid lines
var yTicks = [];
var tickInterval = 5;
for(var i = 0; i <= y_domain_upperBound; i = i + tickInterval) {
yTicks.push(i);
}
console.log(yTicks);
// gridlines in y axis function
function draw_yAxis_gridlines() {
return d3.axisLeft(y)
.tickValues(yTicks);
}
// Reference line - The red line
var targetGoalArr = [7];
svg.selectAll(".targetgoal")
.data(targetGoalArr)
.enter().append("line")
.attr("class", "targetgoal")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", y)
.attr("y2", y)
.style("stroke", "#cc0000");
// append the rectangles for the bar chart
svg.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function(d) {
return x(d.month);
})
.attr("width", x.bandwidth())
.attr("y", function(d) { return y(d.points); })
.attr("height", function(d) { return height - y(d.points); })
.attr("fill", function(d){return barColor(d.data_month_number, d.current_month_number)});
// column labels
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
return d.points;
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return x(d.month) + x.bandwidth() / 2;
})
.attr("y", function(d) {
return y(d.points) - 10;
})
.attr("font-family", "Roboto")
.attr("font-size", "13px")
.attr("font-weight", "bold")
.attr("fill", "#606668");
// add the x Axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the Y gridlines
svg.append("g")
.attr("class", "grid axis")
.call(draw_yAxis_gridlines()
.tickSize(-width)
);
</script>
<?php return ob_get_clean();
}
To add a label to your target line, you are best to create group (g) element, and then append a line and text element to it. The g element can be translated to the correct y position, so that the line and text can be positioned relatively to the g.
var targetGoalArr = [7];
var target = g.selectAll(".targetgoal")
.data(targetGoalArr)
.enter()
.append("g")
.attr("transform", function(d){
return "translate(0, " + y(d) +")"
})
target.append("line")
.attr("class", "targetgoal")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", 0) //these can be omitted
.attr("y2", 0)
.style("stroke", "#cc0000");
target.append("text")
.text(function(d){ return "Target growth: " + d })
.attr("x", width)
.attr("y", "0.35em")
I am trying to add a regression line to a scatter plot. I am using the below code as an example for scatter plot.
http://bl.ocks.org/majetisiri/57da501b3182bd08d17402261c7187f7
I am appending the path to svg as explained here:
Plot regression line on a scatter plot from regression coefficients
But the regression line is not visible.
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<!-- Example based on http://bl.ocks.org/mbostock/3887118 -->
<!-- Tooltip example from http://www.d3noob.org/2013/01/adding-tooltips-to-d3js-graph.html -->
<style>
body {
font: 11px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.dot {
stroke: #000;
}
.tooltip {
position: absolute;
width: 200px;
height: 28px;
pointer-events: none;
}
h1 {
text-align: center;
}
h2 {
text-align: left;
}
</style>
<body>
<p><span><label for="y-axis">Select y-axis</label></span>
<select id="y-value">
<option value="FLFPR">Female LFPR</option>
<option value="lnGDP">Log GDP per capita</option>
<option value="Fertility">Fertility rate</option>
</select>
<p><span><label for="x-axis">Select x-axis</label></span>
<select id="x-value">
<option value="FLFPR">Female LFPR</option>
<option value="lnGDP">Log GDP per capita</option>
<option value="Fertility">Fertility rate</option>
</select>
<button onclick="setGraph()">submit</button>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
<script src="https://unpkg.com/d3-regression#1.2.3/dist/d3-regression.min.js"></script>
<script>
function drawGraph(xText, yText) {
$('svg').remove();
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
/*
* value accessor - returns the value to encode for a given data object.
* scale - maps value to a visual display encoding, such as a pixel position.
* map function - maps from data value to display value
* axis - sets up axis
*/
// setup x
var xValue = function(d) { return d[xText];}, // data -> value
xScale = d3.scale.linear().range([0, width]), // value -> display
xMap = function(d) { return xScale(xValue(d));}, // data -> display
xAxis = d3.svg.axis().scale(xScale).orient("bottom");
// setup y
var yValue = function(d) { return d[yText];}, // data -> value
yScale = d3.scale.linear().range([height, 0]), // value -> display
yMap = function(d) { return yScale(yValue(d));}, // data -> display
yAxis = d3.svg.axis().scale(yScale).orient("left");
// setup fill color
var cValue = function(d) { return d.IG;},
color = d3.scale.category20();
// add the graph canvas to the body of the webpage
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 + ")");
// add the tooltip area to the webpage
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// load data
d3.csv("scatter2.csv", function(error, data) {
// change string (from CSV) into number format
data.forEach(function(d) {
d[yText] = +d[yText];
d[xText] = +d[xText];
//console.log (d.School);
//console.dir (d);
});
// don't want dots overlapping axis, so add in buffer to data domain
xScale.domain([d3.min(data, xValue)-1, d3.max(data, xValue)+1]);
yScale.domain([d3.min(data, yValue)-1, d3.max(data, yValue)+1]);
// x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text(xText);
// y-axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(yText);
// draw dots
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 6.6)
.attr("opacity", 0.9)
.style("stroke", function(d) { return color(cValue(d));})
.attr("cx", xMap)
.attr("cy", yMap)
.style("fill", function(d) { return color(cValue(d));})
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(d["Player"] + "<br/> " + d.School + "<br/>(" + xValue(d)
+ ", " + yValue(d) + ")")
.style("left", (d3.event.pageX + 10) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
// draw legend
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(10," + (i+7) * 20 + ")"; });
// draw legend colored rectangles
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
// draw legend text
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d;})
// get regression coefficients
regData = data.map(item => ({x: item[xText], y: item[yText]}));
res = drawRegressionLine(regData)
console.log("regression results")
console.log(res)
var line = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("linear");
// var line = d3.svg.line()
// .x(function(d) { return xScale(d['x']); })
// .y(function(d) { return yScale(d['y']); });
// var lineFunction = d3.svg.line()
// .x(function(d) { return d.x; })
// .y(function(d) { return d.y; })
// .interpolate("linear");
var regLine = svg.append("path")
.datum(res)
.attr("d", line)
.style("stroke", "steelblue")
.style("stroke-width", "6px");
// var lineGraph = svg.append("path")
// .attr("d", line(res))
// .attr("stroke", "blue")
// .attr("stroke-width", 2)
// .attr("fill", "black");
});
}
// draw regression line
function drawRegressionLine(regData) {
console.log("beginning")
console.log("inside draw regression lilne")
linearRegression = d3.regressionLinear()
.x(d => d.x)
.y(d => d.y);
res = linearRegression(regData)
return res;
}
// drawGraph('Passing TD', 'Rushing TD');
function setGraph() {
console.log("inside set graph")
console.log($('#x-value').val())
drawGraph($('#x-value').val(), $('#y-value').val());
}
</script>
</body>
</html>
Please help me find what is wrong with the code.
It looks like you are not passing the your linear regression into your x and y scale.
Try:
var line = d3.svg.line()
.x(function(d) { return xScale(d[0]); })
.y(function(d) { return yScale(d[1]); })
.interpolate("linear");
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)