0

I would like to create this plot in D3.js v7.

enter image description here

To do so, I wrote the following script:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>D3 Histogram</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <style>
        body {
            font-family: "Arial", sans-serif;
        }
        .bar {
            fill: steelblue;
        }
        .axis-label {
            font-size: 14px;
        }
        .grid line {
            stroke: lightgray;
            stroke-opacity: 0.7;
            shape-rendering: crispEdges;
        }
        .grid path {
            stroke-width: 0;
        }
        .background-rect {
            fill: #f0f0f0;
        }
    </style>
</head>
<body>
    <script>
        // Set the dimensions and margins of the graph
        const margin = { top: 20, right: 20, bottom: 70, left: 50 },
            width = 600 - margin.left - margin.right,
            height = 600 - margin.top - margin.bottom;

        // Append the SVG object to the body of the page
        const 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})`);

        // Add a background rectangle
        svg.append("rect")
            .attr("class", "background-rect")
            .attr("width", width)
            .attr("height", height)
            .attr("fill", "#f0f0f0");

        // Parse the CSV data
        d3.csv("penguins.csv").then(function(data) {
            console.log(data)
            // Convert flipper_length_mm to numbers
            data.forEach(d => d.flipper_length_mm = +d.flipper_length_mm);

            // Set the x scale
            const x = d3.scaleLinear()
                .domain([170, 240])
                .range([0, width]);

            // Set the histogram parameters
            const histogram = d3.bin()
                .value(d => d.flipper_length_mm)
                .domain(x.domain())
                .thresholds(x.ticks(12));

            // Group the data for the bars
            const bins = histogram(data);

            // Set the y scale
            const y = d3.scaleLinear()
                .domain([0, d3.max(bins, d => d.length)])
                .range([height, 0]);

            // Add the x gridlines
            svg.append("g")
                .attr("class", "grid")
                .attr("transform", `translate(0,${height})`)
                .call(d3.axisBottom(x)
                    .tickSize(-height)
                    .tickFormat(""));

            // Add the y gridlines
            svg.append("g")
                .attr("class", "grid")
                .call(d3.axisLeft(y)
                    .tickSize(-width)
                    .tickFormat(""));

            // Append the bars
            svg.selectAll("rect.bar")
                .data(bins)
                .enter().append("rect")
                .attr("class", "bar")
                .attr("x", 1)
                .attr("transform", d => `translate(${x(d.x0)}, ${y(d.length)})`)
                .attr("width", d => x(d.x1) - x(d.x0) - 1)
                .attr("height", d => height - y(d.length));

            // Add the x axis
            svg.append("g")
                .attr("transform", `translate(0,${height})`)
                .call(d3.axisBottom(x))
                .selectAll("text")
                .attr("transform", "rotate(-45)")
                .style("text-anchor", "end");

            // Add the y axis
            svg.append("g")
                .call(d3.axisLeft(y));

            // Add axis labels
            svg.append("text")
                .attr("class", "axis-label")
                .attr("transform", `translate(${width / 2}, ${height + margin.bottom - 20})`)
                .style("text-anchor", "middle")
                .text("flipper_length_mm");

            svg.append("text")
                .attr("class", "axis-label")
                .attr("transform", "rotate(-90)")
                .attr("y", -margin.left + 20)
                .attr("x", -(height / 2))
                .style("text-anchor", "middle")
                .text("Count");
        });
    </script>
</body>
</html>

Because of character limit, I cannot include csv file in HTML file, so here it https://github.com/mwaskom/seaborn-data/blob/master/penguins.csv

However, in original seaborn histogram, y-axis is up to 80, but mine is up to 60. Why is that? I look at the code and error messages, but only saw that **Document. domain: string Deprecated symbol used, consult docs for better alternative ** How can I have the exact same histogram of seaborn shown in the first image using d3 js v7?

0

1 Answer 1

2

As Yogi mentions in the comments, seaborn and d3 are using different binning thresholds for the histogram. To me the seaborn ones are odd as they do not fall on nice round increments (see edits below) but instead seem to start at the min of the data, 172 and then increment by 6. To replicate that with d3 change your bin function to:

const histogram = d3.bin()
    .value(d => d.flipper_length_mm)
    .domain(x.domain())
    .thresholds(d3.range(172,237,6));

The d3.range call will produce bins as:

[172, 178, 184, 190, 196, 202, 208, 214, 220, 226, 232]

Here it is running.

EDITS

Curiosity got the better of me so I looked up why Seaborn generates the bins the way it does. It is actually a quite sophisticated implementation which by default uses a numpy method. D3 also has implementations of the same algorithms but out of the box they didn't produce the same bins as Seaborn.

1
  • then, we could say d3 algorithms are less odd than seaborn ones? :) Commented Jun 28 at 8:09

Not the answer you're looking for? Browse other questions tagged or ask your own question.