Series labels in Multi-line chart in d3.js - javascript

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.

Related

Update multi-line graph with D3

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.

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

limit number of tick marks in d3.js x axis with ordinal scale

I have been exploring possibilities of limiting the number of tick marks in d3.js so that it doesn't get overloaded with text if data set is relatively large. I did have a look at http://bl.ocks.org/mbostock/3212294 and documentation around the ticks() and tickValues() and this seemed to me like it would work, but it did not draw anything:
HTML:
<!DOCTYPE html>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
CSS:
<style>
body {
font: 10px Arial;
}
.axis path {
fill: none;
stroke: grey;
shape-rendering: crispEdges;
}
.axis text {
font-family: Arial;
font-size: 10px;
}
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
JAVASCRIPT:
function renderAreaChart() {
var data = [{"name":"24-Apr-07","value":93.24},{"name":"25-Apr-07","value":95.35},{"name":"26-Apr-07","value":98.84},{"name":"27-Apr-07","value":99.92},{"name":"30-Apr-07","value":99.8},{"name":"1-May-07","value":99.47},{"name":"2-May-07","value":100.39},{"name":"3-May-07","value":100.4},{"name":"4-May-07","value":100.81},{"name":"7-May-07","value":103.92},{"name":"8-May-07","value":105.06},{"name":"9-May-07","value":106.88},{"name":"10-May-07","value":107.34},{"name":"11-May-07","value":108.74},{"name":"14-May-07","value":109.36},{"name":"15-May-07","value":107.52},{"name":"16-May-07","value":107.34},{"name":"17-May-07","value":109.44},{"name":"18-May-07","value":110.02},{"name":"21-May-07","value":111.98},{"name":"22-May-07","value":113.54},{"name":"23-May-07","value":112.89},{"name":"24-May-07","value":110.69},{"name":"25-May-07","value":113.62},{"name":"29-May-07","value":114.35},{"name":"30-May-07","value":118.77},{"name":"31-May-07","value":121.19},{"name":"1-Jun-07","value":118.4},{"name":"4-Jun-07","value":121.33},{"name":"5-Jun-07","value":122.67},{"name":"6-Jun-07","value":123.64},{"name":"7-Jun-07","value":124.07},{"name":"8-Jun-07","value":124.49},{"name":"11-Jun-07","value":120.19},{"name":"12-Jun-07","value":120.38},{"name":"13-Jun-07","value":117.5},{"name":"14-Jun-07","value":118.75},{"name":"15-Jun-07","value":120.5},{"name":"18-Jun-07","value":125.09},{"name":"19-Jun-07","value":123.66},{"name":"20-Jun-07","value":121.55},{"name":"21-Jun-07","value":123.9},{"name":"22-Jun-07","value":123},{"name":"25-Jun-07","value":122.34},{"name":"26-Jun-07","value":119.65},{"name":"27-Jun-07","value":121.89},{"name":"28-Jun-07","value":120.56},{"name":"29-Jun-07","value":122.04},{"name":"2-Jul-07","value":121.26},{"name":"3-Jul-07","value":127.17},{"name":"5-Jul-07","value":132.75},{"name":"6-Jul-07","value":132.3},{"name":"9-Jul-07","value":130.33},{"name":"10-Jul-07","value":132.35},{"name":"11-Jul-07","value":132.39},{"name":"12-Jul-07","value":134.07},{"name":"13-Jul-07","value":137.73},{"name":"16-Jul-07","value":138.1},{"name":"17-Jul-07","value":138.91},{"name":"18-Jul-07","value":138.12},{"name":"19-Jul-07","value":140},{"name":"20-Jul-07","value":143.75},{"name":"23-Jul-07","value":143.7},{"name":"24-Jul-07","value":134.89}];
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 800 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(data.map(function (d) { return d.name; }))
.rangePoints([0, width], 1);
var y = d3.scale.linear()
.range([height, 0]);
var newArray = [];
var stepSize = Math.floor(data.length / 4);
for (var index in data){
if (index % 3 === stepSize){
newArray.push(data[index]["name"])
}
};
var xAxis = d3.svg.axis()
.scale(x)
.tickValues(newArray)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function (d) { return x(d.name); })
.y(function (d) { return y(d.value); });
var area = d3.svg.area()
.x(function (d) { return x(d.name); })
.y0(height)
.y1(function (d) { return y(d.value); });
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 + ")");
data.forEach(function (d) {
d.name = d.name;
d.value = +d.value;
});
y.domain([0, 650]);
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("Label");
svg.append("path")
.datum(data)
.attr("d", area)
.attr("fill", "#FF0000");
}
renderAreaChart();
RENDER:
As you can see for some reason (and I am sure its my faulty understanding of javascript and d3 since i am a noob), it doesn't draw any tick marks. I will appreciate if someone would be able to help me with this.
It looks like your logic is ok, but some specifics might be steering you wrongly. Have you debugged by outputting newArray after you populate it?
I got an empty array when I ran your code, because your check:
index % 3 === stepSize;
doesn't work with this dataset.
The LHS (left-hand-side) of the equation will be either 0, 1, 2, depending on the value of indx. The RHS (right-hand-side) is 16, for the specific dataset that you have here. 0, 1, 2 will never equal 16, so you wind up with an empty newArray, and thus no tickValues.
You can try, for example:
newArray = data
.filter(function(d, i) { return !(i % 4); })
.map(function(d) { return d.name; })
to give you every fourth tick.
I also found this example:
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickValues(data.map( function(d,i)
{
if(i % 9 ===0 ) {
return d.name;
}
})
.filter(function (d)
{
return !!d;
}
));
it seems to be just what i need to filter out every nth tick value without generating an additional array.

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)

D3.js Multi-Series Chart with Y value tracking

Working solution: Right now I'm working on styling and on solving some of the issues regarding my problem with creating chart consisting of multiple-data series with values tracking. I will try as soon as I can to give you a sample of working code soo if anybody will came across the same or similar problem as I did, could work on it as a base. For now most of the tips which I used are in the comments below.
This will be my first question on StackOverflow and I'm looking forward to seeing what answers you might have to my problem.
Recently I got project in which I have to write Javascript code for generating charts and in which I would be able to read Y values from every line of the chart at the same time. I very new to D3 framework and by now I'm able to read csv data, create multi-series chart and track and read Y value but only when I'm creating chart from a single data series. I was trying to create multiple similar functions that would track data from diferent series of data but it won't work and in console i see that the Y is showing as null from what I can understand. I was using examples from D3 website to try to learn it and for now code will be very similar to those examples.
Later on I would need to do some other things with it but i think that after solving that problem i will be able to keep going. There will be like:
reduce data from csv by code because I will need to delete header infromation
change visual style of the chart and edit axis scaling
For now I have something like that. Sorry if it is a little bit messy but I'm still learning and trying a lot of different things. I have added also screenshot from what it looks like for me and some console information that i could get. I hope it will help you guys see what I'm doing wrong and what I would need to learn. Also this is not my only approach and it would be too long to show them all.
EDIT: I'm trying a little bit different approach. On the bottom of the page i will show what I have done by now.
EDIT2: Sorry if i was't precise enough about my goal. What I'm trying to do with this is I want to be able to read all Y-axis values of drawn lines (it will be 4 of them) at the same time on one X-axis value. I have added screenshot of the second code in which I'm able to read only one Y-axis value and can't read the over one.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: none;
stroke: steelblue;
}
</style>
<body>
<script src="d3.min.js"></script>
<script>
var margin = {top: 20, right: 80, bottom: 30, left: 200},
//-margin.left
width = 960 - margin.right,
height = 750 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y-%M-%d %H:%M").parse,
//dodane do sledzenia myszy i rysowania kuleczek
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 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.transfers); });
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("data2.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 bitrates = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, transfers: +d[name]};
})
};
});
console.log(bitrates);
//data.sort(function(a, b) {
//return a.date - b.date;
//});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(bitrates, function(c) { return d3.min(c.values, function(v) { return v.transfers; }); }),
d3.max(bitrates, function(c) { return d3.max(c.values, function(v) { return v.transfers; }); })
]);
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("Transfers");
var chart = svg.selectAll(".chart")
.data(bitrates)
.enter().append("g")
.attr("class", "chart");
chart.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
//.attr("d", line);
.style("stroke", function(d) { return color(d.name); });
chart.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.transfers) + ")"; })
.attr("x", 3)
.attr("dy", ".35em");
//.text(function(d) { return d.name; });
//sledzenie myszy i rysowanie kuleczek
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("g").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 x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")");
focus.select("text").text(formatCurrency(d.value));
}
});
</script>
It looks like for me like this:
Generated chart
CSV data file looks like this:
date,Średni wych.:,Średni wch.:,Maks. wych.:,Maks. wch.:
2014-02-14 15:40,86518717581,101989990772,88304882317,108036052338
2014-02-14 16:00,85739038102,98312113056,87060053514,107154908503
Some over information that I inspected while trying to understand what is wrong:
[Object, Object, Object, Object]
0: Object
name: "Średni wych.:"
values: Array[504]
__proto__: Object
1: Object
2: Object
name: "Maks. wych.:"
values: Array[504]
[0 … 99]
[100 … 199]
100: Object
date: Thu Jan 16 2014 01:00:00 GMT+0100 (Środkowoeuropejski czas stand.)
transfers: 49305177944
__proto__: Object
101: Object
date: Thu Jan 16 2014 01:20:00 GMT+0100 (Środkowoeuropejski czas stand.)
transfers: 42169641572
__proto__: Object
102: Object
date: Thu Jan 16 2014 01:40:00 GMT+0100 (Środkowoeuropejski czas stand.)
transfers: 39400112189
__proto__: Object
103: Object
104: Object
105: Object
106: Object
107: Object
108: Object
109: Object
110: Object
I would really appreciate any help from you. I know some Object Oriented Programming, HTML, CSS, but for now I wasn't really working with any framework and it is fun to learn but on the over hand could be really frustrating while trying to figure out what the heck I'm doing wrong.
EDIT
Now I'm trying drawing two lines separately. It is working great and it could make it easier for me to change lines style later on. Now i need to use mousemove function for each of those lines. Then it would be fairly easy to just pass readed values to some variables and show them in some box or something.
This is the code for the my second try(sorry for post getting long):
Screenshot for the second code is called Chart2.jpg
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: none;
stroke: steelblue;
}
</style>
<body>
<script src="d3.js"></script>
<script>
var margin = {top: 20, right: 50, bottom: 30, left: 100},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%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 xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
var valueline2 = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.open); });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
d.open = +d['open data'];
});
data.sort(function(a, b) {
return a.date - b.date;
});
x.domain([data[0].date, data[data.length - 1].date]);
y.domain([0, d3.max(data, function(d) { return Math.max(d.close, d.open); })]);
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("Price ($)");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg.append("path")
.datum(data)
.attr("class", "line")
.style("stroke", "red")
.attr("d", valueline2);
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", mousemove1)
.on("mousemove", mousemove2);
function mousemove1() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus = focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
focus.select("text").text(formatCurrency(d.close));
}
function mousemove2() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus = focus.attr("transform", "translate(" + x(d.date) + "," + y(d.open) + ")");
focus.select("text").text(formatCurrency(d.open));
}
});
</script>
You've got all the basic code there, you just need to get it all to run at the same time.
The first problem is that you're setting two different "mousemove" event handlers on the same elements. Unless you use namespaces to distinguish them, the second function just replaces the first, so your first function is never getting called. Rather than creating two event handlers with different namespaces, it's much easier to just put all your event-handling code into one function.
The second problem is that you only have one "focus" element, and so even if you did run both functions to set the two different tooltip contents and position, only the second version would be displayed, because it just replaces the first.
So to recap, you need to: create a tooltip/focus element for each path, and then have one event-handling function that sets all the values and positions according to the appropriate column of your data file.
To keep the code concise, and to allow you to quickly switch from two lines to four or more, I'm going to suggest that you create the focus elements as a data-joined selection, where the data is an array of column names:
var columnNames = d3.keys( data[0] ) //grab the key values from your first data row
//these are the same as your column names
.slice(1); //remove the first column name (`date`);
var focus = svg.selectAll("g")
.data(columnNames)
.enter().append("g") //create one <g> for each columnName
.attr("class", "focus")
.style("display", "none");
focus.append("circle") //add a circle to each
.attr("r", 4.5);
focus.append("text") //add a text field to each
.attr("x", 9)
.attr("dy", ".35em");
Now, when you show or hide focus in the mouseover/mouseout events, it will show or hide all the tooltips:
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);
But what should you do in your mousemove function? The first part, figuring out the nearest x-value (date) is the same. But then you have to set the text and position of each focus tooltip according to the values in the correct column. You can do this because each focus element has a column name bound to it as a data object, and d3 will pass that data object as the first parameter to any function you pass in to a d3 method:
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
//d is now the data row for the date closest to the mouse position
focus.attr("transform", function(columnName){
return "translate(" + x( d.date ) + "," + y( d[ columnName ] ) + ")";
});
focus.select("text").text(function(columnName){
//because you didn't explictly set any data on the <text>
//elements, each one inherits the data from the focus <g>
return formatCurrency(d[ columnName ]);
});
}
By the way, you can use this same structure -- use the column names as data, and then use that name in a function to grab the correct data value -- to create all your lines with the same code, without having to create separate data arrays. Let me know if you have difficulty implementing that.

Categories

Resources