d3.scaleTime dates between 1890 and 1910 appear as :00 (d3.v4) - javascript

I am creating a line plot in d3 using an example I found here -> https://www.d3-graph-gallery.com/graph/line_brushZoom.html
My data contains observations from 1890 through to 2018 in the following format:
1880-01-01,1
1890-01-01,3
1890-02-02,1
1890-02-17,1
1890-03-29,1
1890-04-04,1
1890-05-04,1
1890-06-02,1
1890-06-05,1
1890-06-11,1
1890-07-01,1
1890-10-28,1
1890-12-24,1
1890-12-25,1
1891-01-29,1
1891-03-03,1
1891-06-07,1
1892-05-09,1
1893-08-20,1
1893-10-06,1
1894-03-28,1
1895-10-17,1
1896-05-25,1
1897-02-05,1
1897-07-29,1
1897-08-26,1
1898-07-05,1
1900-01-01,1
1900-08-12,1
1901-09-21,1
1903-08-16,1
1903-09-23,1
1904-02-13,1
1904-09-02,1
1904-09-04,1
1905-05-08,1
1905-07-06,1
1905-11-19,1
1906-09-24,1
1908-02-03,1
1909-01-01,1
1910-09-26,1
I noticed that the x axis scale renders dates between 1890 and 1910 with the following ticks :00
rather than 1890, 1900, 1910
The original chart code gives the following line to set the
// Add X axis --> it is a date format
var x = d3.scaleTime()
//.domain(d3.extent(data, function(d) { return d.date; }))// original line
.domain([new Date(1880, 0, 1), new Date(2018, 0, 1)]) // debugline
.range([ 0, width ]);
xAxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
d3.v5 does it. little different
const xScale = d3.scaleTime().range([0,width]);
const yScale = d3.scaleLinear().rangeRound([height, 0]);
xScale.domain(d3.extent(data, function(d){
return timeConv(d.date)}));
yScale.domain([(0), d3.max(slices, function(c) {
return d3.max(c.values, function(d) {
return d.measurement + 4; });
})
]);
I can't figure out what the problem is, I tried the same data in d3.v5 and I can't reproduce the problem. I am wondering if there is some additional parsing that is required for distinct types?
Thanks
Jonathan
PS Full code was requested (copy and pasted from the d3-gallery)
<!-- Code from d3-graph-gallery.com -->
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<script>
// set the dimensions and margins of the graph
var margin = {top: 50, right: 30, bottom: 30, left: 60},
width = 900 - margin.left - margin.right,
height = 600 - 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 + ")");
//Read the data
d3.csv("all_cases.csv",
// When reading the csv, I must format variables:
function(d){
return { date : d3.timeParse("%Y-%m-%d")(d.date), value : d.value }
},
// Now I can use this dataset:
function(data) {
// Add X axis --> it is a date format
var x = d3.scaleTime()
// .domain(d3.extent(data, function(d) { return d.date; }))
.domain([new Date(1880, 0, 1), new Date(2018, 0, 1)])
.range([ 0, width ]);
xAxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.value; })+5])
.range([ height, 0 ]);
yAxis = svg.append("g")
.call(d3.axisLeft(y));
// Add a clipPath: everything out of this area won't be drawn.
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width )
.attr("height", height )
.attr("x", 0)
.attr("y", 0);
// Add brushing
var brush = d3.brushX() // Add the brush feature using the d3.brush function
.extent( [ [0,0], [width,height] ] ) // initialise the brush area: start at 0,0 and finishes at width,height: it means I select the whole graph area
.on("end", updateChart) // Each time the brush selection changes, trigger the 'updateChart' function
// Create the line variable: where both the line and the brush take place
var line = svg.append('g')
.attr("clip-path", "url(#clip)")
// Add the line
line.append("path")
.datum(data)
.attr("class", "line") // I add the class line to be able to modify this line later on.
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(d) { return x(d.date) })
.y(function(d) { return y(d.value) })
)
// Add the brushing
line
.append("g")
.attr("class", "brush")
.call(brush);
// A function that set idleTimeOut to null
var idleTimeout
function idled() { idleTimeout = null; }
// A function that update the chart for given boundaries
function updateChart() {
// What are the selected boundaries?
extent = d3.event.selection
// If no selection, back to initial coordinate. Otherwise, update X axis domain
if(!extent){
if (!idleTimeout) return idleTimeout = setTimeout(idled, 350); // This allows to wait a little bit
x.domain([ 4,8])
}else{
x.domain([ x.invert(extent[0]), x.invert(extent[1]) ])
line.select(".brush").call(brush.move, null) // This remove the grey brush area as soon as the selection has been done
}
// Update axis and line position
xAxis.transition().duration(1000).call(d3.axisBottom(x))
line
.select('.line')
.transition()
.duration(1000)
.attr("d", d3.line()
.x(function(d) { return x(d.date) })
.y(function(d) { return y(d.value) })
)
}
// If user double click, reinitialize the chart
svg.on("dblclick",function(){
x.domain(d3.extent(data, function(d) { return d.date; }))
xAxis.transition().call(d3.axisBottom(x))
line
.select('.line')
.transition()
.attr("d", d3.line()
.x(function(d) { return x(d.date) })
.y(function(d) { return y(d.value) })
)
});
})
svg.append("text")
.attr("x", 400)
.attr("y", -5)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("text-decoration", "solid")
.text("Outbreaks 1890 - 2018");
</script>

You're using D3 v4, and the axis works with D3 v5:
<script src="https://d3js.org/d3.v4.min.js"></script>
To make your code work with v5, you have to change the syntax of d3.csv slightly, as it uses promises in v5. You don't need to change anything else.
d3.csv("all_cases.csv",
function(d){
return { date : d3.timeParse("%Y-%m-%d")(d.date), value : d.value }
})
.then(function(data) {
// code
})

Related

How do I select one group of values from dataset to plot a linechart using D3.js and not the whole dataset

I have a dataset that looks like this
entity,code,year,value
Afghanistan,AFG,1990,10.31850413
Afghanistan,AFG,1991,10.32701045
Albania,ALB,1990,3.985169898
Albania,ALB,1991,4.199006705
I want to plot a linechart with D3.js, but only for the country with code "AFG". the x-axis is going to be years from 1990 - 2017, the y-axis is the value. Currently, my code takes all the countries and thus creates a linechart with over a hundred overlapping lines. How do I change this code in order for it to take the specified value:
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 560 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg2 = 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 + ")");
//Read the data
d3.csv("./files/suicide-death-rates.csv",
// Now I can use this dataset:
function(data) {
// Add X axis --> it is a date format
var x = d3.scaleLinear()
.domain(d3.extent(data, function(d) { return d.year; }))
.range([ 0, width ]);
svg2.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return +d.value; })])
.range([ height, 0 ]);
svg2.append("g")
.call(d3.axisLeft(y));
// Add the line
svg2.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(d) { return x(d.year) })
.y(function(d) { return y(d.value) })
)
})
Thanks in advance!
You simply filter the .datum(data) like so:
// Add the line
svg2.append("path")
.datum(data.filter(f => f.code ==="AFG"))
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(d) { return x(d.year) })
.y(function(d) { return y(d.value) })
)

D3 Graph - Inject graph into div

I have some D3 javascript that works just fine. Here is the code if it helps. The trouble is, it loads in place of the javascript, which means I have to put the javascript in the right place in the HTML to position the graph.
what I want to be able to do is have this:
<div class="graph"></div>
and have the javascript inject the graph into that div.
Can anyone pleasse help me understand how to do this.
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
// 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("test3.csv", 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);
});
var inter = setInterval(function() {
updateData();
}, 5000);
// ** Update data section (Called from the onclick)
function updateData() {
// Get the data again
d3.csv("test3.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
// 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.close; })]);
// Select the section we want to apply our changes to
var svg = d3.select("body").transition();
// Make the changes
svg.select(".line") // change the line
.duration(750)
.attr("d", valueline(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);
});
}
</script>
This line:
d3.select("body")
.append("svg")
is telling d3 to append the svg to the body of the DOM. Try:
d3.select(".graph")
.append("svg")
Which finds your div by class name and appends the svg to that.

How to use rangeRound in scaleTime in d3 v4?

I am trying to create a divergent bar chart which uses time scale(date) as x-axis. I am having trouble using ScaleBands with date, the date labels are overlapping.
This is what I got so far. https://jsfiddle.net/14ch7yeo/ when I use scaleTime, Unfortunately, the graph does not load.
I need to use zoom and brush on this graph.
<!DOCTYPE html>
<meta charset="utf-8">
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var data = [{"Date":"2015-01-02T00:00:00.000Z","Buy":554646.5,"Sell":-406301.3547},{"Date":"2015-02-02T00:00:00.000Z","Buy":565499.5,"Sell":-673692.5697},{"Date":"2015-03-02T00:00:00.000Z","Buy":421954.5,"Sell":-571685.4629},{"Date":"2015-04-02T00:00:00.000Z","Buy":466242.0,"Sell":-457477.7121},{"Date":"2015-05-02T00:00:00.000Z","Buy":350199.7,"Sell":-579682.8772},{"Date":"2015-06-02T00:00:00.000Z","Buy":391035.1,"Sell":-338816.6205},{"Date":"2015-07-02T00:00:00.000Z","Buy":437644.6,"Sell":-502329.557},{"Date":"2015-08-02T00:00:00.000Z","Buy":291978.9,"Sell":-504067.0329},{"Date":"2015-09-02T00:00:00.000Z","Buy":360913.8,"Sell":-489519.6652},{"Date":"2015-10-02T00:00:00.000Z","Buy":505799.1,"Sell":-723353.7089},{"Date":"2015-11-02T00:00:00.000Z","Buy":510691.0,"Sell":-374061.8139},{"Date":"2015-12-02T00:00:00.000Z","Buy":527757.1,"Sell":-597800.0116},{"Date":"2016-01-02T00:00:00.000Z","Buy":564799.1,"Sell":-451779.1593},{"Date":"2016-02-02T00:00:00.000Z","Buy":336533.7,"Sell":-522601.1707},{"Date":"2016-03-02T00:00:00.000Z","Buy":460684.6,"Sell":-643556.0079999999},{"Date":"2016-04-02T00:00:00.000Z","Buy":428388.1,"Sell":-349216.2376},{"Date":"2016-05-02T00:00:00.000Z","Buy":525459.5,"Sell":-597258.4075},{"Date":"2016-06-02T00:00:00.000Z","Buy":677659.1,"Sell":-513192.107},{"Date":"2016-07-02T00:00:00.000Z","Buy":365612.8,"Sell":-287845.8089},{"Date":"2016-07-03T00:00:00.000Z","Buy":358775.2,"Sell":-414573.209}]
var parseTime = d3.utcParse("%Y-%m-%dT%H:%M:%S.%LZ");
data.forEach(d => {
d["Date"] = parseTime(d["Date"]);
})
var series = d3.stack()
.keys(["Buy", "Sell"])
.offset(d3.stackOffsetDiverging)
(data);
var svg = d3.select("svg"),
margin = {top: 20, right: 30, bottom: 30, left: 60},
width = +svg.attr("width"),
height = +svg.attr("height");
var x = d3.scaleBand()
.domain(data.map(function(d) { return d['Date']; }))
.rangeRound([margin.left, width - margin.right])
.padding(0.1);
var y = d3.scaleLinear()
.domain([d3.min(series, stackMin), d3.max(series, stackMax)])
.rangeRound([height - margin.bottom, margin.top]);
var z = d3.scaleOrdinal()
.range(["green","red"]);
svg.append("g")
.selectAll("g")
.data(series)
.enter().append("g")
.attr("fill", function(d) { return z(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("width", x.bandwidth)
.attr("x", function(d) { return x(d.data["Date"]); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
svg.append("g")
.attr("transform", "translate(0,"+ (height-margin.top) + ")")
.call(d3.axisBottom(x));
svg.append("g")
.attr("transform", "translate(" + margin.left + ",0)")
.call(d3.axisLeft(y));
function stackMin(serie) {
return d3.min(serie, function(d) { return d[0]; });
}
function stackMax(serie) {
return d3.max(serie, function(d) { return d[1]; });
}
</script>
d3.scaleTime has to be treated differently on a number of fronts.
The scale doesn't take padding as an argument:
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) { return d.Date; }))
.rangeRound([margin.left, width - margin.right]);
Time is continuous rather than discrete, so the widths of the bars need to be calculated manually, as a ratio of rect and series.length. I got this to work, but maybe you want something more elegant:
.attr("width", width/series.length - 450)

small multiples d3 uncaught y.domain not a function

I can't get mike bostock code to work as I continue to get console error: Uncaught TypeError: y.domain is not a function.
I have tried using different d3.js version and nothing works.
I have also formatted my data exactly like the bostock example.
I appreciate help on this issue as I just can't solve it.
Thank you.
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("data/year-cornTestSorted.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;
}
You can try by setting the below code at the above.
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; })
]);
then write the function to get the max price.
y.domain([0, methodToGetMaxPrice()];
After that remove all statements(y.domain([0, d.maxPrice]) from inside attr.

How to draw a line graph using d3.js?

I am new to d3.js and am trying to graph three lines on the same plot. I'm reading from a tsv file with four columns: time, x, y, and z. This is accelerometer data. I want to plot the x,y,z columns vs time but can't seem to get it. Any suggestions?
function graph(){
// 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;
// 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.time); })
.y(function(d) { return y(d.x); });
var valueline2 = d3.svg.line()
.x(function(d) { return x(d.time); })
.y(function(d) { return y(d.y); })
var valueline3 = d3.svg.line()
.x(function(d) { return x(d.time); })
.y(function(d) { return y(d.z); });
// 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.tsv("data/data2.tsv", function(error, data) {
data.forEach(function(d) {
d.time = +d.time;
d.x = +d.x;
d.y = +d.y;
d.z = +d.z;
});
// 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 Math.max(d.x, d.y, d.z); })]);
// Add the valueline path.
svg.append("path") // Add the valueline path.
.attr("class", "line")
.attr("d", valueline(data));
svg.append("path") // Add the valueline path.
.attr("class", "line")
.style("stroke", "red")
.attr("d", valueline2(data));
svg.append("path") // Add the valueline path.
.attr("class", "line")
.style("stroke", "green")
.attr("d", valueline3(data));
// Add the X Axis
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
});}
You can add each line one at a time to the canvas but it's not very d3 or efficient. It's much better to organise your data in the appropriate way. So the the main issue is the way that the data's been prepared. In the example that Lar's pointed to the data is nested using this block of code:
var series = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
time: d.time,
score: +d[name]
};
})
};
});
And what this does is to create an array of 3 objects. Each object has a key value pair: name and
an array. In each array is another object which contains the time and score information for plotting. It is this last array of object that is passed to the line generator.
I've created a fiddle, here, that's heavily based on the example that Lar's has pointed to and it's commented throughout. The trick at this stage is to inspect the elements and use console.log
Best of luck.

Categories