Creating an area chart: path with NaN - javascript

I am starting with D3 and trying to recreate example from the url
I am getting this error
Error: <path> attribute d: Expected number, "MNaN,NaNLNaN,NaNL…"
Attribute d is referred to each and every data. But why is it coming as MNaN?
I would be thankful, if the solution provider explain how the debug is done.
data = [
{
"date": "2007-04-23",
"close": 93.24
},
{
"date": "2007-04-24",
"close": 95.35
}];
//Update 1
data = data.map((item) => ({date:item.date, value:item.close}))
data.y = "$ Close";
//Update 1
height = 500;
width = 500;
margin = ({ top: 20, right: 20, bottom: 30, left: 30 });
x = d3.scaleTime()
.domain(d3.extent(data, d => d.date))
.range([margin.left, width - margin.right])
y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)]).nice()
.range([height - margin.bottom, margin.top])
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0));
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove())
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 3)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text(data.y))
area = d3.area()
.x(d => x(d.date))
.y0(y(0))
.y1(d => y(d.value))
const svg = d3.select('svg')
.attr('width', width)
.attr('height', height)
svg.append("path")
.datum(data)
.attr("fill", "steelblue")
.attr("d", area);
svg.append("g")
.call(xAxis);
svg.append("g")
.call(yAxis);
Here is the JSFIDDLE
Update 1
I need have the data in date and value and y..... but i had date and close..... updated the code in jsfiddle too.
Now i am getting the x and y axis but the error message remains the same.
Update 1 Fiddle

Understanding the error in the path's d attribute
When you get a NaN in a path creates by a line (or an area for that matter), you have to check where is the NaN. For instance:
MNaN,NaNL...
↑ ↑
x y
This shows that the problem is in both x and y methods. On the other hand, when you get:
MNaN,42L...
↑ ↑
x y
The problem is in the x method, while...
M42,NaNL...
↑ ↑
x y
... shows that the problem is in the y method. According to your updated JSFiddle, the problem comes from the x method.
Your problem
The problem is that you're passing things like this to the time scale:
"2007-04-24"
This is not a date, this is just a string. You have to parse the dates. For instance, given your string format:
data.forEach(function(d){
d.date = d3.timeParse("%Y-%m-%d")(d.date);
});
Here is the code with that change:
data = [{
"date": "2007-04-23",
"close": 93.24
},
{
"date": "2007-04-24",
"close": 95.35
}
];
data.forEach(function(d) {
d.date = d3.timeParse("%Y-%m-%d")(d.date);
d.value = d.close;
})
height = 500;
width = 500;
margin = ({
top: 20,
right: 20,
bottom: 30,
left: 30
});
x = d3.scaleTime()
.domain(d3.extent(data, d => d.date))
.range([margin.left, width - margin.right])
y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)]).nice()
.range([height - margin.bottom, margin.top])
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0));
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove())
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 3)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text(data.y))
area = d3.area()
.x(d => x(d.date))
.y0(y(0))
.y1(d => y(d.value))
const svg = d3.select('svg')
.attr('width', width)
.attr('height', height)
svg.append("path")
.datum(data)
.attr("fill", "steelblue")
.attr("d", area);
svg.append("g")
.call(xAxis);
svg.append("g")
.call(yAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

Related

d3.js line chat grid issue

I am new to d3 library and I have made a line chart containing grid. I am having trouble in reducing grid lines length so that they can match the lengths with x axis and y axis lines.
Code link : https://codepen.io/liot/pen/yLYyRZj
Line chart Image highlighting extra length of grid lines:
Below is my code
const data = [
{ date: "01/01/2016", value: 10000 },
{ date: "01/02/2016", value: 20000 },
{ date: "01/03/2016", value: 40000 },
{ date: "01/04/2016", value: 30000 },
{ date: "01/05/2016", value: 30000 }
];
const width = 600;
const height = 400;
const margin = { top: 10, right: 40, bottom: 40, left: 70 };
const parseDate = d3.timeParse("%m/%d/%Y");
const xFormat = "%d-%b";
const yMax = d3.max(data, (d) => d.value);
const x = d3
.scaleTime()
.domain(d3.extent(data, (d) => parseDate(d.date)))
.rangeRound([margin.left, width - margin.right])
.nice(data.length);
const y = d3
.scaleLinear()
.domain([0, yMax < 10 ? 10 : yMax])
.range([height - margin.bottom, margin.top])
.nice();
const xAxis = (g) =>
g
.style("font-size", "15px")
.style("color", "#424242")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).tickFormat(d3.timeFormat(xFormat)).ticks(d3.timeDay))
.call((g) => g.select(".domain"))
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("y2", -height)
.attr("stroke-opacity", 0.2)
);
const yAxis = (g) =>
g
.style("font-size", "15px")
.style("color", "#424242")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call((g) => g.select(".domain"))
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("x2", width)
.attr("stroke-opacity", 0.2)
);
const line = d3
.line()
// eslint-disable-next-line no-restricted-globals
.defined((d) => !isNaN(d.value))
.x((d) => x(parseDate(d.date)))
.y((d) => y(d.value));
const svg = d3
.select("body")
.append("svg")
.attr("viewBox", [0, 0, width, height]);
svg.append("g").call(xAxis);
svg.append("g").call(yAxis);
svg
.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "#007bff")
.attr("stroke-width", 3)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("d", line.curve(d3.curveBasis));
CSS code is below :
body {
width:600px;
height:400px;
}
These two lines below which control the lengths of x2 and y2 need to be adjusted:
.attr("y2", - height + 50)
.attr("x2", width - 110)
Complete Code:
const data = [
{ date: "01/01/2016", value: 10000 },
{ date: "01/02/2016", value: 20000 },
{ date: "01/03/2016", value: 40000 },
{ date: "01/04/2016", value: 30000 },
{ date: "01/05/2016", value: 30000 }
];
const width = 600;
const height = 400;
const margin = { top: 10, right: 40, bottom: 40, left: 70 };
const parseDate = d3.timeParse("%m/%d/%Y");
const xFormat = "%d-%b";
const yMax = d3.max(data, (d) => d.value);
const x = d3
.scaleTime()
.domain(d3.extent(data, (d) => parseDate(d.date)))
.rangeRound([margin.left, width - margin.right])
.nice(data.length);
const y = d3
.scaleLinear()
.domain([0, yMax < 10 ? 10 : yMax])
.range([height - margin.bottom, margin.top])
.nice();
const xAxis = (g) =>
g
.style("font-size", "15px")
.style("color", "#424242")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).tickFormat(d3.timeFormat(xFormat)).ticks(d3.timeDay))
.call((g) => g.select(".domain"))
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("y2", - height+50)
.attr("stroke-opacity", 0.2)
);
const yAxis = (g) =>
g
.style("font-size", "15px")
.style("color", "#424242")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call((g) => g.select(".domain"))
.call((g) =>
g
.selectAll(".tick line")
.clone()
.attr("x2", width -110)
.attr("stroke-opacity", 0.2)
);
const line = d3
.line()
// eslint-disable-next-line no-restricted-globals
.defined((d) => !isNaN(d.value))
.x((d) => x(parseDate(d.date)))
.y((d) => y(d.value));
const svg = d3
.select("body")
.append("svg")
.attr("viewBox", [0, 0, width, height]);
svg.append("g").call(xAxis);
svg.append("g").call(yAxis);
svg
.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "#007bff")
.attr("stroke-width", 3)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("d", line.curve(d3.curveBasis));
CSS Code:
body {
width:600px;
height:400px;
}

D3 bar chart add legend

I have made a bar chart in d3 but I am struggeling with 2 things.
First of all I want to add a legend because I have 2 different bars so I want people to know what each bar is.
The second I want to load data from a json file instead of this:
var data = [
{
"year":1970,
"count1":1,
"count2":0
},
{
"year":1975,
"count1":1,
"count2":0
},
{
"year":1980,
"count1":1,
"count2":1
}]
But when I try to use a json file my map function gives an error.
My code :
var svgSelect = d3.select("svg")
var width = 1020
var height = 900
var margin = {top: 35, right: 25, bottom: 35, left: 50}
var paddingBars = .2
var axisTicks = {amount: 25, outerSize: 0}
var svg = svgSelect
.append("svg")
.attr("widht", width)
.attr("height", height)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`)
var xScale0 = d3.scaleBand().range([0, width - margin.left - margin.right]).padding(paddingBars)
var xScale1 = d3.scaleBand()
var yScale = d3.scaleLinear().range([height - margin.top - margin.bottom, 0])
var xAxis = d3.axisBottom(xScale0).tickSizeOuter(axisTicks.outerSize)
var yAxis = d3.axisLeft(yScale).ticks(axisTicks.amount).tickSizeOuter(axisTicks.outerSize)
xScale0.domain(data.map(d => d.year))
xScale1.domain(['count1', 'count2']).range([0, xScale0.bandwidth()])
yScale.domain([0, d3.max(data, d => d.count1 > d.count2 ? d.count1 : d.count2)])
var year = svg.selectAll(".year")
.data(data)
.enter().append("g")
.attr("class", "year")
.attr("transform", d => `translate(${xScale0(d.year)},0)`)
/* Add count1 bars */
year.selectAll(".bar.count1")
.data(d => [d])
.enter()
.append("rect")
.attr("class", "bar count1")
.style("fill","blue")
.attr("x", d => xScale1('count1'))
.attr("y", d => yScale(d.count1))
.attr("width", xScale1.bandwidth())
.attr("height", d => {
return height - margin.top - margin.bottom - yScale(d.count1)
})
/* Add coun2 bars */
year.selectAll(".bar.count2")
.data(d => [d])
.enter()
.append("rect")
.attr("class", "bar count2")
.style("fill","red")
.attr("x", d => xScale1('count2'))
.attr("y", d => yScale(d.count2))
.attr("width", xScale1.bandwidth())
.attr("height", d => {
return height - margin.top - margin.bottom - yScale(d.count2)
})
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0,${height - margin.top - margin.bottom})`)
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)

Y Axis not displaying for scatter plot with d3 v4

I am trying to create a scatter plot with d3.js (v4). I am a newbie in d3, and thanks to the limited documentation of examples with v4, am struggling to create the plot (already have asked here once before). My code is given below:
const margin = { top: 100, right: 50, left: 50, bottom: 50};
const width = 1300 - margin.right - margin.left;
const height = 1250 - margin.top - margin.bottom;
d3.csv("http://localhost:9000/data.csv", (error, data) => {
if (error) throw error;
const x = (d) => d["Category"];
const xScale = d3.scalePoint()
.domain(data.map((d) => d["Category"]))
.range([0, width]);
const xMap = (d) => xScale(x(d));
const xAxis = d3.axisBottom().scale(xScale);
// Plotting Score 1 for now
const y = (d) => d["Score 1"];
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, y)])
.range([height, 0]);
const yMap = (d) => yScale(y(d))
const yAxis = d3.axisLeft().scale(yScale);
const svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
svg.append('g')
.attr('class', 'axis axis--x')
.call(xAxis)
.attr('transform', 'translate(0, 800)')
.append('text')
.attr('class', 'label')
.attr('x', width)
.attr('y', -6)
.style('text-anchor', 'middle')
.text('Category');
svg.append('g')
.attr('class', 'axis axis--y')
.call(yAxis)
.attr('transform', 'rotate(-90)')
.attr('y', 0 - margin.left)
.attr('x', 0 - (height/2))
.attr("dy", "1em")
.style('text-anchor', 'middle')
.text('Score');
svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('class', 'dot')
.attr('cx', xMap)
.attr('cy', yMap)
.attr('r', 3.5)
.attr('fill', 'red');
});
A few lines of data.csv are given:
Name,Category,Score 1,Score 2,Score 3,Average score
A1_P1,A01,2.3,2.4,4.1,2.4
A2_P1,A02,1.4,1.5,2.4,1.5
A3_P1,A03,0.9,0.9,0.9,0.9
A4_P1,B01,1.5,1.5,1,1.5
A5_P1,B02,1.2,1.2,1.4,1.2
A6_P1,B03,1,1,1.1,1
A7_P1,C01,1.6,1.2,1,1.2
A8_P1,C02,1.2,1.2,0.9,1.2
A9_P1,C03,1.1,1.1,1,1.1
A10_P1,D01,1.5,1.6,1.1,1.5
The x-axis is showing (but not the 'Category' label), and even more importantly, the y-axis is not showing at all. The points themselves are being shown correctly, though. Does anyone know what is wrong with my y-axis setting and axis labels? Thanks in advance!
When I start a new chart with d3, I find it best to start with a known simple set-up example to place my drawing g, axis, etc... This is my usual starting point.
That said, here's list of issues I see in your chart:
Not placing x-axis dynamically.
svg container is sized wrong and ends up on top of axis.
Attempting to append a text axis label to y axis but you never actually append the text (and then apply attributes and styling to the axis itself instead of that missing text element).
Cleaning this all up looks like this:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
const margin = {
top: 100,
right: 50,
left: 50,
bottom: 50
};
const width = 500 - margin.right - margin.left;
const height = 500 - margin.top - margin.bottom;
//d3.csv("data.csv", (error, data) => {
// if (error) throw error;
var data = [{"Name":"A1_P1","Category":"A01","Score 1":"2.3","Score 2":"2.4","Score 3":"4.1","Average score":"2.4"},{"Name":"A2_P1","Category":"A02","Score 1":"1.4","Score 2":"1.5","Score 3":"2.4","Average score":"1.5"},{"Name":"A3_P1","Category":"A03","Score 1":"0.9","Score 2":"0.9","Score 3":"0.9","Average score":"0.9"},{"Name":"A4_P1","Category":"B01","Score 1":"1.5","Score 2":"1.5","Score 3":"1","Average score":"1.5"},{"Name":"A5_P1","Category":"B02","Score 1":"1.2","Score 2":"1.2","Score 3":"1.4","Average score":"1.2"},{"Name":"A6_P1","Category":"B03","Score 1":"1","Score 2":"1","Score 3":"1.1","Average score":"1"},{"Name":"A7_P1","Category":"C01","Score 1":"1.6","Score 2":"1.2","Score 3":"1","Average score":"1.2"},{"Name":"A8_P1","Category":"C02","Score 1":"1.2","Score 2":"1.2","Score 3":"0.9","Average score":"1.2"},{"Name":"A9_P1","Category":"C03","Score 1":"1.1","Score 2":"1.1","Score 3":"1","Average score":"1.1"},{"Name":"A10_P1","Category":"D01","Score 1":"1.5","Score 2":"1.6","Score 3":"1.1","Average score":"1.5"}];
const x = (d) => d["Category"];
const xScale = d3.scalePoint()
.domain(data.map((d) => d["Category"]))
.range([0, width]);
const xMap = (d) => xScale(x(d));
const xAxis = d3.axisBottom().scale(xScale);
// Plotting Score 1 for now
const y = (d) => d["Score 1"];
const yScale = d3.scaleLinear()
.domain([0, d3.max(data, y)])
.range([height, 0]);
const yMap = (d) => yScale(y(d))
const yAxis = d3.axisLeft().scale(yScale);
const svg = d3.select('body')
.append('svg')
.attr('width', width + margin.right + margin.left)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
svg.append('g')
.attr('class', 'axis axis--x')
.call(xAxis)
.attr('transform', 'translate(0,' + height + ')')
.append('text')
.attr('class', 'label')
.attr('x', width)
.attr('y', -6)
.style('text-anchor', 'middle')
.text('Category');
svg.append('g')
.attr('class', 'axis axis--y')
.call(yAxis)
.append("text")
.attr('transform', 'rotate(-90)')
.attr('y', 0 - margin.left)
.attr('x', 0 - (height / 2))
.attr("dy", "1em")
.style('text-anchor', 'middle')
.text('Score')
.style('fill', 'black')
svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('class', 'dot')
.attr('cx', xMap)
.attr('cy', yMap)
.attr('r', 3.5)
.attr('fill', 'red');
//});
</script>
</body>
</html>

Create scatter plot with ordinal scale in d3 version 4

I am trying to create a scatter plot with d3 v4. I have been through many examples for v3 and v4, but there are not many explaining how to create a graph with an ordinal scale in v4. My code is given below:
const margin = { top: 100, right: 50, left: 50, bottom: 50};
const width = 1500 - margin.right - margin.left;
const height = 1250 - margin.top - margin.bottom;
d3.csv("http://localhost:9000/data.csv", (error, data) => {
if (error) throw error;
const x = (d) => d["Category"];
const xScale = d3.scaleOrdinal()
.domain(data.map((d) => d["Category"]))
.range([0, width]);
const xAxis = d3.axisBottom(xScale);
const y = (d) => d["Score"];
const yScale = d3.scaleLinear()
.range([height, 0]);
yScale.domain([d3.min(data, y)-1, d3.max(data, y)+1]);
const yAxis = d3.axisLeft(yScale);
const svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
svg.append('g')
.attr('class', 'x axis')
.call(xAxis)
.attr('transform', 'translate(0, 400)')
.append('text')
.attr('class', 'label')
.attr('x', width)
.attr('y', -6)
.style('text-anchor', 'end')
.text('Category');
svg.append('g')
.attr('class', 'y axis')
.call(yAxis)
.attr('class', 'label')
.attr('transform', 'rotate(-90)')
.attr('y', 6)
.attr('dy', '.71rem')
.text('Score');
svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('class', 'dot')
.attr('cx', (d) => {
console.log(x(d));
return x(d);
})
.attr('cy', (d) => y(d))
.attr('r', 5)
.attr('fill', 'red');
});
CSS:
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
My problem is that nothing is really appearing. I get this error message for every x value I want to show on the ordinal scale:
Error: <circle> attribute cx: Expected length, "B10".
The y-axis does not appear at all, and the x-axis appears without the scale and the 'Category' text. Please help!
Don't use a scaleOrdinal here, since you have a continuous range.
Use a scalePoint instead. According to the API:
Point scales are a variant of band scales with the bandwidth fixed to zero. Point scales are typically used for scatterplots with an ordinal or categorical dimension.
Thus, it should be:
const xScale = d3.scalePoint()
.domain(data.map((d) => d["Category"]))
.range([0, width]);
Besides that, you should use the scale to set the cx attribute, not the "x" const:
.attr('cx', (d) => xScale(d.Category))

D3 the x axis positions in wrong way

I have run into problem of displaying the chart X axis which is translated in wrong position. Here is my example http://jsfiddle.net/staar2/Vww3h/1/.
As you can look from the html inspector the svg elements get translated wrong. What I think the xScale are set wrong.
var data = JSON.parse('[{"hour":0,"time":147},{"hour":1,"time":0},{"hour":2,"time":74},{"hour":3,"time":141},{"hour":4,"time":137},{"hour":5,"time":210},{"hour":6,"time":71},{"hour":7,"time":73},{"hour":8,"time":0},{"hour":9,"time":68},{"hour":10,"time":70},{"hour":11,"time":0},{"hour":12,"time":147},{"hour":13,"time":0},{"hour":14,"time":0},{"hour":15,"time":69},{"hour":16,"time":67},{"hour":17,"time":67},{"hour":18,"time":66},{"hour":19,"time":0},{"hour":20,"time":0},{"hour":21,"time":66},{"hour":22,"time":210},{"hour":23,"time":0}] ');
var w = 15,
h = 80;
var xScale = d3.scale.linear()
.domain([0, 1])
.range([0, w]);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, function (d) {
return d.time;
})])
.rangeRound([5, h]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
var chart = d3.select("#viz")
.append("svg")
.attr("class", "chart")
.attr("width", w * data.length - 1)
.attr("height", h);
chart.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function(d, i) {
return xScale(i) - 0.5;
})
.attr("y", function(d) {
return h - yScale(d.time) - 0.5;
})
.attr("width", w)
.attr("height", function(d) {
return yScale(d.time);
});
chart.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
if (d.time > 10) {
return Math.round(d.time);
}
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "#FFF")
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return xScale(i) + w / 2;
})
.attr("y", function(d) {
return h - yScale(d.time) - 0.5 + 10;
});
chart.append("g")
.attr("transform", "translate(0," + (h) + ")")
.call(xAxis);
You need to change your xScale domain and range as well accordingly, as
xScale
.domain([0, data.length-1])
.range([0, w*(data.length-1)])
while you call xScale(i) where i is the index of data element, as far as i understood, and so the domain should be between 0 - data.length.

Categories