I am trying to build a simple line graph with d3.js and render it inside of a react component. Unfortunately, I get the following error:
Error: <path> attribute d: Expected number, "MNaN,36.393574100…"
The stackoverflow similar questions have unfortunately not been very helpful to me. My theory is I am not reading in the data object correctly (data.forEach) to my "d" attribute when I append the path to the chart. I have tried doing this both WITH and WITHOUT the parseDate function. When I do it WITHOUT it my dates look how they should in the console. If I include the parseDate function later on in the code while I am declaring my x axis and/or inside the x function of the path d attribute...nothing really changes. I'm also not entirely sure if my data object is nested correctly or not or if I should be serving the entire data object to the datum() function ....
Here is my code:
import * as d3 from "d3";
import React, { useRef, useEffect } from "react";
function LineBarChart({ width, height, data }) {
const ref = useRef();
useEffect(() => {
d3.select(ref.current)
.attr("width", width)
.attr("height", height)
// .style("border", "1px solid black");
}, [height, width]);
useEffect(() => {
draw();
}, [data]);
const draw = () => {
const parseDate = d3.timeFormat("%Y-%m-%d");
data.forEach(function (d) {
d.dateM = d.dateM;
// d.dateM = parseDate(d.dateM);
d.closeM = d.closeM;
console.log("WHAT-DO-I-LOOK-LIKE", d.dateM)
});
// const dataNest = d3.nest()
// .key((d) => d.dateM)
// .key((d) => d.closeM)
// .map(data);
const margin = 80;
const width = 1400 - 2 * margin;
const height = 800 - 2 * margin;
const svg = d3.select(ref.current);
const chart = svg
.append("g")
.attr("class", "linechart")
.attr("transform", `translate(${margin}, ${margin})`);
var x = d3.scaleTime()
.domain(d3.extent(data, function(d) { return d.dateM; }))
//.domain(d3.extent(data, function(d) { return parseDate(d.dateM); }))
.range([ 0, width ]);
chart.append("g")
.attr("transform", "translate(0," height ")")
.call(d3.axisBottom(x));
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d.closeM; })])
.range([ height, 0 ]);
chart.append("g")
.call(d3.axisLeft(y));
chart.append("path")
.datum(data)
.attr('class', 'lineChartClass')
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
// .x(function(d) { return parseDate(d.dateM) })
.x(function(d) { return x(d.dateM) })
.y(function(d) { return y(d.closeM) })
)
}; //draw
return (
<div className="chartSixPointFive">
<svg ref={ref}></svg>
</div>
);
}
export default LineBarChart;
My React app uses Axios to hit a Flask endpoint. There isn't anything fancy going on here, I just return the linechart like so.
....
....
<LineBarChart width={1350} height={1000} data={data} />
I can include the full react code if necessary (although I don't think it's needed)
Here is what my data object looks like:
"results": [
{
"date": "2022-02-07",
"open": 172.860001,
"high": 173.949997,
"low": 170.949997,
"close": 171.660004,
"volume": 77045100,
"adjClose": 171.660004
},
rinse & repeat ....
]
I appreciate ANY help I can get!
CodePudding user response:
d3.timeFormat
formats a date object and returns a string. What you want is d3.timeParse
instead, which will do the opposite: it will parse your strings into date objects, that can be used by the time scale:
const parseDate = d3.timeParse("%Y-%m-%d");
PS: the M command of the "d" attribute uses (as pretty much everything in every language) the coordinate pair x,y
(horizontal/vertical). Thus, when you have the error:
MNaN,42...
...you know the problem is in your x scale, while the error:
M42,NaN...
...shows you the problem is in the y scale.