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.
Related
I have a bar chart graph, created with d3 v5, which needs to have a straight line as a "limit" depending on a specific y value.
This is the graph I currently have and the line which should be created (added on paint)
Here is the code in order to create the graph
(async ()=> {
const response = await fetch('https://api);
const myJson = await response.json();
//need myJson.DailyDelvs to be the y value of the line
// set the dimensions and margins of the graph
var margin = {top: 30, right: 30, bottom: 70, left: 60},
width = 400 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom
tip = d3.tip()
.attr('class', 'd3-tip')
.html(function(d) { return "DAY: "+d.DIA+"<br/>PO: "+d.PO_ID })
// append the svg object to the body of the page
var svg = d3.select("#dailyDeliveryVolume")
.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 + ")")
.call(tip)
var datos
// get the data
d3.json("https://api2").then(function(data) {
datos = data
d3.select("#dailyDeliveryVolume_spinner").remove();
var x = d3.scaleBand()
.range([ 0, width ])
.domain([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31])
.padding(0.05);
var xAxis = d3.axisBottom(x)
.tickValues(x.domain().filter(function(d,i){ return !(i%2)}));
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
var y = d3.scaleLinear()
.domain([0, d3.max(data.STD, function(d) { return +d.PO_ID })])
.range([ height, 0]);
svg.append("g")
.call(d3.axisLeft(y))
// Bars
svg.selectAll("mybar")
.data(data.STD)
.enter()
.append("rect")
.attr("x", function(d) { return x(d.DIA); })
.attr("y", function(d) { return y(d.PO_ID); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.PO_ID); })
.attr("fill", "#69b3a2")
.attr("border-color", "black")
.on("mouseover", tip.show)
.on("mouseleave", tip.hide )
})
})()
In order to create the line, I tried the following code, right after I append the bars, which is causing an error
var linea = drawLine(svg, x, y, data.STD);
var drawLine = function(svg, x, y, data) {
var lineFunc = d3.line()
.x(function(obj) {
return x(obj.DIA);
})
.y(function(obj) {
return y(obj.PO_ID);
});
var linea = svg.append("linea") //SVG Paths represent the outline of a shape that can be stroked, filled, used as a clipping path, or any combination of all three. We can draw rectangles, circles, ellipses, polylines, polygons, straight lines, and curves through path
.attr("d", lineFunc(data))
.attr("stroke", '#87CEEB')
.attr("stroke-width", 3)
.attr("fill", "black");
return linea;
};
dailyDeliveryVolume.js:64 Uncaught (in promise) TypeError: drawLine is not a function at dailyDeliveryVolume.js:64
I also tried to directly append a line to the svg with fixed attributes x1, x2, y1 and y2 but seems it is done base on the entire container and can't get to suit the x and y axis values.
The goal is that myJson.DailyDelvs is the y value (based on the y scale values) and then is just stright thru all width.
One issue is that you need to call the function (var linea = drawLine(...)) after defining the function (var drawLine = ...).
I think you can also simplify what you're doing by drawing a <line> directly:
svg.append("line")
.attr("x1", x(0))
.attr("x2", x(31))
.attr("y1", y(obj.PO_ID))
.attr("y2", y(obj.PO_ID))
.attr("stroke", '#87CEEB')
.attr("stroke-width", 3)
.attr("fill", "black");
(I'm not sure which value you are trying to use for the y-axis, but it seems like it should be similar to above...)
I'm new to D3.js and am trying to build a very-wide line chart that pans horizontally to show more data from a .csv file. I haven't been able to find many good resources for the latest version of D3.
What I'm trying to achieve:
chart fills the screen with overflow hidden
drag/mousewheel reveals more data from the right
axis labels stay put (but x-axis changes to reflect new data)
no zooming
The code I have right now renders the chart, and it can be clicked and dragged, but the whole thing just moves off the screen... Here's the code:
var margin = {top: 50, right: 50, bottom: 50, left: 50},
width = window.innerWidth - margin.left - margin.right,
height = window.innerHeight - margin.top - margin.bottom;
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var line = d3.line()
.x(function(d) { return x(d.mile); })
.y(function(d) { return y(d.elevation); })
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.call(d3.zoom().on("zoom", function () {
svg.attr("transform", d3.event.transform)
}))
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
function draw(data) {
data.forEach(function(d) {
d.mile = +d.mile;
d.elevation = +d.elevation;
});
x.domain(d3.extent(data, function(d) { return d.mile }) )
y.domain([0, d3.max(data, function(d) {
return Math.max(d.elevation) }) ]);
svg.append("path")
.data([data])
.attr("class", "line")
.attr("d", line)
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
}
d3.json("data.json", function(error, data) {
if (error) throw error;
draw(data)
});
And here's a sample of what the data looks like:
mile,elevation
1505.9,1800
1506.4,1360
1507.0,1340
1507.9,1750
1509.7,2365
Thanks in advance for any resources anyone can offer me to help solve this!
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>
A little background, I'm still fairly new to JS and development in general, so I may be missing something obvious. I got this chart working pretty well using d3, but I cannot get the positioning right no matter what I do. I've tried manipulating it with CSS and it just doesn't seem to behave in a logical way. I set display to block and margins to auto and it didn't affect it at all. The only way I can change the positioning is adjusting the margins in the d3 code, but that doesn't do very much for responsiveness. I've also tried using text-align and that didn't work either. What I'm trying to do is center it and have it scale larger as the screen size increases. This should all be easy to do in CSS in theory, but it just doesn't seem to work at all. Thanks for any help.
Here is the JS code:
// Set the dimensions of the canvas / graph
var margin = {top: 20, right: 0, bottom: 70, left: 70},
width = 300 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%-m/%-d/%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(10);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
// chart area fill
var area = d3.svg.area()
.x(function(d) { return x(d.Date); })
.y0(height)
.y1(function(d) { return y(d.Forecast); });
// Define the line
var valueline = d3.svg.line()
.interpolate("cardinal")
.x(function(d) { return x(d.Date); })
.y(function(d) { return y(d.Orders); });
var valueline2 = d3.svg.line()
.interpolate("cardinal")
.x(function(d) { return x(d.Date); })
.y(function(d) { return y(d.Forecast); });
// 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("csv/Forecast.csv", function(error, data) {
data.forEach(function(d) {
d.Date = parseDate(d.Date);
d.Orders = +d.Orders;
});
// 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.Orders; })]);
// Area
svg.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline2(data))
.style("stroke", "#A7A9A6");
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)
.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(yAxis);
});
JSFIDDLES
Responsive & Centered:
https://jsfiddle.net/sladav/6fyjhmne/3/
Not Responsive: https://jsfiddle.net/sladav/7tp5vdkr/
For responsiveness, take advantage of the SVG viewBox:
Some links:
https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox
Using ViewBox to resize svg depending on the window size
Setting up viewBox:
var margin = {top: 100, right: 150, bottom: 100, left: 150}
var outerWidth = 1600,
outerHeight = 900;
var width = outerWidth - margin.right - margin.left,
height = outerHeight - margin.top - margin.bottom;
d3.select(".plot-div").append("svg")
.attr("class", "plot-svg")
.attr("width", "100%")
.attr("viewBox", "0 0 " + outerWidth + " " + outerHeight)
.append("g")
.attr("class", "plot-space")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")"
);
Points of note in the above:
SVG is put in a div -- I'll adjust the size and position of the div rather than the svg
SVG width is set as % of parent/div, not absolute.
Everything you draw in SVG now is with respect to outerWidth x outerHeight and unitless parameters are rescaled by viewBox.
Taking a look at the rect in my JSFIDDLE example...
d3.select(".plot-svg").append("rect")
.attr("x", 0)
.attr("y", 3*outerHeight/4)
.attr("width", 800)
.attr("height", outerHeight/4)
.attr("fill", "grey")
Resize your window and the rectangle will always fill 1/2 the svg because 800/1600 (note: 1600 is outerWidth).
For adjusting position/centering:
Manipulate the div containing your SVG/chart to position it how you want to. In my example it takes up 50% of the page and is centered because of margin: auto.
.plot-div{
width: 50%;
display: block;
margin: auto;
}
Whatever methods you use to scale/position your div, your chart should follow suit.
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.