I'm following this example by Mike himself. The timeFormat in the example is ("%d-%b-%y"), but using my own data uses just the year. I've made all the necessary changes (I think). The y-axis shows, but the x-axis doesn't. There are also no errors showing, so I'm not sure where to go. Below is my code. Thanks!
<!DOCTYPE html>
<meta charset="utf-8">
<p></p>
<style>
.axis--x path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
</style>
<!--We immediately define the variables of our svg/chart-->
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// Now we give our svg some attributes. We use conventional margins as set out by Mike Bostock himself.
// Sets width and height minus the margins.
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Here we set out the time format: date-month-year.
//var parseTime = d3.timeParse("%d-%b-%y");
var formatTime = d3.timeFormat("%Y");
formatTime(new Date); // "2015"
// Now we set our axis. X is according to the time, and y is linear.
// We use rangeRound to round all the values to the nearest whole number.
// We don't use rangeBands or rangePoints as we're not creating a bar chart or scatter plot.
var x = d3.scaleTime()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
// Now we tell what we want our line to do/represent.
// x is the date, and y is the close price.
var line = d3.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.close);
});
// This is where we load our tsv file.
d3.tsv("/LineCharts/Line Chart 2 - MO Capital Punishment/data/data.tsv", function(d) {
d.date = formatTime(d.date);
d.close = +d.close;
return d;
}, function(error, data) {
if (error) throw error;
// The .extent function returns the minimum and maximum value in the given array.
// Then, function(d) { return d.date; } returns all the 'date' values in 'data'.
// The .domain function which returns those maximum and minimum values to D3 as the range for the x axis.
x.domain(d3.extent(data, function(d) {
return d.date;
}));
//Same as above for the x domain.
y.domain(d3.extent(data, function(d) {
return d.close;
}));
// Note that we use attr() to apply transform as an attribute of g.
// SVG transforms are quite powerful, and can accept several different kinds of transform definitions, including scales and rotations.
// But we are keeping it simple here with only a translation transform, which simply pushes the whole g group over and down by some amount, each time a new value is loaded onto the page.
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Doing the same as above but for the y axis.
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y))
//This is where we append(add) text labels to our y axis.
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.style("text-anchor", "end")
.text("Total");
g.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
});
</script>
Related
I am learning d3.js and this is me trying to learn area chart but i am getting this error of :
<!DOCTYPE html>
<meta charset="utf-8" />
<style>
/* set the CSS */
.line {
fill: none;
stroke: green;
stroke-width: 2px;
}
.area {
fill: green;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%B %e, %Y");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the area
var area = d3
.area()
.x(function (d) {
return x(d.Date);
})
.y0(height)
.y1(function (d) {
return y(d["New cases"]);
});
// define the line
var valueline = d3
.line()
.x(function (d) {
return x(d.date);
})
.y(function (d) {
return y(d.newcases);
});
// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3
.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// get the data
d3.csv(
"https://raw.githubusercontent.com/pravinpoudel/file-host/main/COVID-Utah.csv"
).then(function (data) {
// format the data
data.forEach(function (d) {
d.Date = parseTime(d.Date);
d["New cases"] = +d["New cases"];
});
x.domain(
d3.extent(data, function (d) {
return d.Date;
})
);
y.domain([
0,
d3.max(data, function (d) {
return d["New cases"];
}),
]);
// add the area
svg.append("path").data([data]).attr("class", "area").attr("d", area);
// add the valueline path.
svg
.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline);
// add the X Axis
svg
.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// add the Y Axis
svg.append("g").call(d3.axisLeft(y));
});
</script>
</body>
can anyone please help me on this !!
I think I've found an answer, so let me try and pitch in.
The biggest issue is how you append the <path> elements for the area and line.
svg.append("path").data([data]).attr("class", "area").attr("d", area);
svg.append("path").data([data]).attr("class", "line").attr("d", valueline);
Once you append the <path> element you instead need to pass the data in the area and valueline functions.
svg.append("path").attr("class", "area").attr("d", area(data));
svg.append("path").attr("class", "line").attr("d", valueline(data));
Unfortunately this is not enough, and the console points you to the error.
Error: <path> attribute d: Expected number, "…5.1232876712329,NaNL882.68493150…"
Some coordinates are assigned a value of NaN, and looking at the data this has to do with the New cases field. The value is not always a number, often NaN or N/A. As a workaround you can try the conversion and provide a fallback.
d["New cases"] = +d["New cases"]
+d["New cases"] = +d["New cases"] || 0;
It's a bit of a judgment call, however. You need to decide how to interpret data points which do not have an available value.
With these two changes you finally are able to display the area, but not the line. Looking at the definition of valueline it seems you point to two different properties.
var valueline = d3
.line()
.x(function (d) {
return x(d.date);
})
.y(function (d) {
return y(d.newcases);
});
Be sure to reference the properties of the data, as you have done with the area function.
-return x(d.date);
+return x(d.Date);
-return y(d.newcases);
+return y(d['New cases']);
As the line and area have the same color you might find it difficult to distinguish them visually, but they are both plotted.
A minor note on the name of the svg variable.
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 + ")");
As you mention in the comment you append a group element to translate the origin. This means the variable refers to the group element, and not the svg.
Hope it helps.
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
})
I'm new to D3.js. I have two problem while creating line chart in D3.js.
One is, I have tried to create line chart in D3.js using the following input values. In my output graph X-axis, I didn't get the time along with date which is present in the input file. I didn't understand how do date parsing into some format happening while creating graph. I don't know how to parse the date into some format to get date with time in my graph x-axis.
file name : lne1-data.csv
date,value
1-May-12 7:30,58.13
30-Apr-12 8:30,53.98
27-Apr-12 9:10,67.00
26-Apr-12 10:20,89.70
25-Apr-12 11:00,99.00
24-Apr-12 12:30,130.28
23-Apr-12 13:20,166.70
20-Apr-12 14:40,234.98
19-Apr-12 15:10,345.44
My Code:
<!DOCTYPE html>
<meta charset="utf-8">
<style> /* set the CSS */
body { font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<body>
<!-- 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},
var margin = {top: 30, right: 40, bottom: 70, left: 50},
width = 1600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%d-%b-%y %H:%M").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 xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(6)
.tickFormat(d3.time.format("%Y-%m-%d %H:%M"));
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(7);
// 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("lne1-data.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);
});
</script>
</body>
My output graph:
Instead of the above graph, I need a graph which should have date with time in X-axis. what do i need to do to get time?
Another one is, I need to draw a vertical line to make two seperate parts like pre cutover, post cutover in that graph for particular date.
Eg. I need a vertical line on the date "2012-Apr-25 11:00"
How do I solve this problems? Can anyone help me to solve these problem please?
I'm trying to run a basic d3.js line graph but I keep getting an error in the function d3_time_parse. The example I'm attempting to copy is here: http://bl.ocks.org/d3noob/b3ff6ae1c120eea654b5
Specifically, the error is d3.v3.js:2550 Uncaught TypeError: Cannot read property 'length' of undefined corresponding to
2549: function d3_time_parse(date, template, string, j) {
2550: var c, p, t, i = 0, n = template.length, m = string.length;
My code:
<!DOCTYPE html>
<meta charset="utf-8">
<style> /* set the CSS */
body { font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.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("data.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);
});
</script>
</body>
data.csv
date,close
1-May-12,58.13
30-Apr-12,53.98
27-Apr-12,67.00
26-Apr-12,89.70
25-Apr-12,99.00
24-Apr-12,130.28
23-Apr-12,166.70
20-Apr-12,234.98
19-Apr-12,345.44
18-Apr-12,443.34
17-Apr-12,543.70
16-Apr-12,580.13
13-Apr-12,605.23
12-Apr-12,622.77
11-Apr-12,626.20
10-Apr-12,628.44
9-Apr-12,636.23
5-Apr-12,633.68
4-Apr-12,624.31
3-Apr-12,629.32
2-Apr-12,618.63
30-Mar-12,599.55
29-Mar-12,609.86
28-Mar-12,617.62
27-Mar-12,614.48
26-Mar-12,606.98
A few users have asked similar questions, and the issues were fixed after correcting the time format used in var parseDate and making sure the CSV values are separated by commas. I've checked for these issues and still no luck.
I determined there was an issue with the .csv file after I downloaded the data file from the example website I was using.
Although the .csv files look identical in excel, the code only runs when I use the file downloaded from the example site.
I'm trying to learn d3js and started with an example of a Area Chart example. I understand what is happening in this example and i tried to manipulate it a little bit to fix my data to this chart
my data looks like
date,close
0:15,0.433
0:30,0.919
0:45,0.750
1:00,0.699
1:15,0.629
1:30,0.896
1:45,0.794
2:00,0.802
2:15,0.866
2:30,0.943
2:45,0.750
3:00,0.518
3:15,0.721
3:30,0.649
3:45,0.816
4:00,0.698
4:15,0.403
4:30,0.772
4:45,0.605
5:00,0.721
5:15,0.684
5:30,0.559
5:45,0.697
6:00,0.751
and the code
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.area {
fill: steelblue;
}
</style>
<body>
<div class="test"></div>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 50},
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()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var area = d3.svg.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return y(d.close); });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("data/data2.csv", function(error, data) {
data.forEach(function(d) {
d.close = +d.close;
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.close; })]);
svg.append("path")
.data(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("Price ($)");
});
</script>
</body>
</html>
this doesn't work and i think i know why...because now the x-axis is not a "date" and the domain isn't set in the right way?! So what do you think should i do? Is there a way to say d3 that the x-axis is a timestemp every 15 minutes?! And then, is the data set in the right way with ".data(data)" and not ".datum(data)".
best regards
linda
There are at least 3 issues here:
You need to convert the time strings (eg 4:15) to javascript Date objects when you parse the csv. +d.close is insufficient; use d3.time.format('%H:%M').parse() method for that.
For the svg.append("path") bit, calling .data(data) on it ends up just assigning the first element of data to the path, instead of the entire array. That's just the way d3 selections work with data. This in turn causes .attr("d", area) to only pass the single data point to the area generator, instead of the whole set. Instead of .data(data), use .datum(data) (or, I think .data([data]) would work as well), which will bind the entire array to the path.
As you point out, the scale x needs to be d3.time.scale(). However, fixing the 2 things above should already make it present the graph, but instead of having dates along the x axis, you would see relly big numbers, corresponding to milliseconds since 1970.
That should do it, unless there are other issues I missed. If so, put this stuff into a jsfiddle and I'll take a look.