Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3 bar chart returning height of "NaN"

I'm attempting to display a basic animated bar-chart using D3 (v7) to my web page. This is being done from within a Django project and so my chart is being fed data from my views.py file. However, the chart only partially displays on the page. The x and y axis lines are displayed, along with the axis labels. Here is my error message:

Error: <rect> attribute height: Expected length, "NaN".

Here is the relevant part of my html template:

 <div id="chart-container"></div>

        <script src="https://d3js.org/d3.v7.min.js"></script>

        <script type="text/javascript">
          const data = JSON.parse('{{ assign_levels|safe }}');

          const margin = {top: 10, right: 30, bottom: 140, left: 60};
          const width = 400 - margin.left - margin.right;
          const height = 500 - margin.top - margin.bottom;

          const svg = d3.select("#chart-container")
              .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})`);

          const x = d3.scaleBand()
              .range([0, width])
              .domain(data.map(d => d.level_assign))
              .padding(0.3);
          
          const y = d3.scaleLinear()
              .range([height, 0])
              .domain([0, d3.max(data, d => d.count)]);

          svg.append("g")
              .call(d3.axisLeft(y));

          svg.selectAll(".bar")
              .data(data)
              .enter()
              .append("rect")
              .attr("class", "bar")
              .attr("x", d => x(d.level_assign))
              .attr("width", x.bandwidth())
              .attr("y", height)
              .attr("height", 0)
              .attr("fill", "#5F9EA0")
              .transition()
              .duration(1000)
              .delay((d, i) => i * 100)
              .attr("y", d => y(d.count))
              .attr("height", d => height - y(d.count));

          svg.append("g")
              .attr("transform", `translate(0, ${height})`)
              .call(d3.axisBottom(x))
              .selectAll("text")
              .attr("transform", "rotate(-45)")
              .style("text-anchor", "end")
              .attr("dx", "-.8em")
              .attr("dy", ".15em");

          svg.append("text")
              .attr("transform", "rotate(-90)")
              .attr("y", 0 - margin.left)
              .attr("x",0 - (height / 2))
              .attr("dy", "1em")
              .style("text-anchor", "middle")
              .text("Study Count");

          svg.append("text")
              .attr("x", (width + margin.left + margin.right) / 2)
              .attr("y", margin.top / 2)
              .attr("text-anchor", "middle")
              .style("font-size", "16px")
              .text("Level of Assignment");
      </script>

And here is the code I am inputting to the chart:

[["Class", 36], ["School - cluster", 6], ["Individual", 20], ["N", 1], ["School - multi-site", 9], ["Not provided/ not available", 4], ["Region or district", 1]]

I can't understand why the error message is telling me that it has received a "NaN" for height as I can see that the height has been set in absolute terms e.g. 500.

like image 902
Beatdown Avatar asked Feb 01 '26 06:02

Beatdown


1 Answers

You're passing an array of arrays as data but the D3 code expects an array of objects with specific keys. I tried to convert the data format into an array of objects.

You need to update your code slightly to achieve this. Check the code below:

const rawData = JSON.parse('{{ assign_levels|safe }}');
const data = rawData.map(d => ({level_assign: d[0], count: d[1]}));

This will convert your data to this format instead of a list of arrays.

[
  {"level_assign": "Class", "count": 36},
  {"level_assign": "School - cluster", "count": 6},
  {"level_assign": "Individual", "count": 20},
  {"level_assign": "N", "count": 1},
  {"level_assign": "School - multi-site", "count": 9},
  {"level_assign": "Not provided/ not available", "count": 4},
  {"level_assign": "Region or district", "count": 1}
]

Here is an example with full code:

//script.js
const rawData = [
    ["Class", 36],
    ["School - cluster", 6],
    ["Individual", 20],
    ["N", 1],
    ["School - multi-site", 9],
    ["Not provided/ not available", 4],
    ["Region or district", 1],
];

const data = rawData.map((d) => ({
    level_assign: d[0],
    count: d[1]
}));

const margin = {
    top: 10,
    right: 30,
    bottom: 140,
    left: 60
};
const width = 400 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;

const svg = d3
    .select("#chart-container")
    .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})`);

const x = d3
    .scaleBand()
    .range([0, width])
    .domain(data.map((d) => d.level_assign))
    .padding(0.3);

const y = d3
    .scaleLinear()
    .range([height, 0])
    .domain([0, d3.max(data, (d) => d.count)]);

svg.append("g").call(d3.axisLeft(y));

svg.selectAll(".bar")
    .data(data)
    .enter()
    .append("rect")
    .attr("class", "bar")
    .attr("x", (d) => x(d.level_assign))
    .attr("width", x.bandwidth())
    .attr("y", height)
    .attr("height", 0)
    .attr("fill", "#5F9EA0")
    .transition()
    .duration(1000)
    .delay((d, i) => i * 100)
    .attr("y", (d) => y(d.count))
    .attr("height", (d) => height - y(d.count));

svg.append("g")
    .attr("transform", `translate(0, ${height})`)
    .call(d3.axisBottom(x))
    .selectAll("text")
    .attr("transform", "rotate(-45)")
    .style("text-anchor", "end")
    .attr("dx", "-.8em")
    .attr("dy", ".15em");

svg.append("text")
    .attr("transform", "rotate(-90)")
    .attr("y", 0 - margin.left)
    .attr("x", 0 - (height / 2))
    .attr("dy", "1em")
    .style("text-anchor", "middle")
    .text("Study Count");

svg.append("text")
    .attr("x", (width + margin.left + margin.right) / 2)
    .attr("y", margin.top / 2)
    .attr("text-anchor", "middle")
    .style("font-size", "16px")
    .text("Level of Assignment");
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>D3 Bar Chart</title>
</head>
<body>
    <div id="chart-container"></div>

    <script src="https://d3js.org/d3.v7.min.js"></script>
    <script src="script.js"></script>
</body>
</html>
like image 84
Mohammad Golam Dostogir Avatar answered Feb 03 '26 19:02

Mohammad Golam Dostogir