Swapping between multi-line plots with different numbers of lines D3 - d3.js

I am trying to essentially merge these two examples
https://www.d3-graph-gallery.com/graph/line_change_data.html
https://www.d3-graph-gallery.com/graph/line_several_group.html
But for the life of me I cannot get it to work. The data is coming in from pandas/flask so I have complete control on how to format it.
I also tried to simply feed in different datasets into the multiline plot (i.e. not worry about updating in a fancy transition, just swap between plots) but it kept just drawing the new lines without removing the old ones (i googled for various .exit() and .remove() strategies, but either it didn't work or removed the whole picture)
I feel the transition might be complicated as my understanding is one needs to give new data to the old points on the axis, but if that line doesn't exist in the new plot what would happen?
EDIT: Okay so I found a way to just switch between plots and refresh things with the following code, in particular by removing all the 'g' elements and then redrawing the axes which are lost in this (as you can tell I am still learning the ropes as to the different components). I tried only removing line and path elements but that did not work, would appreciate input on that too please.
So in this case, how would I go about updating this using transitions?
HTML
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Add 2 buttons -->
<button onclick="update(data1)">Dataset 1</button>
<button onclick="update(data2)">Dataset 2</button>
<button onclick="update(d_all)">Dataset 3</button>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<script>
var data1 = {{ d1|safe }}
var data2 = {{ d2|safe }}
var d_all = {{ d_all | safe}}
</script>
<script type="text/javascript" src="{{ url_for('static', filename='scripts/test.js') }}"></script>
JS
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 50},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
// Initialise a X axis:
var x = d3.scaleLinear().range([0,width]);
var xAxis = d3.axisBottom().scale(x);
// Initialize an Y axis
var y = d3.scaleLinear().range([height, 0]);
var yAxis = d3.axisLeft().scale(y);
// Create a function that takes a dataset as input and update the plot:
function update(data) {
d3.selectAll("g > *").remove()
svg.append("g")
.attr("class","myYaxis")
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class","myXaxis")
// Create the X axis:
x.domain([0, d3.max(data, function(d) { return d.ser1 }) ]);
svg.selectAll(".myXaxis") //.transition()
// .duration(3000)
.call(xAxis);
// create the Y axis
y.domain([0, d3.max(data, function(d) { return d.ser2 }) ]);
svg.selectAll(".myYaxis")
// .transition()
// .duration(3000)
.call(yAxis);
var grouped = d3.nest() // nest function allows to group the calculation per level of a factor
.key(function(d) { return d.l;})
.entries(data);
// color palette
var res = grouped.map(function(d){ return d.key }) // list of group names
var color = d3.scaleOrdinal()
.domain(res)
.range(['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33','#a65628','#f781bf','#999999'])
// Draw the line
svg.selectAll(".line")
.data(grouped)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", function(d){ return color(d.key) })
.attr("stroke-width", 1.5)
.attr("d", function(d){
return d3.line()
.x(function(d) { return x(d.ser1); })
.y(function(d) { return y(d.ser2); })
(d.values)
})
}
update(d_all)

Okay I more or less have a working answer for this, it's not perfect but it deals with the bulk of it as far as i can tell.
Watching this video helped a lot too so kudos there
https://www.youtube.com/watch?v=IyIAR65G-GQ
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 50},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
// Initialise a X axis:
var x = d3.scaleLinear().range([0,width]);
var xAxis = d3.axisBottom().scale(x);
// Initialize an Y axis
var y = d3.scaleLinear().range([height, 0]);
var yAxis = d3.axisLeft().scale(y);
svg.append("g")
.attr("class","myYaxis")
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class","myXaxis")
// Create a function that takes a dataset as input and update the plot:
function update(data) {
// Create the X axis:
x.domain([0, d3.max(data, function(d) { return d.ser1 }) ]);
svg.selectAll(".myXaxis") //.transition()
// .duration(3000)
.call(xAxis);
// create the Y axis
y.domain([0, d3.max(data, function(d) { return d.ser2 }) ]);
svg.selectAll(".myYaxis")
// .transition()
// .duration(3000)
.call(yAxis);
var grouped = d3.nest() // nest function allows to group the calculation per level of a factor
.key(function(d) { return d.l;})
.entries(data);
var res = grouped.map(function(d){ return d.key }) // list of group names
var color = d3.scaleOrdinal()
.domain(res)
.range(['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33','#a65628','#f781bf','#999999'])
var update = svg.selectAll(".line")
.data(grouped);
var t01Enter = update.enter().append("g")
.attr("class", ".line");
t01Enter.append("path")
.attr("class", "line")
.attr("fill", "none")
.style("opacity", 0.0)
.merge(update)
.transition()
.duration(2000)
.style("opacity", 1.0)
.attr("d", function(d){
return d3.line()
.x(function(d) { return x(d.ser1); })
.y(function(d) { return y(d.ser2); })
(d.values)
})
.style("stroke", function(d){ return color(d.key) })
.attr("stroke-width", 1.5)
update.exit()
.transition().duration(1000).style("opacity", 0.0).remove()
}
update(d_all)
I'd still like to work out how to chain the transitions more carefully and avoid issues of returning a type transition to merge, but that's for the next post!

Related

d3-js: scale time over sec, min, hour, day, month and year

how can i create a time scale over seconds, minutes, hours, days, months and years. in my code i get a second line when the seconds overlap.
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%dT%H:%M:%S");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the 0 line
var valueline0 = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value0); });
// append the svg object to the id="chart" of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom + 75 )
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Get the data
d3.csv("/trace/O00.csv", function(error, data) {
if (error) throw error;
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.value0 = +d.value0;
});
// 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.value0); })]);
// Add the valueline0 path.
svg.append("path")
.data([data])
.attr("class", "line")
.style("stroke", "steelblue")
.attr("d", valueline0);
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickFormat(d3.timeFormat("%Y-%m-%d %H:%M:%S")))
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
O00.csv:
date,value0
2020-07-14T14:03:51,35.66
2020-07-14T14:04:01,23.56
2020-07-14T14:03:11,32.64
2020-07-14T14:03:21,22.55
2020-07-14T14:03:31,28.60
2020-07-14T14:03:41,38.70
2020-07-14T14:03:51,35.66
2020-07-14T14:04:01,23.56
2020-07-14T14:04:11,21.54
chart with 2lines
the second line starts with the 7th data record (2020-07-14T14:03:51,35.66) because the seconds (51) from the 1st data record (2020-07-14T14:03:51,35.66) are repeated.
Thanks in advance, Onka
There is "only one line". You have Dates with multiple values. If you don't want one of the values, then you have to remove that value from your dataset, by filtering the data in some way.
If you want to remove the extra datapoint you'll have to figure out which one is the correct value. For instance if we say, "Let's use the max value", convert this code:
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.value0 = +d.value0;
});
To this
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.value0 = +d.value0;
});
const dataMap = {};
let dupCount = 0;;
data.forEach((d, index) => {
if (!dataMap[d.date]) {
dataMap[d.date] = true;
} else {
// remove the duplicate from the CSV
data.splice(index - dupCount, 1);
dupCount++;
}
});
Alternative, and much simpler, would be to first filter the data from the CSV using the csv parser: https://www.npmjs.com/package/csv-parser and then passing that to the .data(filteredCsvData) function rather than using the builtin d3.csv() which doesn't contain what you need.
the problem was due to the non-consecutive records in the csv file. if the sequence is correct, everything works as desired! done! thanks for all...

How to generate multiple panels of multilines plots in D3?

I'm trying to generate multiple panels of multiple lines plots in D3 with a 2 levels nested data structure.
Can someone please point me on how to properly generate line plots. I've intuitively tried to use a 2 levels nested data structure, but I can`t find how to properly distribute the lines in their corresponding panels.
See here for the results I have so far:
http://jtremblay.github.io/viz/example.html
Here is my code.
var s = `condition,taxon,abundance,date
condition01,speciesA,0.31,2017-04-13
condition01,speciesA,0.54,2017-04-20
condition01,speciesB,0.21,2017-04-13
condition01,speciesB,0.60,2017-04-20
condition02,speciesA,0.31,2017-04-13
condition02,speciesA,0.48,2017-04-20
condition02,speciesB,0.19,2017-04-13
condition02,speciesB,0.61,2017-04-20
condition03,speciesA,0.13,2017-04-13
condition03,speciesA,0.11,2017-04-20
condition03,speciesB,0.04,2017-04-13
condition03,speciesB,0.11,2017-04-20
`;
var data = d3.csvParse(s);
data.forEach(function(d) { // Make every date in the csv data a javascript date object format
var aDate = new Date(d.date);
d.date = aDate;
});
var taxa = data.map(function (d){
return d.taxon
});
taxa = taxa.filter(onlyUniqueArray);
var dates = data.map(function (d){
return d.dates
});
var dataNested = d3.nest() // nest function allows to group the calculation per level of a factor
.key(function(d) { return d.condition;})
.key(function(d) { return d.taxon;})
.entries(data);
console.log(dataNested);
var fillColors = ["#0000CD", "#00FF00", "#FF0000", "#808080"]
// color palette
var color = d3.scaleOrdinal()
.domain(taxa)
.range(fillColors);
//Margins
var margin = { top: 20, right: 20, bottom: 60, left: 50},
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// Define dom and svg
var dom = d3.select("#viz");
var svg = dom.selectAll("multipleLineCharts")
.data(dataNested)
.enter()
.append("div")
.attr("class", "chart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
//.attr("fake", function(d) {console.log("d inside svg:"); console.log(d);})
// Add X axis --> it is a date format
var xScale = d3.scaleTime()
.rangeRound([0, width])
xScale.domain(d3.extent(data, function(d) {return d.date; }));
svg
.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class", "x axis")
.call(d3.axisBottom(xScale))
.selectAll("text")
.style("text-anchor", "end")
.attr("transform", "rotate(-90)")
.attr("dx", "-0.8em")
.attr("dy", "-0.45em")
//Add Y axis - Here because we want all panels to be on same scale, we cant use the dates from the global data structure.
var yScale = d3.scaleLinear()
.domain([
d3.min(data, function(d) { return d.abundance; } ),
d3.max(data, function(d) { return d.abundance; } )
])
.range([ height, 0 ]);
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale).ticks(5));
//Add Z scale (colors)
var zScale = d3.scaleOrdinal()
.range(fillColors);
zScale.domain(taxa);
// generate lines.
svg
.append("path")
.attr("class", "line")
.style("stroke", function(d) { return zScale(d.key); })
.attr("d", function(d, i){
return d3.line()
.x(function(d) { return xScale(d.date); })
.y(function(d) { return yScale(d.abundance); })
(data); //I know something else should go in there, but can't figure out what/how exactly...
})
/* Util functions */
function onlyUniqueArray(value, index, self) {
return self.indexOf(value) === index;
}
I don't understand how to effectively handle my data structure for what I want to do...
Is my 2x nested data structure is adequate for what I'm trying to accomplish? I've tried with a one level nested data structure, but with no success.
Finally solved it. This example helped me to understand how to handle nested selections : http://bl.ocks.org/stepheneb/1183998
Essentially, the last block of code was replaced with this:
// generate lines.
var lines = svg.selectAll("lines")
.data(function(d) { return d.values;})
.enter()
.append("path")
.attr("class", "line")
.attr("d", function(d){
return d3.line()
.x(function(d) { return xScale(d.date); })
.y(function(d) { return yScale(d.abundance); })
(d.values);
})
.style("stroke", function(d) { return zScale(d.key); })
With a working example here: http://jtremblay.github.io/viz/example-fixed.html

Parsing a dataset of time in the format "Y-M-D H:M:S.MS" gives me 0NaN-NaN-NaNTNaN:NaN:NaN.NaNZ

I am able to see glucose readings but time shows up as: 0NaN-NaN-NaNTNaN:NaN:NaN.NaNZ
I am trying to parse a dataset of time of the format "Y-M-D H:M:S.MS". I need it to be formatted properly so that I can show it on the x axis. I have attached sample dataset to this code.
My code looks like this:
<script>
function overview(){
// Set the dimensions of the canvas / graph
var margin = {top: 10, right: 20, bottom: 30, left: 30},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// // Parse the date / time
var parseDate = d3.utcFormat("%Y-%m-%dT%H:%M:%S.%LZ");
var x = d3.scaleTime()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom()
.tickFormat(d3.timeFormat("%H"));
var yAxis = d3.axisLeft();
// Define the line
var valueline = d3.line()
.x(function(d) { return x(d.time); })
.y(function(d) { return y(d.glucoseReading); });
// Adds the svg canvas
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Get the data
d3.csv("glucose.csv", function(error, data) {
data.forEach(function(d) {
d.time = parseDate(d.time);
d.glucoseReading = +d.glucoseReading;
console.log(d.time);
console.log(d.glucoseReading);
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.time; }));
y.domain([0, d3.max(data, function(d) { return d.glucoseReading; })]);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// Add the scatterplot
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.time); })
.attr("cy", function(d) { return y(d.glucoseReading); });
// 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);
});
}
overview();
</script>
My Dataset looks like:
You want to convert (parse) strings to dates, not the other way around. Therefore, instead of d3.utcFormat(), you have to use d3.utcParse(). On top of that, your specifier is incorrect: there is no timezone in your strings.
So, this should be your parseDate function and specifier:
var parseDate = d3.utcParse("%Y-%m-%d %H:%M:%S.%L")
Here is it working (check your browse console, not the snippet's one):
var parseDate = d3.utcParse("%Y-%m-%d %H:%M:%S.%L")
var string = "2017-08-23 00:03:52.591";
console.log(parseDate(string))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

function mousemove code doesn't work in a d3.js stacked barplot

I try to combine multiple mouse events, which i took from https://www.d3-graph-gallery.com/graph/barplot_stacked_hover.html AND https://www.d3-graph-gallery.com/graph/barplot_stacked_highlight.html .
Unfortunately, the mousemove funtion doesn't work. I want to have the "tooltip" following my mouse as shown in the first example.
// set the dimensions and margins of the graph
var margin = {
top: 10,
right: 30,
bottom: 20,
left: 50
},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
// Parse the Data
d3.csv("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_stackedXL.csv").then(function(data) {
// List of subgroups = header of the csv files = soil condition here
var subgroups = data.columns.slice(1)
// List of groups = species here = value of the first column called group -> I show them on the X axis
var groups = d3.map(data, function(d) {
return (d.group)
}).keys()
// Add X axis
var x = d3.scaleBand()
.domain(groups)
.range([0, width])
.padding([0.2])
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSizeOuter(0));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 120])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// color palette = one color per subgroup
var color = d3.scaleOrdinal()
.domain(subgroups)
.range(d3.schemeSet2);
//stack the data? --> stack per subgroup
var stackedData = d3.stack()
.keys(subgroups)
(data)
// ----------------
// Highlight a specific subgroup when hovered
// ----------------
// ----------------
// Create a tooltip
// ----------------
var tooltip = d3.select("#my_dataviz")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "1px")
.style("border-radius", "5px")
.style("padding", "1px")
// What happens when user hover a bar
var mouseover = function(d) {
// what subgroup are we hovering?
var subgroupName = d3.select(this.parentNode).datum().key; // This was the tricky part
var subgroupValue = d.data[subgroupName];
// Reduce opacity of all rect to 0.2
d3.selectAll(".myRect").style("opacity", 0.2)
// Highlight all rects of this subgroup with opacity 0.8. It is possible to select them since they have a specific class = their name.
d3.selectAll("." + subgroupName)
.style("opacity", 1)
tooltip
.html("subgroup: " + subgroupName + "<br>" + "Value: " + subgroupValue)
.style("opacity", 1)
}
var mousemove = function(d) {
tooltip
.style("left", (d3.mouse(this)[0] + 90) + "px") // It is important to put the +90: other wise the tooltip is exactly where the point is an it creates a weird effect
.style("top", (d3.mouse(this)[1]) + "px")
}
// When user do not hover anymore
var mouseleave = function(d) {
// Back to normal opacity: 0.8
d3.selectAll(".myRect")
.style("opacity", 0.8)
tooltip
.style("opacity", 0)
}
// Show the bars
svg.append("g")
.selectAll("g")
// Enter in the stack data = loop key per key = group per group
.data(stackedData)
.enter().append("g")
.attr("fill", function(d) {
return color(d.key);
})
.attr("class", function(d) {
return "myRect " + d.key
}) // Add a class to each subgroup: their name
.selectAll("rect")
// enter a second time = loop subgroup per subgroup to add all rectangles
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return x(d.data.group);
})
.attr("y", function(d) {
return y(d[1]);
})
.attr("height", function(d) {
return y(d[0]) - y(d[1]);
})
.attr("width", x.bandwidth())
.attr("stroke", "grey")
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave)
})
<head>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js "></script>
</head>
<body>
<div id="my_dataviz"></div>
</body>

How to structure .csv file to allow D3.js to associate a line with a specified color?

I don't have a MWE for this, as it mostly deals with the preprocessing of data prior to writing D3 code.
I have a .csv file which is essentially structured as follows:
category,value,date
cat1,200000,2016-08-07
cat2,500000,2016-08-07
cat3,600000,2016-08-07
cat1,200000,2016-09-07
cat2,500000,2016-09-07
cat3,600000,2016-09-07
and so forth. The task is to draw a line graph for each one of cat1, cat2, and cat3 with the x-axis being the date and the y-axis being the value. From every example I've found, the solution appears to be that there must be one date per row:
date,cat1,cat2,cat3
2016-08-07,200000,500000,600000
2016-09-07,200000,500000,600000
which makes sense. However, let's suppose that cat1, cat2, and cat3 have required colors that must be associated with them. For example, suppose that the line for cat1 must have color #4472C4, the line for cat2 must have color #ED7D31, and the line for cat3 must have color #FFC000.
What is the best way to structure the data above to not only easily plot the lines, but to associate each line with a specified color?
If a MWE is preferred, please let me know, and I will delete this question until I have a sufficient MWE prepared.
You can define your custom color scale this way:
var color = d3.scaleOrdinal()
.domain(['cat1', 'cat2', 'cat3'])
.range(["#4472C4", "#ED7D31" , "#FFC000"]);
console.log("color('cat1') ==>", color('cat1'));
console.log("color('cat2') ==>", color('cat2'));
console.log("color('cat3') ==>", color('cat3'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
As you can see it returns the appropriate color for the category name that you pass.
Check the primitive demo how it works in action:
var dataAsCsv = `category,value,date
cat1,200000,2016-08-07
cat2,500000,2016-08-07
cat3,600000,2016-08-07
cat1,400000,2016-09-07
cat2,600000,2016-09-07
cat3,200000,2016-09-07`;
var color = d3.scaleOrdinal()
.domain(['cat1', 'cat2', 'cat3'])
.range(["#4472C4", "#ED7D31" , "#FFC000"]);
var data = d3.csvParse(dataAsCsv);
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 300 - margin.left - margin.right,
height = 100 - margin.top - margin.bottom;
var parseTime = d3.timeParse("%Y-%m-%d");
data.forEach(function(d) {
d.date = parseTime(d.date);
d.value = +d.value;
});
var dataByCategory = d3.nest()
.key(function(d) { return d.category; })
.entries(data);
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
var valueline = d3.line()
.x(function(d) { return x(d.date); })
.y(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 + ")");
svg.selectAll('path')
.data(dataByCategory)
.enter()
.append("path")
.attr("class", "line")
.attr("stroke", function(d){
console.log(color(d.key))
return color(d.key);
})
.attr("d", function(d) {
return valueline(d.values);
});
.line {
fill: none;
stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
One thing you could try is to class each line by category, then use css to add colour, something like this:
d3.selectAll("path").data(csv)
.enter().append("path")
.attr("class", function(d){return d.category}
Alternatively, you could also add a colour scale which can be seen here.

Resources