block by curran a6c261aca1a12452111cb1b797c04d70

React & D3 Starter

Full Screen

Exported from VizHub: React & D3 Starter.

This viz is a starter that demonstrates patterns used in real world projects in which complexity is anticipated to scale.

Any questions? Ask on Twitter!

index.js

import { App } from './App';
import ReactDOM from 'react-dom';

ReactDOM.render(
  <App outputPath="" />,
  document.getElementById('root')
);

index.html

<!DOCTYPE html><html><head>
    <title>React &amp; D3 Starter</title>
    <link rel="stylesheet" href="styles.css">
  <script src="https://unpkg.com/d3@7.4.4/dist/d3.min.js"></script><script src="https://unpkg.com/react@18.1.0/umd/react.production.min.js"></script><script src="https://unpkg.com/react-dom@18.1.0/umd/react-dom.production.min.js"></script></head>
  <body>
    <div id="root"></div>
  

<script src="bundle.js"></script></body></html>

App.js

VizWrapper.js

import { useRef, useEffect } from 'react';
import { select } from 'd3';
import { viz } from './viz';

export const VizWrapper = ({ data }) => {
  const ref = useRef();

  const width = window.innerWidth;
  const height = window.innerHeight;

  useEffect(() => {
    viz(select(ref.current), {
      data,
      xValue: (d) => d.sepal_length,
      yValue: (d) => d.petal_length,
      width,
      height,
      margin: {
        top: 20,
        right: 20,
        bottom: 40,
        left: 40,
      },
      circleRadius: 10,
    });
  }, [data]);

  return (
    <svg
      width={width}
      height={height}
      ref={ref}
    />
  );
};

axes.js

import { axisLeft, axisBottom } from 'd3';

export const axes = (
  selection,
  { height, margin, xScale, yScale }
) => {
  const { left, bottom } = margin;

  selection
    .selectAll('g.axis-y')
    .data([null])
    .join('g')
    .attr('class', 'axis axis-y')
    .attr('transform', `translate(${left},0)`)
    .call(axisLeft(yScale));

  selection
    .selectAll('g.axis-x')
    .data([null])
    .join('g')
    .attr('class', 'axis axis-x')
    .attr(
      'transform',
      `translate(0,${height - bottom})`
    )
    .call(axisBottom(xScale));
};

bundle.js

(function (react, d3, ReactDOM) {
  'use strict';

  ReactDOM = ReactDOM && Object.prototype.hasOwnProperty.call(ReactDOM, 'default') ? ReactDOM['default'] : ReactDOM;

  const parseRow = (d) => {
    d.sepal_length = +d.sepal_length;
    d.sepal_width = +d.sepal_width;
    d.petal_length = +d.petal_length;
    d.petal_width = +d.petal_width;
    return d;
  };

  const useData = (outputPath) => {
    const [data, setData] = react.useState(null);

    react.useEffect(async () => {
      setData(await d3.csv('data.csv', parseRow));
    }, []);

    return data;
  };

  const axes = (
    selection,
    { height, margin, xScale, yScale }
  ) => {
    const { left, bottom } = margin;

    selection
      .selectAll('g.axis-y')
      .data([null])
      .join('g')
      .attr('class', 'axis axis-y')
      .attr('transform', `translate(${left},0)`)
      .call(d3.axisLeft(yScale));

    selection
      .selectAll('g.axis-x')
      .data([null])
      .join('g')
      .attr('class', 'axis axis-x')
      .attr(
        'transform',
        `translate(0,${height - bottom})`
      )
      .call(d3.axisBottom(xScale));
  };

  const viz = (
    selection,
    {
      data,
      xValue,
      yValue,
      width,
      height,
      margin,
      circleRadius,
    }
  ) => {
    const { top, right, bottom, left } = margin;

    const xScale = d3.scaleLinear()
      .domain(d3.extent(data, xValue))
      .range([left, width - right]);

    const yScale = d3.scaleLinear()
      .domain(d3.extent(data, yValue))
      .range([height - bottom, top]);

    selection
      .selectAll('circle')
      .data(data)
      .join('circle')
      .attr('r', circleRadius)
      .attr('fill-opacity', 0.5)
      .attr('cx', (d) => xScale(xValue(d)))
      .attr('cy', (d) => yScale(yValue(d)));

    axes(selection, {
      height,
      margin,
      xScale,
      yScale,
    });
  };

  const VizWrapper = ({ data }) => {
    const ref = react.useRef();

    const width = window.innerWidth;
    const height = window.innerHeight;

    react.useEffect(() => {
      viz(d3.select(ref.current), {
        data,
        xValue: (d) => d.sepal_length,
        yValue: (d) => d.petal_length,
        width,
        height,
        margin: {
          top: 20,
          right: 20,
          bottom: 40,
          left: 40,
        },
        circleRadius: 10,
      });
    }, [data]);

    return (
      React.createElement( 'svg', {
        width: width, height: height, ref: ref })
    );
  };

  const App = () => {
    const data = useData();
    return data ? (
      React.createElement( VizWrapper, { data: data })
    ) : (
      // Could be expanded with a real loading indicator.
      'Loading...'
    );
  };

  ReactDOM.render(
    React.createElement( App, { outputPath: "" }),
    document.getElementById('root')
  );

}(React, d3, ReactDOM));

//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VzIjpbInVzZURhdGEuanMiLCJheGVzLmpzIiwidml6LmpzIiwiVml6V3JhcHBlci5qcyIsIkFwcC5qcyIsImluZGV4LmpzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHVzZVN0YXRlLCB1c2VFZmZlY3QgfSBmcm9tICdyZWFjdCc7XG5pbXBvcnQgeyBjc3YgfSBmcm9tICdkMyc7XG5cbmNvbnN0IHBhcnNlUm93ID0gKGQpID0+IHtcbiAgZC5zZXBhbF9sZW5ndGggPSArZC5zZXBhbF9sZW5ndGg7XG4gIGQuc2VwYWxfd2lkdGggPSArZC5zZXBhbF93aWR0aDtcbiAgZC5wZXRhbF9sZW5ndGggPSArZC5wZXRhbF9sZW5ndGg7XG4gIGQucGV0YWxfd2lkdGggPSArZC5wZXRhbF93aWR0aDtcbiAgcmV0dXJuIGQ7XG59O1xuXG5leHBvcnQgY29uc3QgdXNlRGF0YSA9IChvdXRwdXRQYXRoKSA9PiB7XG4gIGNvbnN0IFtkYXRhLCBzZXREYXRhXSA9IHVzZVN0YXRlKG51bGwpO1xuXG4gIHVzZUVmZmVjdChhc3luYyAoKSA9PiB7XG4gICAgc2V0RGF0YShhd2FpdCBjc3YoJ2RhdGEuY3N2JywgcGFyc2VSb3cpKTtcbiAgfSwgW10pO1xuXG4gIHJldHVybiBkYXRhO1xufTtcbiIsImltcG9ydCB7IGF4aXNMZWZ0LCBheGlzQm90dG9tIH0gZnJvbSAnZDMnO1xuXG5leHBvcnQgY29uc3QgYXhlcyA9IChcbiAgc2VsZWN0aW9uLFxuICB7IGhlaWdodCwgbWFyZ2luLCB4U2NhbGUsIHlTY2FsZSB9XG4pID0+IHtcbiAgY29uc3QgeyBsZWZ0LCBib3R0b20gfSA9IG1hcmdpbjtcblxuICBzZWxlY3Rpb25cbiAgICAuc2VsZWN0QWxsKCdnLmF4aXMteScpXG4gICAgLmRhdGEoW251bGxdKVxuICAgIC5qb2luKCdnJylcbiAgICAuYXR0cignY2xhc3MnLCAnYXhpcyBheGlzLXknKVxuICAgIC5hdHRyKCd0cmFuc2Zvcm0nLCBgdHJhbnNsYXRlKCR7bGVmdH0sMClgKVxuICAgIC5jYWxsKGF4aXNMZWZ0KHlTY2FsZSkpO1xuXG4gIHNlbGVjdGlvblxuICAgIC5zZWxlY3RBbGwoJ2cuYXhpcy14JylcbiAgICAuZGF0YShbbnVsbF0pXG4gICAgLmpvaW4oJ2cnKVxuICAgIC5hdHRyKCdjbGFzcycsICdheGlzIGF4aXMteCcpXG4gICAgLmF0dHIoXG4gICAgICAndHJhbnNmb3JtJyxcbiAgICAgIGB0cmFuc2xhdGUoMCwke2hlaWdodCAtIGJvdHRvbX0pYFxuICAgIClcbiAgICAuY2FsbChheGlzQm90dG9tKHhTY2FsZSkpO1xufTtcbiIsImltcG9ydCB7IHNjYWxlTGluZWFyLCBleHRlbnQgfSBmcm9tICdkMyc7XG5pbXBvcnQgeyBheGVzIH0gZnJvbSAnLi9heGVzJztcblxuZXhwb3J0IGNvbnN0IHZpeiA9IChcbiAgc2VsZWN0aW9uLFxuICB7XG4gICAgZGF0YSxcbiAgICB4VmFsdWUsXG4gICAgeVZhbHVlLFxuICAgIHdpZHRoLFxuICAgIGhlaWdodCxcbiAgICBtYXJnaW4sXG4gICAgY2lyY2xlUmFkaXVzLFxuICB9XG4pID0+IHtcbiAgY29uc3QgeyB0b3AsIHJpZ2h0LCBib3R0b20sIGxlZnQgfSA9IG1hcmdpbjtcblxuICBjb25zdCB4U2NhbGUgPSBzY2FsZUxpbmVhcigpXG4gICAgLmRvbWFpbihleHRlbnQoZGF0YSwgeFZhbHVlKSlcbiAgICAucmFuZ2UoW2xlZnQsIHdpZHRoIC0gcmlnaHRdKTtcblxuICBjb25zdCB5U2NhbGUgPSBzY2FsZUxpbmVhcigpXG4gICAgLmRvbWFpbihleHRlbnQoZGF0YSwgeVZhbHVlKSlcbiAgICAucmFuZ2UoW2hlaWdodCAtIGJvdHRvbSwgdG9wXSk7XG5cbiAgc2VsZWN0aW9uXG4gICAgLnNlbGVjdEFsbCgnY2lyY2xlJylcbiAgICAuZGF0YShkYXRhKVxuICAgIC5qb2luKCdjaXJjbGUnKVxuICAgIC5hdHRyKCdyJywgY2lyY2xlUmFkaXVzKVxuICAgIC5hdHRyKCdmaWxsLW9wYWNpdHknLCAwLjUpXG4gICAgLmF0dHIoJ2N4JywgKGQpID0+IHhTY2FsZSh4VmFsdWUoZCkpKVxuICAgIC5hdHRyKCdjeScsIChkKSA9PiB5U2NhbGUoeVZhbHVlKGQpKSk7XG5cbiAgYXhlcyhzZWxlY3Rpb24sIHtcbiAgICBoZWlnaHQsXG4gICAgbWFyZ2luLFxuICAgIHhTY2FsZSxcbiAgICB5U2NhbGUsXG4gIH0pO1xufTtcbiIsImltcG9ydCB7IHVzZVJlZiwgdXNlRWZmZWN0IH0gZnJvbSAncmVhY3QnO1xuaW1wb3J0IHsgc2VsZWN0IH0gZnJvbSAnZDMnO1xuaW1wb3J0IHsgdml6IH0gZnJvbSAnLi92aXonO1xuXG5leHBvcnQgY29uc3QgVml6V3JhcHBlciA9ICh7IGRhdGEgfSkgPT4ge1xuICBjb25zdCByZWYgPSB1c2VSZWYoKTtcblxuICBjb25zdCB3aWR0aCA9IHdpbmRvdy5pbm5lcldpZHRoO1xuICBjb25zdCBoZWlnaHQgPSB3aW5kb3cuaW5uZXJIZWlnaHQ7XG5cbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICB2aXooc2VsZWN0KHJlZi5jdXJyZW50KSwge1xuICAgICAgZGF0YSxcbiAgICAgIHhWYWx1ZTogKGQpID0+IGQuc2VwYWxfbGVuZ3RoLFxuICAgICAgeVZhbHVlOiAoZCkgPT4gZC5wZXRhbF9sZW5ndGgsXG4gICAgICB3aWR0aCxcbiAgICAgIGhlaWdodCxcbiAgICAgIG1hcmdpbjoge1xuICAgICAgICB0b3A6IDIwLFxuICAgICAgICByaWdodDogMjAsXG4gICAgICAgIGJvdHRvbTogNDAsXG4gICAgICAgIGxlZnQ6IDQwLFxuICAgICAgfSxcbiAgICAgIGNpcmNsZVJhZGl1czogMTAsXG4gICAgfSk7XG4gIH0sIFtkYXRhXSk7XG5cbiAgcmV0dXJuIChcbiAgICA8c3ZnXG4gICAgICB3aWR0aD17d2lkdGh9XG4gICAgICBoZWlnaHQ9e2hlaWdodH1cbiAgICAgIHJlZj17cmVmfVxuICAgIC8+XG4gICk7XG59O1xuIiwiaW1wb3J0IHt1c2VEYXRhfSBmcm9tICcuL3VzZURhdGEnO1xuaW1wb3J0IHtWaXpXcmFwcGVyfSBmcm9tICcuL1ZpeldyYXBwZXInO1xuXG5leHBvcnQgY29uc3QgQXBwID0gKCkgPT4ge1xuICBjb25zdCBkYXRhID0gdXNlRGF0YSgpO1xuICByZXR1cm4gZGF0YSA/IChcbiAgICA8Vml6V3JhcHBlciBkYXRhPXtkYXRhfSAvPlxuICApIDogKFxuICAgIC8vIENvdWxkIGJlIGV4cGFuZGVkIHdpdGggYSByZWFsIGxvYWRpbmcgaW5kaWNhdG9yLlxuICAgICdMb2FkaW5nLi4uJ1xuICApO1xufTtcbiIsImltcG9ydCB7IEFwcCB9IGZyb20gJy4vQXBwJztcbmltcG9ydCBSZWFjdERPTSBmcm9tICdyZWFjdC1kb20nO1xuXG5SZWFjdERPTS5yZW5kZXIoXG4gIDxBcHAgb3V0cHV0UGF0aD1cIlwiIC8+LFxuICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgncm9vdCcpXG4pO1xuIl0sIm5hbWVzIjpbInVzZVN0YXRlIiwidXNlRWZmZWN0IiwiY3N2IiwiYXhpc0xlZnQiLCJheGlzQm90dG9tIiwic2NhbGVMaW5lYXIiLCJleHRlbnQiLCJ1c2VSZWYiLCJzZWxlY3QiXSwibWFwcGluZ3MiOiI7Ozs7O0VBR0EsTUFBTSxRQUFRLEdBQUcsQ0FBQyxDQUFDLEtBQUs7RUFDeEIsRUFBRSxDQUFDLENBQUMsWUFBWSxHQUFHLENBQUMsQ0FBQyxDQUFDLFlBQVksQ0FBQztFQUNuQyxFQUFFLENBQUMsQ0FBQyxXQUFXLEdBQUcsQ0FBQyxDQUFDLENBQUMsV0FBVyxDQUFDO0VBQ2pDLEVBQUUsQ0FBQyxDQUFDLFlBQVksR0FBRyxDQUFDLENBQUMsQ0FBQyxZQUFZLENBQUM7RUFDbkMsRUFBRSxDQUFDLENBQUMsV0FBVyxHQUFHLENBQUMsQ0FBQyxDQUFDLFdBQVcsQ0FBQztFQUNqQyxFQUFFLE9BQU8sQ0FBQyxDQUFDO0VBQ1gsQ0FBQyxDQUFDO0FBQ0Y7RUFDTyxNQUFNLE9BQU8sR0FBRyxDQUFDLFVBQVUsS0FBSztFQUN2QyxFQUFFLE1BQU0sQ0FBQyxJQUFJLEVBQUUsT0FBTyxD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

data.csv

sepal_length,sepal_width,petal_length,petal_width,species
5.1,3.5,1.4,0.2,setosa
4.9,3.0,1.4,0.2,setosa
4.7,3.2,1.3,0.2,setosa
4.6,3.1,1.5,0.2,setosa
5.0,3.6,1.4,0.2,setosa
5.4,3.9,1.7,0.4,setosa
4.6,3.4,1.4,0.3,setosa
5.0,3.4,1.5,0.2,setosa
4.4,2.9,1.4,0.2,setosa
4.9,3.1,1.5,0.1,setosa
5.4,3.7,1.5,0.2,setosa
4.8,3.4,1.6,0.2,setosa
4.8,3.0,1.4,0.1,setosa
4.3,3.0,1.1,0.1,setosa
5.8,4.0,1.2,0.2,setosa
5.7,4.4,1.5,0.4,setosa
5.4,3.9,1.3,0.4,setosa
5.1,3.5,1.4,0.3,setosa
5.7,3.8,1.7,0.3,setosa
5.1,3.8,1.5,0.3,setosa
5.4,3.4,1.7,0.2,setosa
5.1,3.7,1.5,0.4,setosa
4.6,3.6,1.0,0.2,setosa
5.1,3.3,1.7,0.5,setosa
4.8,3.4,1.9,0.2,setosa
5.0,3.0,1.6,0.2,setosa
5.0,3.4,1.6,0.4,setosa
5.2,3.5,1.5,0.2,setosa
5.2,3.4,1.4,0.2,setosa
4.7,3.2,1.6,0.2,setosa
4.8,3.1,1.6,0.2,setosa
5.4,3.4,1.5,0.4,setosa
5.2,4.1,1.5,0.1,setosa
5.5,4.2,1.4,0.2,setosa
4.9,3.1,1.5,0.1,setosa
5.0,3.2,1.2,0.2,setosa
5.5,3.5,1.3,0.2,setosa
4.9,3.1,1.5,0.1,setosa
4.4,3.0,1.3,0.2,setosa
5.1,3.4,1.5,0.2,setosa
5.0,3.5,1.3,0.3,setosa
4.5,2.3,1.3,0.3,setosa
4.4,3.2,1.3,0.2,setosa
5.0,3.5,1.6,0.6,setosa
5.1,3.8,1.9,0.4,setosa
4.8,3.0,1.4,0.3,setosa
5.1,3.8,1.6,0.2,setosa
4.6,3.2,1.4,0.2,setosa
5.3,3.7,1.5,0.2,setosa
5.0,3.3,1.4,0.2,setosa
7.0,3.2,4.7,1.4,versicolor
6.4,3.2,4.5,1.5,versicolor
6.9,3.1,4.9,1.5,versicolor
5.5,2.3,4.0,1.3,versicolor
6.5,2.8,4.6,1.5,versicolor
5.7,2.8,4.5,1.3,versicolor
6.3,3.3,4.7,1.6,versicolor
4.9,2.4,3.3,1.0,versicolor
6.6,2.9,4.6,1.3,versicolor
5.2,2.7,3.9,1.4,versicolor
5.0,2.0,3.5,1.0,versicolor
5.9,3.0,4.2,1.5,versicolor
6.0,2.2,4.0,1.0,versicolor
6.1,2.9,4.7,1.4,versicolor
5.6,2.9,3.6,1.3,versicolor
6.7,3.1,4.4,1.4,versicolor
5.6,3.0,4.5,1.5,versicolor
5.8,2.7,4.1,1.0,versicolor
6.2,2.2,4.5,1.5,versicolor
5.6,2.5,3.9,1.1,versicolor
5.9,3.2,4.8,1.8,versicolor
6.1,2.8,4.0,1.3,versicolor
6.3,2.5,4.9,1.5,versicolor
6.1,2.8,4.7,1.2,versicolor
6.4,2.9,4.3,1.3,versicolor
6.6,3.0,4.4,1.4,versicolor
6.8,2.8,4.8,1.4,versicolor
6.7,3.0,5.0,1.7,versicolor
6.0,2.9,4.5,1.5,versicolor
5.7,2.6,3.5,1.0,versicolor
5.5,2.4,3.8,1.1,versicolor
5.5,2.4,3.7,1.0,versicolor
5.8,2.7,3.9,1.2,versicolor
6.0,2.7,5.1,1.6,versicolor
5.4,3.0,4.5,1.5,versicolor
6.0,3.4,4.5,1.6,versicolor
6.7,3.1,4.7,1.5,versicolor
6.3,2.3,4.4,1.3,versicolor
5.6,3.0,4.1,1.3,versicolor
5.5,2.5,4.0,1.3,versicolor
5.5,2.6,4.4,1.2,versicolor
6.1,3.0,4.6,1.4,versicolor
5.8,2.6,4.0,1.2,versicolor
5.0,2.3,3.3,1.0,versicolor
5.6,2.7,4.2,1.3,versicolor
5.7,3.0,4.2,1.2,versicolor
5.7,2.9,4.2,1.3,versicolor
6.2,2.9,4.3,1.3,versicolor
5.1,2.5,3.0,1.1,versicolor
5.7,2.8,4.1,1.3,versicolor
6.3,3.3,6.0,2.5,virginica
5.8,2.7,5.1,1.9,virginica
7.1,3.0,5.9,2.1,virginica
6.3,2.9,5.6,1.8,virginica
6.5,3.0,5.8,2.2,virginica
7.6,3.0,6.6,2.1,virginica
4.9,2.5,4.5,1.7,virginica
7.3,2.9,6.3,1.8,virginica
6.7,2.5,5.8,1.8,virginica
7.2,3.6,6.1,2.5,virginica
6.5,3.2,5.1,2.0,virginica
6.4,2.7,5.3,1.9,virginica
6.8,3.0,5.5,2.1,virginica
5.7,2.5,5.0,2.0,virginica
5.8,2.8,5.1,2.4,virginica
6.4,3.2,5.3,2.3,virginica
6.5,3.0,5.5,1.8,virginica
7.7,3.8,6.7,2.2,virginica
7.7,2.6,6.9,2.3,virginica
6.0,2.2,5.0,1.5,virginica
6.9,3.2,5.7,2.3,virginica
5.6,2.8,4.9,2.0,virginica
7.7,2.8,6.7,2.0,virginica
6.3,2.7,4.9,1.8,virginica
6.7,3.3,5.7,2.1,virginica
7.2,3.2,6.0,1.8,virginica
6.2,2.8,4.8,1.8,virginica
6.1,3.0,4.9,1.8,virginica
6.4,2.8,5.6,2.1,virginica
7.2,3.0,5.8,1.6,virginica
7.4,2.8,6.1,1.9,virginica
7.9,3.8,6.4,2.0,virginica
6.4,2.8,5.6,2.2,virginica
6.3,2.8,5.1,1.5,virginica
6.1,2.6,5.6,1.4,virginica
7.7,3.0,6.1,2.3,virginica
6.3,3.4,5.6,2.4,virginica
6.4,3.1,5.5,1.8,virginica
6.0,3.0,4.8,1.8,virginica
6.9,3.1,5.4,2.1,virginica
6.7,3.1,5.6,2.4,virginica
6.9,3.1,5.1,2.3,virginica
5.8,2.7,5.1,1.9,virginica
6.8,3.2,5.9,2.3,virginica
6.7,3.3,5.7,2.5,virginica
6.7,3.0,5.2,2.3,virginica
6.3,2.5,5.0,1.9,virginica
6.5,3.0,5.2,2.0,virginica
6.2,3.4,5.4,2.3,virginica
5.9,3.0,5.1,1.8,virginica

package.json

{
  "scripts": {
    "build": "rollup -c"
  },
  "devDependencies": {
    "rollup": "latest",
    "@rollup/plugin-buble": "latest"
  }
}

rollup.config.js

const buble = require('@rollup/plugin-buble');
  
export default {
  input: 'index.js',
  external: ["d3","react","react-dom"],
  output: {
    file: 'bundle.js',
    format: 'iife',
    sourcemap: true,
    globals: {"d3":"d3","react":"React","react-dom":"ReactDOM"}
  },
  plugins: [buble()]
};

styles.css

body {
  margin: 0;
  overflow: hidden;
}

.message {
  font-size: 13em;
  text-align: center;
}

useData.js

import { useState, useEffect } from 'react';
import { csv } from 'd3';

const parseRow = (d) => {
  d.sepal_length = +d.sepal_length;
  d.sepal_width = +d.sepal_width;
  d.petal_length = +d.petal_length;
  d.petal_width = +d.petal_width;
  return d;
};

export const useData = (outputPath) => {
  const [data, setData] = useState(null);

  useEffect(async () => {
    setData(await csv('data.csv', parseRow));
  }, []);

  return data;
};

viz.js

import { scaleLinear, extent } from 'd3';
import { axes } from './axes';

export const viz = (
  selection,
  {
    data,
    xValue,
    yValue,
    width,
    height,
    margin,
    circleRadius,
  }
) => {
  const { top, right, bottom, left } = margin;

  const xScale = scaleLinear()
    .domain(extent(data, xValue))
    .range([left, width - right]);

  const yScale = scaleLinear()
    .domain(extent(data, yValue))
    .range([height - bottom, top]);

  selection
    .selectAll('circle')
    .data(data)
    .join('circle')
    .attr('r', circleRadius)
    .attr('fill-opacity', 0.5)
    .attr('cx', (d) => xScale(xValue(d)))
    .attr('cy', (d) => yScale(yValue(d)));

  axes(selection, {
    height,
    margin,
    xScale,
    yScale,
  });
};