D3 Stack Line Chart - javascript

I'm working on a multi-line line chart in D3 (version 4) right now, and having some trouble. Here's what I am getting with my current code:
I think it has something to do with my x scale, but I can't figure out what is wrong. I've been following the tutorial here, with slight modifications because I am working with V4. Any help would be greatly appreciated.
var parseDate = d3.timeParse("%Y");
var color = d3.scaleOrdinal(["#969FFD","#7173BF","#4B4C7F","#262640", "red"]);
var mySvg = d3.select("#chart8").append('svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.bottom + margin.top)
var chartGroup = mySvg.append("g")
.attr("class","fullGroup")
.attr("transform", "translate("+margin.left+","+margin.top+")");
var parseDate = d3.timeParse("%Y");
d3.csv("../Assets/datasets/allDrugs3.csv", function(error, data){
if (error) throw error;
// FORMAT THE DATA //
var labelVar = "year";
var varNames = d3.keys(data[0]).slice(1);
// Alternatively --> .filter(function(key){ return key !== labelVar;})
console.log(varNames); // <-- Names of drugs, used for color array
// Add color domain of names
color.domain(varNames);
var seriesData = varNames.map(function(name){
return {
name: name,
values: data.map(function(d) {
return { name: name, label: parseDate(d[labelVar]), value: +d[name]};
})
};
});
console.log(seriesData);
// Y-SCALE //
var yScale = d3.scaleLinear()
.domain([
d3.min(seriesData, function(c){
return d3.min(c.values, function(d){ return d.value; });
}),
d3.max(seriesData, function(c){
return d3.max(c.values, function(d){ return d.value;});
})
])
.range([height, 0]);
console.log(
"The y domain is",
d3.min(seriesData, function(c){
return d3.min(c.values, function(d){ return d.value; })}),
"to",
d3.max(seriesData, function(c){
return d3.max(c.values, function(d){ return d.value;});
}));
// X-SCALE //
var xYears = data.map(function(d){return parseDate(d.year);});
var xScale = d3.scaleTime()
.domain(xYears)
.range([0, width]);
console.log(
"The x domain is",
xYears
)
var series = chartGroup.selectAll(".series")
.data(seriesData)
.enter().append("g")
.attr("class","series");
var line = d3.line()
.x(function(d){return xScale(d.label)})
.y(function(d){return yScale(d.value); })
.curve(d3.curveCardinal);
series.append("path")
.attr("d", function(d){ return line(d.values); })
.style("stroke", function (d) { return color(d.name); })
.style("stroke-width", "4px")
.style("fill", "none");
// Axes
var yAxis = d3.axisLeft(yScale).ticks(5).tickPadding(5);
var xAxis = d3.axisBottom(xScale).ticks(10);
chartGroup.append("g")
.attr("class","Xaxis")
.call(xAxis)
.attr("transform","translate(0,"+height+")")
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
chartGroup.append("g")
.attr("class","Yaxis")
.call(yAxis)
.selectAll("text")
.style("text-anchor", "middle")
.attr("dy", "-1em")
.attr("transform", "rotate(-90)");
});
Here's the data:
key,deaths,year
heroin,289,2007
heroin,360,2008
heroin,238,2009
heroin,247,2010
heroin,392,2011
heroin,399,2012
heroin,464,2013
heroin,578,2014
heroin,748,2015
heroin,1212,2016
opiods,280,2007
opiods,251,2008
opiods,311,2009
opiods,342,2010
opiods,311,2011
opiods,302,2012
opiods,316,2013
opiods,330,2014
opiods,351,2015
opiods,418,2016
alchohol,175,2007
alchohol,162,2008
alchohol,160,2009
alchohol,161,2010
alchohol,195,2011
alchohol,187,2012
alchohol,238,2013
alchohol,270,2014
alchohol,310,2015
alchohol,582,2016
benzodiazepine,48,2007
benzodiazepine,52,2008
benzodiazepine,58,2009
benzodiazepine,68,2010
benzodiazepine,73,2011
benzodiazepine,37,2012
benzodiazepine,69,2013
benzodiazepine,103,2014
benzodiazepine,91,2015
benzodiazepine,126,2016
cocaine,157,2007
cocaine,162,2008
cocaine,135,2009
cocaine,148,2010
cocaine,153,2011
cocaine,248,2012
cocaine,154,2013
cocaine,198,2014
cocaine,221,2015
cocaine,463,2016

There's a lot here, it might be easier just to post some working code. You idea is good, but there's a couple places it's breaking:
var varNames = d3.keys(data[0]).slice(1);
// Alternatively --> .filter(function(key){ return key !== labelVar;})
console.log(varNames); // <-- Names of drugs, used for color array
If you look at the console, you'll see this isn't giving you the drug names; it's giving you part of the first row of data, which isn't really useful.
For data in the format you have, one option is to use d3.nest(). This will take your array and give a new array grouped by the key of your choice. In this case we can group by drug like this:
var nested = d3.nest()
.key(d => d.key)
.map(data)
Now nested will be an array of objects like:
[{alchohol: [{key: "alchohol", deaths: "175", year: "2007"},...},
{benzodiazepine: [{key: "benzodiazepine", deaths: "48", year: "2007"}...
]
This makes the rest very natural. You can get the keys with nested.keys() and the arrays with .entries()
One thing to keep in mind with scales is that d3.max() returns the max in natural order — you need to to force the numerical order in the scale with .domain([0 , d3.max(data, d => +d.deaths) ])
Once that's all straightened out you can just pass nested into the data() function, and .entries() into your line generator and everything works pretty well.
Here's a working example that should help:
<html>
<head>
<script src="d3/d3.min.js"></script>
<script src="d3-selection-multi/d3-selection-multi.min.js"></script>
<style>
#chart div {
background-color: steelblue;
color: white;
padding: 8px;
text-align: right;
margin:1px;
font: 10px sans-serf
}
div#graphic {
display: inline-block;
/* border: 1px solid #333;*/
}
.title {
fill: #666;
font-family: Arial, Helvetica, sans-serif;
text-anchor: middle;
font-size: 24px;
}
.axis .domain, .axis .tick{
stroke: #000;
fill: none;
}
.bar {
fill:steelblue;
stroke: #444;
}
</style>
</head>
<body>
<div id ="chart"></div>
<div id="chart8"></div>
<button onclick="refresh()">refresh</button>
<script>
data = [{"key":"heroin","deaths":"289","year":"2007"},{"key":"heroin","deaths":"360","year":"2008"},{"key":"heroin","deaths":"238","year":"2009"},{"key":"heroin","deaths":"247","year":"2010"},{"key":"heroin","deaths":"392","year":"2011"},{"key":"heroin","deaths":"399","year":"2012"},{"key":"heroin","deaths":"464","year":"2013"},{"key":"heroin","deaths":"578","year":"2014"},{"key":"heroin","deaths":"748","year":"2015"},{"key":"heroin","deaths":"1212","year":"2016"},{"key":"opiods","deaths":"280","year":"2007"},{"key":"opiods","deaths":"251","year":"2008"},{"key":"opiods","deaths":"311","year":"2009"},{"key":"opiods","deaths":"342","year":"2010"},{"key":"opiods","deaths":"311","year":"2011"},{"key":"opiods","deaths":"302","year":"2012"},{"key":"opiods","deaths":"316","year":"2013"},{"key":"opiods","deaths":"330","year":"2014"},{"key":"opiods","deaths":"351","year":"2015"},{"key":"opiods","deaths":"418","year":"2016"},{"key":"alchohol","deaths":"175","year":"2007"},{"key":"alchohol","deaths":"162","year":"2008"},{"key":"alchohol","deaths":"160","year":"2009"},{"key":"alchohol","deaths":"161","year":"2010"},{"key":"alchohol","deaths":"195","year":"2011"},{"key":"alchohol","deaths":"187","year":"2012"},{"key":"alchohol","deaths":"238","year":"2013"},{"key":"alchohol","deaths":"270","year":"2014"},{"key":"alchohol","deaths":"310","year":"2015"},{"key":"alchohol","deaths":"582","year":"2016"},{"key":"benzodiazepine","deaths":"48","year":"2007"},{"key":"benzodiazepine","deaths":"52","year":"2008"},{"key":"benzodiazepine","deaths":"58","year":"2009"},{"key":"benzodiazepine","deaths":"68","year":"2010"},{"key":"benzodiazepine","deaths":"73","year":"2011"},{"key":"benzodiazepine","deaths":"37","year":"2012"},{"key":"benzodiazepine","deaths":"69","year":"2013"},{"key":"benzodiazepine","deaths":"103","year":"2014"},{"key":"benzodiazepine","deaths":"91","year":"2015"},{"key":"benzodiazepine","deaths":"126","year":"2016"},{"key":"cocaine","deaths":"157","year":"2007"},{"key":"cocaine","deaths":"162","year":"2008"},{"key":"cocaine","deaths":"135","year":"2009"},{"key":"cocaine","deaths":"148","year":"2010"},{"key":"cocaine","deaths":"153","year":"2011"},{"key":"cocaine","deaths":"248","year":"2012"},{"key":"cocaine","deaths":"154","year":"2013"},{"key":"cocaine","deaths":"198","year":"2014"},{"key":"cocaine","deaths":"221","year":"2015"},{"key":"cocaine","deaths":"463","year":"2016"}]
var margin = {
top: 20,
bottom: 20,
left: 20,
right: 20
}
var width = 800 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom
var parseDate = d3.timeParse("%Y");
var color = d3.scaleOrdinal(["#969FFD","#7173BF","#4B4C7F","#262640", "red"]);
var mySvg = d3.select("#chart8").append('svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.bottom + margin.top)
var chartGroup = mySvg.append("g")
.attr("class","fullGroup")
.attr("transform", "translate("+margin.left+","+margin.top+")");
var parseDate = d3.timeParse("%Y");
d3.csv("./allDrugs3.csv", function(error, data){
console.log(JSON.stringify(data))
if (error) throw error;
// FORMAT THE DATA //
var nested = d3.nest()
.key(d => d.key)
.map(data)
console.log("nest", nested.keys()) // <-- Names of drugs, used for color array
console.log(nested) // <-- the actual data
color.domain(nested);
// Y-SCALE //
var yScale = d3.scaleLinear()
.domain([0 , d3.max(data, d => +d.deaths) ])
.range([height, 0]);
// X-SCALE //
var xScale = d3.scaleTime()
.domain(d3.extent(data, d => d.year))
.range([0, width])
var line = d3.line()
.x(d => xScale(d.year))
.y(d => yScale(d.deaths))
.curve(d3.curveCardinal);
var series = chartGroup.selectAll(".series")
.data(nested.entries())
.enter().append("g")
.attr("class","series")
.append("path")
.attr("d", d => {console.log(d); return line(d.value)})
.style("stroke", function (d) { return color(d.key); })
.style("stroke-width", "4px")
.style("fill", "none");
// Axes
var yAxis = d3.axisLeft(yScale).ticks(5).tickPadding(5);
var xAxis = d3.axisBottom(xScale).ticks(10);
chartGroup.append("g")
.attr("class","Xaxis")
.call(xAxis)
.attr("transform","translate(0,"+height+")")
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
chartGroup.append("g")
.attr("class","Yaxis")
.call(yAxis)
.selectAll("text")
.style("text-anchor", "middle")
.attr("dy", "-1em")
.attr("transform", "rotate(-90)");
});
</script>
</body>
</html>

Related

filter data before generating d3 graph

I have a D3 graph which is supposed to generate a bar chart with dates on the X axis and a value on Y axis with the following script. I am able to display the chart properly but I want to filter the data before I plot it.
I only want to display data with even number dates 2,4,6,8.....30.
So, is there a way to filter the data after reading the csv?
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<style>
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #F2F2F2;
shape-rendering: crispEdges;
}
.d3-tip {
line-height: 1;
box-shadow: 0 0 5px #999999;
font-weight: normal;
font-size: 12px;
padding: 12px;
background: #FFFFFF;
color: #000;
border-radius: 2px;
}
.tick:nth-child(2n) text {
visibility: hidden;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<script>
function filter_Data(d) {
if (d.getDate() % 2 ==0)
{
return true;
}
}
var margin = {top: 120, right: 20, bottom: 70, left: 140},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%m/%d/%Y").parse
var formatTime = d3.time.format("%d %b, %Y");
var x = d3.scale.ordinal().rangeRoundBands([0, width], .5);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%a, %b %d"));
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(3);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Date:</strong> <span style='color:Black'>" + formatTime(d.date) + "</span>" + "</br><strong>Amount Spent:</strong> $<span style='color:Black'>" + 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 + ")");
svg.call(tip);
d3.csv("bar-data.csv", function(error, data) {
data2 = data.filter(function(d) { if filter_Data(){
d.date
} });
data2.forEach(function(d) {
d.date = parseDate(d.date);
d.value = +d.value;
});
x.domain(data.map(function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-60)" );
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -55)
.attr("x", -90)
.attr("dy", "0.71em")
.text("Amount Spent ($)");
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", "#6760C3")
.attr("x", function(d) { return x(d.date); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
});
</script>
</body>
My approach to filter data was:
function filter_Data(d) {
if (d.getDate() % 2 ==0)
{
return true;
}
}
d3.csv("bar-data.csv", function(error, data) {
data2 = data.filter(function(d) { if filter_Data(){
d.date
} });
data2.forEach(function(d) {
d.date = parseDate(d.date);
d.value = +d.value;
});
However, I still get all dates in the graph.
Please excuse me since my Javascript is not very strong.
I would appreciate the help.
The csv is as follows:
date,value
1/1/2018,53
1/2/2018,165
1/3/2018,269
1/4/2018,344
1/5/2018,376
1/6/2018,410
1/7/2018,421
1/8/2018,405
1/9/2018,376
1/10/2018,359
1/11/2018,392
1/12/2018,433
1/13/2018,455
1/14/2018,478
You have a problem in your filter:
data2 = data.filter(function(d) {
if filter_Data(){
d.date
}
});
Even ignoring the syntax error, this is not how you use an Array.prototype.filter. On top of that, you're not passing anything to filter_Data.
So, it should be:
var data2 = data.filter(function(d) {
return filter_Data(d.date)
});
Finally, use the filter after parsing the dates.
Here is the demo:
var tsv = `date,value
1/1/18,53
1/2/18,165
1/3/18,269
1/4/18,344
1/5/18,376
1/6/18,410
1/7/18,421
1/8/18,405
1/9/18,376
1/10/18,359
1/11/18,392
1/12/18,433
1/13/18,455
1/14/18,478`;
var data = d3.csv.parse(tsv);
var parseDate = d3.time.format("%m/%d/%Y").parse;
data.forEach(function(d) {
d.date = parseDate(d.date);
d.value = +d.value;
});
var data2 = data.filter(function(d) {
return filter_Data(d.date)
});
function filter_Data(d) {
if (d.getDate() % 2 == 0) {
return true;
}
}
console.log(data2)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
PS: depending on your time zone you'll see only odd dates in the console! One way to avoid that is dealing with the actual strings, not the date objects, something like what the other answer proposes.
function filter_Data(d) {
var day = d.date.split("/")[0];
return day %2;
}
data2 = data.filter(filter_data)

How to get the index of the data element in a histogram on mouseover?

I have a d3.v3.min.js histogram created using this as reference Histogram chart using d3 and I'd like to highlight in a separate plot (scatter plot) all the points that fall within one bar of this histogram. To this end I hook on the mouseover event of the rectangle to get the values within one bin. This works fine but I can't get their indices from the original input array:
var data = d3.layout.histogram().bins(xTicks)(values);
bar.append("rect")
.attr("x", 1)
.attr("width", (x(data[0].dx) - x(0)) - 1)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) })
.on("mouseover", function (d, i) { console.log(d); });
d is an array containing all the values within the bin, and i is the bin index. I need the indices of the original data values I passed to the histogram function so that I can look them up in the other plot by index (as opposed to a binary search needed on the value).
Instead of just passing number values to the histogram generator you could create an array of objects carrying additional information:
// Generate a 1000 data points using normal distribution with mean=20, deviation=5
var f = d3.random.normal(20, 5);
// Create full-fledged objects instead of mere numbers.
var values = d3.range(1000).map(id => ({
id: id,
value: f()
}));
// Accessor function for the objects' value property.
var valFn = d => d.value;
// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(20))
.value(valFn) // Provide accessor function for histogram generation
(values);
By providing an accessor function to the histogram generator you are then able to create the bins from this array of objects. Calling the histogram generator will consequently result in bins filled with objects instead of just raw numbers. In an event handler you are then able to access your data objects by reference. The objects will carry all the initial information, be it the id property as in my example, an index or anything else you put in them in the first place.
Have a look at the following snippet for a working demo:
var color = "steelblue";
var f = d3.random.normal(20, 5);
// Generate a 1000 data points using normal distribution with mean=20, deviation=5
var values = d3.range(1000).map(id => ({
id: id,
value: f()
}));
var valFn = d => d.value;
// A formatter for counts.
var formatCount = d3.format(",.0f");
var margin = {top: 20, right: 30, bottom: 30, left: 30},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var max = d3.max(values, valFn);
var min = d3.min(values, valFn);
var x = d3.scale.linear()
.domain([min, max])
.range([0, width]);
// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(20))
.value(valFn)
(values);
var yMax = d3.max(data, function(d){return d.length});
var yMin = d3.min(data, function(d){return d.length});
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var y = d3.scale.linear()
.domain([0, yMax])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
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 + ")");
var bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; })
.on("mouseover", d => { console.log(d)});
bar.append("rect")
.attr("x", 1)
.attr("width", (x(data[0].dx) - x(0)) - 1)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.append("text")
.attr("dy", ".75em")
.attr("y", -12)
.attr("x", (x(data[0].dx) - x(0)) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
/*
* Adding refresh method to reload new data
*/
function refresh(values){
// var values = d3.range(1000).map(d3.random.normal(20, 5));
var data = d3.layout.histogram()
.value(valFn)
.bins(x.ticks(20))
(values);
// Reset y domain using new data
var yMax = d3.max(data, function(d){return d.length});
var yMin = d3.min(data, function(d){return d.length});
y.domain([0, yMax]);
var colorScale = d3.scale.linear()
.domain([yMin, yMax])
.range([d3.rgb(color).brighter(), d3.rgb(color).darker()]);
var bar = svg.selectAll(".bar").data(data);
// Remove object with data
bar.exit().remove();
bar.transition()
.duration(1000)
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.select("rect")
.transition()
.duration(1000)
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", function(d) { return colorScale(d.y) });
bar.select("text")
.transition()
.duration(1000)
.text(function(d) { return formatCount(d.y); });
}
// Calling refresh repeatedly.
setInterval(function() {
var values = d3.range(1000).map(id => ({
id: id,
value: f()
}));
refresh(values);
}, 2000);
body {
font: 10px sans-serif;
}
.bar rect {
shape-rendering: crispEdges;
}
.bar text {
fill: #999999;
}
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.as-console-wrapper {
height: 20%;
}
<script src="https://d3js.org/d3.v3.min.js"></script>

Having trouble with updating D3.js multiline chart

I am fairly new to D3 and am hitting a problem.
I've created a simplied example of what I'm trying to achieve.
Firstly, I have a CSV file with data. In this example, it consists of phone sales data for some popular phones for several months for 2 stores. The data is shown below:
Store,Product,Month,Sold
London,iPhone,0,5
London,iPhone,1,4
London,iPhone,2,3
London,iPhone,3,5
London,iPhone,4,6
London,iPhone,5,7
London,Android Phone,0,3
London,Android Phone,1,4
London,Android Phone,2,5
London,Android Phone,3,7
London,Android Phone,4,8
London,Android Phone,5,9
London,Windows Phone,0,1
London,Windows Phone,1,2
London,Windows Phone,2,6
London,Windows Phone,3,7
London,Windows Phone,4,8
London,Windows Phone,5,5
Glasgow,iPhone,0,3
Glasgow,iPhone,1,4
Glasgow,iPhone,2,5
Glasgow,iPhone,3,2
Glasgow,iPhone,4,1
Glasgow,iPhone,5,3
Glasgow,Android Phone,0,4
Glasgow,Android Phone,1,3
Glasgow,Android Phone,2,7
Glasgow,Android Phone,3,4
Glasgow,Android Phone,4,3
Glasgow,Android Phone,5,6
Glasgow,Windows Phone,0,3
Glasgow,Windows Phone,1,6
Glasgow,Windows Phone,2,7
Glasgow,Windows Phone,3,5
Glasgow,Windows Phone,4,3
Glasgow,Windows Phone,5,4
I've written the following code in JS/D3.js:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-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>
<p id="menu"><b>Test</b>
<br>Select Store:
<select>
<option value="0">London</option>
<option value="1">Glasgow</option>
</select>
</p>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 900 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// construct a linear scale for x axis
var x = d3.scale.linear()
.range([0,width]);
// construct a linear scale for y axis
var y = d3.scale.linear()
.range([height,0]);
// use the default line colours (see http://stackoverflow.com/questions/21208031/how-to-customize-the-color-scale-in-a-d3-line-chart for info on setting colours per line)
var color = d3.scale.category10();
// create the x axis and orient of ticks and labels at the bottom
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
// create the y axis and orient of ticks and labels on the left
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// line generator function
var line = d3.svg.line()
//.interpolate("basis")
.x(function(d) { return x(d.Month); })
.y(function(d) { return y(d.Sold); });
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("sampleData.csv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key == "Product"; }));
// first we need to corerce the data into the right formats
// map the data from the CSV file
data = data.map( function (d) {
return {
Store: d.Store,
Product: d.Product,
Month: +d.Month,
Sold: +d.Sold };
});
// nest the data by regime and then CI
var salesDataByStoreProduct = d3.nest()
.key(function(d) { return d.Store; })
.key(function(d) { return d.Product; })
.entries(data);
// get the first regime's nest
var salesDataForLondon;
salesDataForLondon = salesDataByStoreProduct[0].values;
console.log(salesDataForLondon);
x.domain([d3.min(salesDataForLondon, function(d) { return d3.min(d.values, function (d) { return d.Month; }); }),
d3.max(salesDataForLondon, function(d) { return d3.max(d.values, function (d) { return d.Month; }); })]);
y.domain([0, d3.max(salesDataForLondon, function(d) { return d3.max(d.values, function (d) { return d.Sold; }); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
var Products = svg.selectAll(".Product")
.data(salesDataForLondon, function(d) { return d.key; })
.enter().append("g")
.attr("class", "Product");
Products.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.key); });
function redraw()
{
var salesDataByStoreProduct = d3.nest()
.key(function(d) { return d.Store; })
.key(function(d) { return d.Product; })
.entries(data);
var salesDataForGlasgow;
salesDataForGlasgow = salesDataByStoreProduct[1].values;
console.log(salesDataForGlasgow);
x.domain([d3.min(salesDataForGlasgow, function(d) { return d3.min(d.values, function (d) { return d.Product; }); }),
d3.max(salesDataForGlasgow, function(d) { return d3.max(d.values, function (d) { return d.Product; }); })]);
y.domain([0, d3.max(salesDataForGlasgow, function(d) { return d3.max(d.values, function (d) { return d.Sales; }); })]);
svg.select("g")
.call(xAxis);
svg.select("g")
.call(yAxis);
var Products = svg.selectAll(".Product")
.data(salesDataForGlasgow, function(d) { return d.key; })
.enter().select("g")
.attr("class", "Product");
Products.select("path")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.key); });
}
/******************************************************/
var menu = d3.select("#menu select")
.on("change", change);
function change()
{
clearTimeout(timeout);
d3.transition()
.duration(altKey ? 7500 : 1500);
redraw();
}
var timeout = setTimeout(function() {
menu.property("value", "ENEUSE").node().focus();
change();
}, 7000);
var altKey;
d3.select(window)
.on("keydown", function() { altKey = d3.event.altKey; })
.on("keyup", function() { altKey = false; });
/******************************************************/
});
</script>
</body>
</html>
where I've read in the CSV file and then used D3 nests to create a hierarchy as shown below:
Store->Product->Month->Sales
I want the chart to present the sales data per product by month for London and then if the selection is changed, to show the sales data by month for Glasgow.
However, although the London data is being presented, the chart isn't being updated when I select Glasgow.
To rule out anything too obvious, I've hardcoded the array index for each store.
I've also added console.log and can see the right data is being used but just not rendered in the chart when redraw() is being called.
I'd be grateful of any suggestions of the cause of the problem which I suspect it related to the following code:
var Products = svg.selectAll(".Product")
.data(salesDataForGlasgow, function(d) { return d.key; })
.enter().select("g")
.attr("class", "Product");
Products.select("path")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.key); });
Any other advice on improving or simplifying the code would be very gratefully appreciated.
As you suspect, the problem is indeed in these two statements:
var Products = svg.selectAll(".Product")
.data(salesDataForGlasgow, function(d) { return d.key; })
.enter().select("g")
.attr("class", "Product");
Products.select("path")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.key); });
Products is derived from the .enter() selection. This contains one element for each data item that isn't joined to an existing element in the DOM. When changing the graph to show the Glasgow data, there are no new elements to add (the London data has three products as does the Glasgow data), so the .enter() selection is empty.
Instead, you need to restart the selection from .Product. Change the second of the two statements to the following:
svg.selectAll(".Product")
.select("path")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.key); });
There are some other issues I found in your code. Firstly, the three lines that set x.domain() and y.domain() use the wrong property names at the end. This causes various NaNs to appear in the ranges of the x and y scales as D3 attempts to convert product names or undefined to numbers. At the end of these three lines, replace d.Product with d.Month and d.Sales with d.Sold, so that they are consistent with the lines that set the ranges of the x and y scales for the London sales data.
Finally, you need to adjust how you reset the axes. At the moment you are using the following code:
svg.select("g")
.call(xAxis);
svg.select("g")
.call(yAxis);
This ends calling the xAxis and then yAxis functions on all g elements, including both axes, all axis ticks and the three graph lines, so the graph looks a bit confused. You've set the class of the X-axis to x axis, but because class names can't have spaces in you've actually given the axis the classes x and axis. A similar thing happens with the y-axis.
What you need to do is to select the axes individually, using the classes you've assigned to them, before calling xAxis or yAxis. Replace the lines above with the following:
svg.select("g.x.axis")
.call(xAxis);
svg.select("g.y.axis")
.call(yAxis);
After you've made all these changes the graph should hopefully do what you want.
While this isn't a code review site, your code could take significant refactoring. First, you aren't handling the enter, update, exit pattern correctly. Second, once you handle the pattern correctly you don't need a seperate redraw function. Just one function to handle creation and update.
Here's a quick refactor:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-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>
<p id="menu"><b>Test</b>
<br>Select Store:
<select>
<option value="London">London</option>
<option value="Glasgow">Glasgow</option>
</select>
</p>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
var margin = {
top: 20,
right: 80,
bottom: 30,
left: 50
},
width = 900 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// construct a linear scale for x axis
var x = d3.scale.linear()
.range([0, width]);
// construct a linear scale for y axis
var y = d3.scale.linear()
.range([height, 0]);
// use the default line colours (see http://stackoverflow.com/questions/21208031/how-to-customize-the-color-scale-in-a-d3-line-chart for info on setting colours per line)
var color = d3.scale.category10();
// create the x axis and orient of ticks and labels at the bottom
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
// create the y axis and orient of ticks and labels on the left
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// line generator function
var line = d3.svg.line()
//.interpolate("basis")
.x(function(d) {
return x(d.Month);
})
.y(function(d) {
return y(d.Sold);
});
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 + ")");
// create gs for axis but don't add yet
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
svg.append("g")
.attr("class", "y axis");
var salesDataByStoreProduct = null;
d3.csv("data.csv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) {
return key == "Product";
}));
// first we need to corerce the data into the right formats
// map the data from the CSV file
data = data.map(function(d) {
return {
Store: d.Store,
Product: d.Product,
Month: +d.Month,
Sold: +d.Sold
};
});
// nest the data by regime and then CI
salesDataByStoreProduct = d3.nest()
.key(function(d) {
return d.Store;
})
.key(function(d) {
return d.Product;
})
.entries(data);
draw("London");
});
function draw(which) {
// get the first regime's nest
var salesData = null;
salesDataByStoreProduct.forEach(function(d) {
if (d.key === which) {
salesData = d.values;
}
});
// set domains
x.domain([d3.min(salesData, function(d) {
return d3.min(d.values, function(d) {
return d.Month;
});
}),
d3.max(salesData, function(d) {
return d3.max(d.values, function(d) {
return d.Month;
});
})
]);
y.domain([0, d3.max(salesData, function(d) {
return d3.max(d.values, function(d) {
return d.Sold;
});
})]);
// draw axis
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
// this is the update selection
var Products = svg.selectAll(".Product")
.data(salesData, function(d) {
return d.key;
});
// this is the enter selection
Products
.enter().append("g")
.attr("class", "Product")
.append("path");
// now do update
Products.selectAll("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", function(d) {
return color(d.key);
});
}
var menu = d3.select("#menu select")
.on("change", change);
function change() {
draw(this.options[this.selectedIndex].value);
}
</script>
</body>
</html>
Running code here.

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

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

How to modify color stroke in multiple charts with d3js nest() data

I'm trying to add line color condition by axis value
attr("stroke","red") if date > 'Jan 2008' I have tried to modify var line with this code below, but it didn't work. Any recommendation would be appreciated.
> var line = d3.svg.line()
> .x(function(d) { return x(d.date); })
> .y(function(d) { return y(d.price); })
> .attr( "stroke", function(d) { if (d.date > 'Jan 2008' { return "red"}; })
from d3 code below or fullcode and dataset from this link : http://bl.ocks.org/mbostock/1157787
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
margin: 0;
}
.line {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
.area {
fill: #e7e7e7;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 8, right: 10, bottom: 2, left: 10},
width = 960 - margin.left - margin.right,
height = 69 - margin.top - margin.bottom;
var parseDate = d3.time.format("%b %Y").parse;
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); });
d3.csv("stocks.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; });
});
function type(d) {
d.price = +d.price;
d.date = parseDate(d.date);
return d;
}
</script>

Categories