D3 single line chart with tsv data - d3.js

I am trying to import single-column tsv data in order to create a line chart with d3.js, but I am getting an error message, and I don't know how to solve this.
Here is my code (based on already existing examples):
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 50}, // as if margin would be an object
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis() // we create a new axis
.scale(x) // we set the scale based on the var x
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d,i) {
console.log('Plotting X value for data point: ' + d + ' using index: ' + i + ' to be at: ' + x(i) + ' using our xScale.');
return x(i);
})
.y(function(d) { return y(d.values); });
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.tsv("data.tsv", type, function(error, data) {
if (error) throw error;
x.domain(d3.extent(data, function(d) { return d.length; })); // get min and max date values
y.domain(d3.extent(data, function(d) { return d.values; }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis); // we call also a var!!
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("Values");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
});
function type(d) {
d.values = +d.values; // converts string to number
return d;
}
</script>
And here is the tsv file:
values
1.5
2.6
3.4
7.8
3.8
1.0
6.7
The error I get is:
"Invalid value for <path> attribute d="MNaN,416.9117647058824LNaN,344.1176470588235LNaN,291.17647058823525LNaN,0LNaN,264.70588235294116LNaN,450LNaN,72.79411764705881"
Apparently my data index is not correctly read.
Could someone maybe help?

One change instead of
x.domain(d3.extent(data, function(d) { return d.length; })); // get min and max
Do this since the x axis is the length of the array.
x.domain([0, data.length-1]); // get min and max
Working code here
Hope this helps!

Related

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>

d3.js x axis date range

i have some values in my csv file and i show a graph with values on y axis and dates on x axis.
For first graph i have following values
date,close
13-Jul-16,0.8736701869033555
15-Jul-16,0.3631761567983922
17-Jul-16,0.4795564555162078
19-Jul-16,0.3754827857186281
21-Jul-16,0.4355941951068847
23-Jul-16,0.34393804366457353
25-Jul-16,0.40967947088135176
27-Jul-16,0.2707818657230363
29-Jul-16,0.34430251610420176
31-Jul-16,0.28089496856221585
For second graph i have following values
date,close
11-Jul-16,0.766705419439816
15-Jul-16,0.7353651170975812
17-Jul-16,0.41531502169603063
19-Jul-16,0.5927871032351933
21-Jul-16,0.7986419920511857
23-Jul-16,0.7904979990272231
25-Jul-16,0.817690401573838
27-Jul-16,0.8433545168648027
29-Jul-16,0.8612307965742473
31-Jul-16,0.806498303188971
But in second graph x axis does not contain all dates.. As an example i put a printscreen of my output graphs myoutput to here.
This is my code which takes datas from csv file and visualize it.
var selectedMonth=document.getElementById('selectedMonth').value;
var selectedTopic=document.getElementById('selectedTopic').value;
var userFileDirectory="../documents/";
userFileDirectory=userFileDirectory+selectedMonth+"/"+selectedTopic+"/"+"dataCs.csv";
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%d-%b-%y").parse;
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
// 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(userFileDirectory, function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// 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);
svg.append("text")
.text("("+selectedMonth+" "+selectedTopic+")");
});
I would try setting the tick values explicitly, using tickValues:
https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#tickValues
ticks(5) will suggest 5 ticks, but will be adapted based on the scale's domain. Alternative to tickValues(), you could try ticks(d3.time.day, 2) to have a tick every 2 days.

Constraining axis labels in d3

I am following a tutorial so I can learn a bit of d3js.
Here is my code:
'use strict';
//Dashboard
//setup size of line chart
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
//parse data from file
var parseDate = d3.time.format("%b").parse;
//set scales
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
//create axes
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
//construct the line using points from data
var line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.users); });
var svg = d3.select(".linechart").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.tsv("data.tsv", function(error, data) {
if (error) throw error;
//traverse through the data
data.forEach(function(d) {
d.date = parseDate(d.date);
d.users = +d.users;
});
//establish the domain for x and y axes
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.users; }));
//add "groups"
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("Users (unique)");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
});
The results look like this:
The data is:
date users
Jan 10
Feb 20
Mar 30
....
My question is about the axis, how can I force it to not insert labels on the x axis that are not in the data set?
Set ticks for x axis manually:
...
if (error) throw error;
var ticks = data.map(function(d) { return parseDate(d.date) };
...
x.domain(d3.extent(data, function(d) { return d.date; })).tickValues(ticks);
https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#tickValues

transform linechart to 'squared' chart not being a barchart

I just created a linechart but what I would really like is that the line is not going from point to point, but that the line is more squared.
I don't know if this has a specific name, but these pictures should make it all a bit more clear:
This is what I have:
This is what I want:
Is there a way to do this in d3 without having to create a script which adds the 'extra' points?
This is the code I use for the line chart:
var maxDepth = graphObj[graphObj.length-1].maxDepth ;
$('#floodRiskChart').html('');
var margin = {top: 5, right: 5, bottom: 50, left: 65},
width = 410 - margin.left - margin.right,
height = 210 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.domain([0, maxDepth ])
.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.exceedance); })
.y(function(d) { return y(d.depth); });
var svg = d3.select("#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 + ")");
graphObj.forEach(function(d) {
//d.date = parseDate(d.date);
d.exceedance = parseFloat(+d.exceedance);
d.depth= parseFloat(+d.depth);
});
x.domain(d3.extent(graphObj, function(d) { return parseFloat(d.exceedance); }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("x", "25%")
.attr("dy", "3em")
.html("chance");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -50)
.attr("dx", 0)
.style("text-anchor", "end")
.html("depth");
svg.append("path")
.datum(graphObj)
.attr("class", "line")
.attr("d", line);
you need to add .interpolate('step-after') on the line generator, like so:
var line = d3.svg.line()
.interpolate('step-after')
.x(function(d) { return x(d.exceedance); })
.y(function(d) { return y(d.depth); });
this will give you that result, more info can be found here: enter link description here towards the end of the page

parseDate = d3.time.format("%Y") not working

My chart is working completely as expected, except that my parseDate function doesn't want to give me correct dates on the x-axis. I'm sure this is something simple.
Currently I'm adding d3.v2.min.js, without any additional helper libraries - do I need something else to get d3.time.format() working?
Without parsing the date my data returns an x-axis with:
.960 .965 .970 .975
rather than
1960 1965 1970 1975
The JSON is structured like this:
[{"year":1959,"average":315.97},
{"year":1960,"average":316.91},
{"year":1961,"average":317.64},
...etc
{"year":2011,"average":391.57}]
The code with the // commented sections being the issue:
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 920 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parseDate below not working
var parseDate = d3.time.format("%Y").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// data taking format
d3.json("{{ asset('js/data_co2.json') }}", function(data) {
// parseDate() not working below
Gives error: Uncaught TypeError: Object 1959 has no method 'substring'
data.forEach(function(d) {
d.year = parseDate(d.year);
d.average = +d.average;
});
x.domain(d3.extent(data, function(d) { return d.year; }));
y.domain([260, 420]);
var area = d3.svg.area()
.x(function(d) { return x(d.year); })
.y0(height)
.y1(function(d) { return y(d.average); });
var svg = d3.select("#co2").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.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
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("CO2 Levels (ppm)");
});
</script>
parseDate, the function "prepared" by d3, expects to be passed a string for parsing. Your years are (appropriately) numbers, so you would need to convert them to strings:
parseDate(String(d.year));
or
parseDate(d.year.toString());
However, keep in mind that the same result – a Date object – could be achieved natively, like this:
var date = new Date(d.year, 0);// The required 0, is for January

Resources