React for Data Visualization [FULL COURSE] - Part 4

s12e57 - Render a Histogram of salaries

Render a Histogram of salaries

Knowing median salaries is great and all, but it doesn’t tell you much about what you can expect. You need to know the distribution to see if it’s more likely you’ll get 140k or 70k.

That’s what histograms are for. Give them a bunch of data, and they show its distribution. We’re going to build one like this:

Basic histogram

In the shortened dataset, 35% of tech salaries fall between $60k and $80k, 26% between $80k and $100k etc. Throwing a weighed dice with this random distribution, you’re far more likely to get 60k-80k than 120k-140k. It’s a great way to gauge situations.

It’s where statistics like “More people die from vending machines than shark attacks” come from. Which are you afraid of, vending machines or sharks? Stats say your answer should be heart disease. :wink:

s12e58 - Step 1: Prep App.js

Step 1: Prep App.js

You know the drill, don’t you? Import some stuff, add it to the render() method in the App component.

// src/App.js
import _ from 'lodash';

// Insert the line(s) between here...
import './style.css';
// ...and here.

import Preloader from './components/Preloader';
import { loadAllData } from './DataHandling';

import CountyMap from './components/CountyMap';
// Insert the line(s) between here...
import Histogram from './components/Histogram';
// ...and here.

We import style.css and the Histogram component. That’s what I love about Webpack - you can import CSS in JavaScript. We got the setup with create-react-app .

There are competing schools of thought about styling React apps. Some say each component should come with its own CSS files, some think it should be in large per-app CSS files, many think CSS-in-JS is the way to go.

Personally I like to use CSS for general cross-component styling and styled-components for more specific styles. We’re using CSS in this project because it works and means we don’t have to learn yet another dependency.

After the imports, we can render our Histogram in the App component.

// src/App.js
// ...
render() {
    // ...
    return (
        <div className="App container">
            <h1>Loaded {this.state.techSalaries.length} salaries</h1>
            <svg width="1100" height="500">
                <CountyMap usTopoJson={this.state.usTopoJson}
                           USstateNames={this.state.USstateNames}
                           values={countyValues}
                           x={0}
                           y={0}
                           width={500}
                           height={500}
                           zoom={zoom} />
                // Insert the line(s) between here...
                <Histogram bins={10}
                           width={500}
                           height={500}
                           x="500"
                           y="10"
                           data={filteredSalaries}
                           axisMargin={83}
                           bottomMargin={5}
                           value={d => d.base_salary} />
                // ...and here.
            </svg>
        </div>
    );
}

We render the Histogram component with a bunch of props. They specify the dimensions we want, positioning, and pass data to the component. We’re using filteredSalaries even though we haven’t set up any filtering yet. One less line of code to change later :ok_hand:

That’s it. App is ready to render our Histogram .

You should now see an error about missing files. That’s normal.

s12e59 - Step 2: CSS changes

Step 2: CSS changes

As mentioned, opinions vary on the best approach to styling React apps. Some say stylesheets per component, some say styling inside JavaScript, others swear by global app styling.

The truth is somewhere in between. Do what fits your project and your team. We’re using global stylesheets because it’s the simplest.

Create a new file src/style.css and add these 29 lines:

.histogram .bar rect {
    fill: steelblue;
    shape-rendering: crispEdges;
}

.histogram .bar text {
    fill: #fff;
    font: 12px sans-serif;
}

button {
    margin-right: .5em;
    margin-bottom: .3em !important;
}

.row {
    margin-top: 1em;
}

.mean text {
    font: 11px sans-serif;
    fill: grey;
}

.mean path {
    stroke-dasharray: 3;
    stroke: grey;
    stroke-width: 1px;
}

We won’t go into details about the CSS here. Many better books have been written about it.

In broad strokes:

  • we’re making .histogram rectangles – the bars – blue
  • labels white 12px font
  • button s and .row s have some spacing
  • the .mean line is a dotted grey with gray 11px text.

More CSS than we need for just the histogram, but we’re already here so might as well write it now.

Adding our CSS before building the Histogram means it’s going to look beautiful the first time around.

s12e60 - Step 3: Histogram component

Step 3: Histogram component

We’re following the full-feature integration approach for our Histogram component. React talks to the DOM, D3 calculates the props.

We’ll use two components:

  1. Histogram makes the general layout, dealing with D3, and translating raw data into a histogram
  2. HistogramBar draws a single bar and labels it

Let’s start with the basics: a Histogram directory and an index.js file. Keeps our code organized and imports easy. I like to use directories for components made of multiple files.

export { default } from "./Histogram";

Import and re-export the histogram componenet from ./Histogram . This way you can keep your histogram code in a file called Histogram and pretend the directory itself is exporting it.

Great way to group files that belong together without exposing your directory’s internal structure.

Now we need the Histogram.js file. Start with some imports, a default export, and a stubbed out Histogram class.

// src/components/Histogram/Histogram.js
import React from "react";
import * as d3 from "d3";

class Histogram extends React.Component {
    state = {
        histogram: d3.histogram(),
        widthScale: d3.scaleLinear(),
        yScale: d3.scaleLinear()
    };

    static getDerivedStateFromProps(props, state) {
        let { histogram, widthScale, yScale } = state;

        return {
            ...state,
            histogram,
            widthScale,
            yScale
        };
    }

    makeBar = bar => {
        const { yScale, widthScale } = this.state;

    };

    render() {
        const { histogram, yScale } = this.state,
            { x, y, data, axisMargin } = this.props;

        return null;
    }
}

We import React and D3, and set up Histogram .

Default state for our D3 objects: histogram, widthScale, and yScale. An empty getDerivedStateFromProps to keep them updated, makeBar to help us render each bar, and render returning null for now.

getDerivedStateFromProps

// src/components/Histogram/Histogram.js
    static getDerivedStateFromProps(props, state) {
        let { histogram, widthScale, yScale } = state;

        histogram.thresholds(props.bins).value(props.value);

        const bars = histogram(props.data),
            counts = bars.map(d => d.length);

        widthScale
            .domain([d3.min(counts), d3.max(counts)])
            .range([0, props.width - props.axisMargin]);

        yScale
            .domain([0, d3.max(bars, d => d.x1)])
            .range([props.height - props.y - props.bottomMargin, 0]);

        return {
            ...state,
            histogram,
            widthScale,
            yScale
        };
    }

First, we configure the histogram generator. thresholds specify how many bins we want and value specifies the value accessor function. We get both from props passed into the Histogram component.

In our case that makes 20 bins, and the value accessor returns each data point’s base_salary .

We feed the data prop into our histogram generator, and count how many values are in each bin with a .map call. We need those to configure our scales.

If you print the result of histogram() , you’ll see an array structure where each entry holds metadata about the bin and the values it contains.

console.log(this.histogram())

Let’s use this info to set up our scales.

widthScale has a range from the smallest ( d3.min ) bin to the largest ( d3.max ), and a range of 0 to width less a margin. We’ll use it to calculate bar sizes.

yScale has a range from 0 to the largest x1 coordinate we can find in a bin. Bins go from x0 to x1 , which reflects the fact that most histograms are horizontally oriented. Ours is vertical so that our labels are easier to read. The range goes from 0 to the maximum height less a margin.

Now let’s render this puppy.

render

// src/components/Histogram/Histogram.js
class Histogram extends React.Component {
    // ...
    render() {
        const { histogram, yScale } = this.state,
            { x, y, data, axisMargin } = this.props;

        const bars = histogram(data);

        return (
            <g className="histogram" transform={`translate(${x}, ${y})`}>
                <g className="bars">
                    {bars.map(this.makeBar))}
                </g>
            </g>
        );
    }
}

We take everything we need out of state and props with destructuring, call histogram() on our data to get a list of bars, and render.

Our render method returns a <g> grouping element transformed to the position given in props and walks through the bars array, calling makeBar for each. Later, we’re going to add an Axis as well.

This is a great example of React’s declarativeness. We have a bunch of stuff, and all it takes to render is a loop. No worrying about how it renders, where it goes, or anything like that. Walk through data, render, done.

makeBar

makeBar is a function that takes a histogram bar’s metadata and returns a HistogramBar component. We use it to make our declarative loop more readable.

// src/components/Histogram/Histogram.js
class Histogram extends React.Component {
    // ...
    makeBar = bar => {
        const { yScale, widthScale } = this.state;

        let percent = (bar.length / this.props.data.length) * 100;

        let props = {
            percent: percent,
            x: this.props.axisMargin,
            y: yScale(bar.x1),
            width: widthScale(bar.length),
            height: yScale(bar.x0) - yScale(bar.x1),
            key: "histogram-bar-" + bar.x0
        };

        return <HistogramBar {...props} />;
    };
}

See, we’re calculating props and feeding them into HistogramBar . Putting it in a separate function just makes the .map construct in render easier to read. There’s a lot of props to calculate.

Some, like axisMargin we pass through, others like width and height we calculate using our scales.

Setting the key prop is important. React uses it to tell the bars apart and only re-render those that change.

s12e61 - Step 4: HistogramBar (sub)component

Step 4: HistogramBar (sub)component

Before our histogram shows up, we need another component: HistogramBar . We could have shoved all of it in the makeBar function, but it makes sense to keep separate. Better future flexibility.

You can write small components like this in the same file as their main component. They’re not reusable since they fit a specific use-case, and they’re small enough so your files don’t get too crazy.

But in the interest of readability, let’s make a HistogramBar file.

// src/components/Histogram/HistogramBar.js
import React from "react";

const HistogramBar = ({ percent, x, y, width, height }) => {
    let translate = `translate(${x}, ${y})`,
        label = percent.toFixed(0) + "%";

    if (percent < 1) {
        label = percent.toFixed(2) + "%";
    }

    if (width < 20) {
        label = label.replace("%", "");
    }

    if (width < 10) {
        label = "";
    }

    return (
        <g transform={translate} className="bar">
            <rect
                width={width}
                height={height - 2}
                transform="translate(0, 1)"
            />
            <text textAnchor="end" x={width - 5} y={height / 2 + 3}>
                {label}
            </text>
        </g>
    );
};

export default HistogramBar;

Pretty long for a functional component. Most of it goes into deciding how much precision to render in the label, so it’s okay.

We start with an SVG translate and a default label . Then we update the label based on the bar size and its value.

When we have a label we like, we return a <g> grouping element with a rectangle and a text. Both positioned based on the width and height of the bar.

Make sure to import HistogramBar in the main Histogram file.

// src/components/Histogram/Histogram.js

import HistogramBar from './HistogramBar'

You should now see a histogram.

Histogram without axis

s12e62 - Step 5: Add an axis

Step 5: Axis HOC

Our histogram is pretty, but it needs an axis to be useful. You’ve already learned how to implement an axis when we talked about blackbox integration. We’re going to use the same approach and copy those concepts into the real project.

D3blackbox

We start with the D3blackbox higher order component. Same as before, except we put it in src/components .

import React from "react";

export default function D3blackbox(D3render) {
    return class Blackbox extends React.Component {
        anchorRef = React.createRef();

        componentDidMount() {
            D3render.call(this);
        }
        componentDidUpdate() {
            D3render.call(this);
        }

        render() {
            const { x, y } = this.props;
            return (
                <g transform={`translate(${x}, ${y})`} ref={this.anchorRef} />
            );
        }
    };
}

Take a D3render function, call it on componentDidMount and componentDidUpdate , and render a positioned anchor element for D3render to hook into.

Axis component

With D3blackbox , we can reduce the Axis component to a wrapped function. We’re implementing the D3render method.

import * as d3 from "d3";
import D3blackbox from "../D3blackbox";

const Axis = D3blackbox(function() {
    const axis = d3
        .axisLeft()
        .tickFormat(d => `${d3.format(".2s")(d)}`)
        .scale(this.props.scale)
        .ticks(this.props.data.length);

    d3.select(this.anchorRef.current).call(axis);
});

export default Axis;

We use D3’s axisLeft generator, configure its tickFormat , pass in a scale from our props, and specify how many ticks we want. To render, we select the anchor element from D3blackbox and call the axis generator on it.

Yes, this Axis works just for our specific use case and that’s okay. No need to generalize your code until you know where else you’re using it.

Remember the YAGNI principle.

Add Axis to Histogram

To render our new Axis , we add it to the Histogram component. It’s a two step process:

  1. Import Axis component
  2. Render it
// src/components/Histogram/Histogram.js
import React, { Component } from 'react';
import * as d3 from 'd3';

// Insert the line(s) between here...
import Axis from './Axis';
// ...and here.

// ...
class Histogram extends Component {
    // ...
    render() {
        const { histogram, yScale } = this.state,
            { x, y, data, axisMargin } = this.props;

        const bars = histogram(data);

        return (
            <g className="histogram" transform={translate}>
                <g className="bars">
                    {bars.map(this.makeBar)}
                </g>
                // Insert the line(s) between here...
                <Axis x={axisMargin-3}
                      y={0}
                      data={bars}
                      scale={yScale} />
                // ...and here.
            </g>
        );
    }

We import our Axis and add it to the render method with some props. It takes an x and y coordinate, the data , and a scale .

An axis appears.

Basic histogram with axis

If that didn’t work, try comparing your changes to this diff on Github.

s13e63 - Add meta info

Make it understandable - meta info

You’ve come so far! There’s a US map and a histogram. They’re blue and shiny and you look at them and you go “Huh?” .

The key to a good data visualization is telling users what it means. An easy way to do that is a good title and description. Just tell them. The picture supports the words, the words explain the picture.

Let’s add those words.

We’re adding a dynamic title and description, and a median line on the histogram. The text is dynamic because we’re adding user controls later, and we want the pictures and the words to stay in sync.

At the end of this section, you’ll have a full visualization of the shortened dataset.

Full visualization without user controls

s13e64 - Dynamic title

Dynamic title

We begin with the title because it shows up first.

We start with an import in App.js and add it to the render method. You know the drill :smile:

// src/App.js
import CountyMap from './components/CountyMap';
import Histogram from './components/Histogram';
// Insert the line(s) between here...
import { Title } from './components/Meta';
// ...and here.

class App extends Component {
    state = {
        techSalaries: [],
        countyNames: [],
        medianIncomes: [],
        // Insert the line(s) between here...
        filteredBy: {
            USstate: '*',
            year: '*',
            jobTitle: '*'
        }
        // ...and here.
    }

    // ...

    render() {
        const { filteredBy } = this.state;
        // ..
        return (
            <div className="App container">
                // Insert the line(s) between here...
                <Title data={filteredSalaries}
                       filteredBy={filteredBy} />
                // ...and here.
                // ...
            </div>
        )
    }
}

Ok, I lied. We did a lot more than just imports and adding to render.

We also set up the App component for future user-controlled data filtering. The filteredBy key in state tells us what the user is filtering by – 3 options: USstate , year , and jobTitle . We set them to “everything” by default.

We added them now so that we can immediately write our Title component in a filterable way. No need to make changes later.

As you can see, Title takes data and filteredBy props.

Prep Meta component

Before we begin the Title component, there are a few things to take care of. Our meta components work together for a common purpose – showing meta data. Grouping them in a directory makes sense.

We make a components/Meta directory and add an index.js . It makes importing easier.

// src/components/Meta/index.js
export { default as Title } from './Title'
export { default as Description } from './Description';

We have to name our Title and Description re-exports because you can’t have two default exports.

Get the USStatesMap file

You need the USStatesMap file.

It’s a big dictionary that translates US state codes to full names. You can get it from Github and save it as components/Meta/USStatesMap.js .

We’ll use it when creating titles and descriptions.

Implement Title

We’re building two types of titles based on user selection. If both years and US state were selected, we return In {US state}, the average {job title} paid ${mean}/year in {year} . If not, we return {job title} paid ${mean}/year in {state} in {year} .

I know, it’s confusing. They look like the same sentence turned around. Notice the and . First option when both are selected, second when either/or.

We start with imports, a stub, and a default export.

// src/components/Meta/Title.js
import React, { Component } from 'react';
import { scaleLinear } from 'd3-scale';
import { mean as d3mean, extent as d3extent } from 'd3-array';

import USStatesMap from './USStatesMap';

class Title extends Component {
    get yearsFragment() {
    }

    get USstateFragment() {
    }

    get jobTitleFragment() {
    }

    get format() {
    }

    render() {
    }
}

export default Title;

We import only what we need from D3’s d3-scale and d3-array packages. I consider this best practice until you’re importing so much that it gets messy to look at.

In the Title component, we have 4 getters and a render. Getters are ES6 functions that work like dynamic properties. You specify a function without arguments, and you use it without () . It’s neat.

The getters

  1. yearsFragment describes the selected year
  2. USstateFragment describes the selected US state
  3. jobTitleFragment describes the selected job title
  4. format returns a number formatter

We can implement yearsFragment , USstateFragment , and format in one code sample. They’re short.

// src/components/Meta/Title.js
class Title extends Component {
    get yearsFragment() {
        const year = this.props.filteredBy.year;

        return year === '*' ? "" : `in ${year}`;
    }

    getteFragment() {
        const USstate = this.props.filteredBy.USstate;

        return USstate === '*' ? "" : USStatesMap[USstate.toUpperCase()];
    }

    // ...

    get format() {
        return scaleLinear()
                 .domain(d3extent(this.props.data, d => d.base_salary))
                 .tickFormat();
    }

In both yearsFragment and USstateFragment , we get the appropriate value from Title’s filteredBy prop, then return a string with the value or an empty string.

We rely on D3’s built-in number formatters to build format . Linear scales have the one that turns 10000 into 10,000 . Tick formatters don’t work well without a domain , so we define it. We don’t need a range because we never use the scale itself.

format returns a function, which makes it a higher order function. Being a getter makes it really nice to use: this.format() . Looks just like a normal function call :smile:

The jobTitleFragment getter is conceptually no harder than yearsFragment and USstateFragment , but it comes with a few more conditionals.

// src/components/Meta/Title.js

class Title extends Component {
    // ...
    get jobTitleFragment() {
        const { jobTitle, year } = this.props.filteredBy;
        let title = "";

        if (jobTitle === "*") {
            if (year === "*") {
                title = "The average H1B in tech pays";
            } else {
                title = "The average tech H1B paid";
            }
        } else {
            title = `Software ${jobTitle}s on an H1B`;
            if (year === "*") {
                title += " make";
            } else {
                title += " made";
            }
        }

        return title;
    }
    // ...
}

We’re dealing with the (jobTitle, year) combination. Each influences the other when building the fragment for a total 4 different options.

The render

We put all this together in the render method. A conditional decides which of the two situations we’re in, and we return an <h2> tag with the right text.

// src/components/Meta/Title.js
class Title extends Component {
    // ...
    render() {
        const mean = this.format(d3mean(this.props.data, d => d.base_salary));

        let title;

        if (this.yearsFragment && this.USstateFragment) {
            title = (
                <h2>
                    In {this.USstateFragment}, {this.jobTitleFragment}
                    ${mean}/year {this.yearsFragment}
                </h2>
            );
        }else{
            title = (
                <h2>
                    {this.jobTitleFragment} ${mean}/year
                    {this.USstateFragment ? `in ${this.stateFragment}` : ''}
                    {this.yearsFragment}
                </h2>
            );
        }

        return title;
    }
}

Calculate the mean value using d3.mean with a value accessor, turn it into a pretty number with this.format , then use one of two string patterns to make a title .

And a title appears.

Dataviz with title

If it doesn’t, consult this diff on Github.

s13e65 - Dynamic description

Dynamic description

You know what? The dynamic description component is pretty much the same as the title. It’s just longer and more complex and uses more code. It’s interesting, but not super relevant to the topic of this book.

So rather than explain it all here, I’m going to give you a link to the diff on Github

We use the same approach as before:

  1. Add imports in App.js
  2. Add component to App render
  3. Add re-export to components/Meta/index.js
  4. Implement component in components/Meta/Description.js
  5. Use getters for sentence fragments
  6. Play with conditionals to construct different sentences

142 lines of mundane code.

All the interesting complexity goes into finding the richest city and county. That part looks like this:

// src/components/Meta/Description.js
get countyFragment() {
  const byCounty = _.groupBy(this.props.data, 'countyID'),
        medians = this.props.medianIncomesByCounty;

  let ordered = _.sortBy(
      _.keys(byCounty)
       .map(county => byCounty[county])
       .filter(d => d.length/this.props.data.length > 0.01),
      items => d3mean(items,
                      d => d.base_salary) - medians[items[0].countyID][0].medianIncome);

  let best = ordered[ordered.length-1],
      countyMedian = medians[best[0].countyID][0].medianIncome;

  // ...
}

We group the dataset by county, then sort counties by their income delta. We look only at counties that are bigger than 1% of the entire dataset. And we define income delta as the difference between a county’s median household income and the median tech salary in our dataset.

This code is not super efficient, but it gets the job done. We could optimize by just looking for the max value, for example.

Similar code handles finding the best city.

Render the description

I recommend copying the Description component from GitHub. Most of it has little to do with React and data visualization. It’s all about combining sentence fragments based on props.

You then render the Description like this:

// src/components/App.js

import { Title, Description } from "./components/Meta";

// ..

<Description
    data={filteredSalaries}
    allData={techSalaries}
    filteredBy={filteredBy}
    medianIncomesByCounty={this.state.medianIncomesByCounty}
/>

Dataviz with Title and Description

s13e66 - Overlay a median household line

Overlay a median household line

Here’s a more interesting component: the median dotted line. It shows a direct comparison between the histogram’s distribution and the median household income in an area. I’m not sure people understand it at a glance, but I think it’s cool.

We’re using a quick approach where everything fits into a functional React component. It’s great for small components like this.

Step 1: App.js

Inside src/App.js , we first have to add an import, then extract the median household value from state, and in the end, add MedianLine to the render method.

Let’s see if we can do it in a single code block :smile:

// src/App.js
import Histogram from './components/Histogram';
import { Title, Description, GraphDescription } from './components/Meta';
// Insert the line(s) between here...
import MedianLine from './components/MedianLine';
// ...and here.

class App extends Component {
    // ...
    render() {
        // ...
        let zoom = null,
            // Insert the line(s) between here...
            medianHousehold = this.state.medianIncomesByUSState['US'][0]
                                  .medianIncome;
            // ...and here.

        return (
            // ...
            <svg width="1100" height="500">
                <CountyMap // ... />
                <Histogram // ... />
                // Insert the line(s) between here...
                <MedianLine data={filteredSalaries}
                            x={500}
                            y={10}
                            width={600}
                            height={500}
                            bottomMargin={5}
                            median={medianHousehold}
                            value={d => d.base_salary} />
                // ...and here.
            </svg>
        )
    }
}

You probably don’t remember medianIncomesByUSState anymore. We set it up way back when tying datasets together. It groups our salary data by US state.

See, using good names helps :smile:

When rendering MedianLine , we give it sizing and positioning props, the dataset, a value accessor, and the median value to show. We could make it smart enough to calculate the median, but the added flexibility of a prop felt right.

Step 2: MedianLine

The MedianLine component looks similar to what you’ve seen so far. Some imports, a constructor that sets up D3 objects, an updateD3 method that keeps them in sync, and a render method that outputs SVG.

// src/components/MedianLine.js

import React from "react";
import * as d3 from "d3";

const MedianLine = ({
    data,
    value,
    width,
    height,
    x,
    y,
    bottomMargin,
    median
}) => {

};

export default MedianLine;

We have some imports, a functional MedianLine component that takes our props, and an export. It should cause an error because it’s not returning anything.

Everything we need to render the line, fits into this function.

// src/components/MedianLine.js

const MedianLine = ({
  // ...
}) => {
    const yScale = d3
            .scaleLinear()
            .domain([0, d3.max(data, value)])
            .range([height - y - bottomMargin, 0]),
        line = d3.line()([[0, 5], [width, 5]]);

    const medianValue = median || d3.median(data, value);

    const translate = `translate(${x}, ${yScale(medianValue)})`,
        medianLabel = `Median Household: ${yScale.tickFormat()(median)}`;

    return (
        <g className="mean" transform={translate}>
            <text
                x={width - 5}
                y="0"
                textAnchor="end"
                style={{ background: "purple" }}
            >
                {medianLabel}
            </text>
            <path d={line} />
        </g>
    );
};

We start with a scale for vertical positioning – yScale . It’s linear, takes values from 0 to max , and translates them to pixels less some margin. For the medianValue , we use props, or calculate our own, if needed. Just like I promised.

A translate SVG transform helps us position our line and label. We use it all to return a <g> grouping element containing a <text> for our label, and a <path> for the line.

Building the d attribute for the path, that’s interesting. We use a line generator from D3.

line = d3.line()([[0, 5], [width, 5]]);

It comes from the d3-shape package and generates splines, or polylines. By default, it takes an array of points and builds a line through all of them. A line from [0, 5] to [width, 5] in our case.

That makes it span the entire width and leaves 5px for the label. We’re using a transform on the entire group to vertically position the final element.

Remember, we already styled medianLine when we built histogram styles earlier.

.mean text {
    font: 11px sans-serif;
    fill: grey;
}

.mean path {
    stroke-dasharray: 3;
    stroke: grey;
    stroke-width: 1px;
}

The stroke-dasharray is what makes it dashed. 3 means each 3px dash is followed by a 3px blank. You can use any pattern you like.

You should see a median household salary line overlaid on your histogram.

Median line over histogram

Almost everyone in tech makes more than an entire median household. Crazy, huh? I think it is.

If that didn’t work, consult the diff on Github.

s14e67 - Add user controls for data slicing and dicing

Add user controls for data slicing and dicing

Now comes the fun part. All that extra effort we put into making our components aware of filtering, and it all comes down to this: User controls.

Here’s what we’re building:

User controlled filters

It’s a set of filters for users to slice and dice our visualization. The shortened dataset gives you 2 years, 12 job titles, and 50 US states. You’ll get 5+ years and many more job titles with the full dataset.

We’re using the architecture we discussed earlier to make it work. Clicking buttons updates a filter function and communicates it all the way up to the App component. App then uses it to update this.state.filteredSalaries , which triggers a re-render and updates our dataviz.

Architecture sketch

We’re building controls in 4 steps, top to bottom:

  1. Update App.js with filtering and a <Controls> render
  2. Build a Controls component, which builds the filter based on inputs
  3. Build a ControlRow component, which handles a row of buttons
  4. Build a Toggle component, which is a button

We’ll go through the files linearly. That makes them easier for me to explain and easier for you to understand, but that also means there’s going to be a long period where all you’re seeing is an error like this:

Controls error during coding

If you want to see what’s up during this process, remove an import or two and maybe a thing from render. For instance, it’s complaining about ControlRow in this screenshot. Remove the ControlRow import on top and delete <ControlRow ... /> from render. The error goes away, and you see what you’re doing.

s14e68 - Step 1: Update App.js

Step 1: Update App.js

All right, you know the drill. Add imports, tweak some things, add to render. We have to import Controls , set up filtering, update the map’s zoom prop, and render a white rectangle and Controls .

The white rectangle makes it so the zoomed-in map doesn’t cover up the histogram. I’ll explain when we get there.

// src/App.js
import MedianLine from './components/MedianLine';

// Insert the line(s) between here...
import Controls from './components/Controls';
// ...and here.

class App extends React.Component {
    state = {
        // ...
        medianIncomes: [],
        // Insert the line(s) between here...
        salariesFilter: () => true,
        // ...and here.
        filteredBy: {
            // ...
        }
    }

    // ...

    // Insert the line(s) between here...
    updateDataFilter = (filter, filteredBy) => {
        this.setState({
            salariesFilter: filter,
            filteredBy: filteredBy
        });
    }
    // ...and here.

    render() {
        // ...
    }
}

We import the Controls component and add a default salariesFilter function to this.state . The updateDataFilter method passes the filter function and filteredBy dictionary from arguments to App state. We’ll use it as a callback in Controls .

The rest of filtering setup happens in the render method.

// src/App.js
class App extends React.Component {
    // ...

    render() {
        // ...
        // Delete the line(s) between here...
        const filteredSalaries = techSalaries
        // ...and here.
        // Insert the line(s) between here...
        const filteredSalaries = techSalaries
                                     .filter(this.state.salariesFilter)
        // ...and here.

        // ...

        let zoom = null,
            medianHousehold = // ...
        // Insert the line(s) between here...
        if (filteredBy.USstate !== '*') {
            zoom = this.state.filteredBy.USstate;
            medianHousehold = d3.mean(medianIncomesByUSState[zoom],
                                      d => d.medianIncome);
        }
        // ...and here.

        // ...
    }
}

We add a .filter call to filteredSalaries , which uses our salariesFilter method to throw out anything that doesn’t fit. Then we set up zoom , if a US state was selected.

We built the CountyMap component to focus on a given US state. Finding the centroid of a polygon, re-centering the map, and increasing the sizing factor. It creates a nice zoom effect.

Zoom effect

And here’s the downside of this approach. SVG doesn’t know about element boundaries. It just renders stuff.

Zoom without white rectangle

See, it goes under the histogram. Let’s fix that and add the Controls render while we’re at it.

// src/App.js
class App extends React.Component {
    // ...

    render() {
        // ...

        return (
            <div //...>
                <svg //...>
                    <CountyMap //... />

                    // Insert the line(s) between here...
                    <rect x="500" y="0"
                          width="600"
                          height="500"
                          style={{fill: 'white'}} />
                    // ...and here.

                    <Histogram //... />
                    <MedianLine //.. />
                </svg>

                // Insert the line(s) between here...
                <Controls data={techSalaries}
                          updateDataFilter={this.updateDataFilter} />
                // ...and here.
            </div>
        )
    }
}

Rectangle, 500 to the right, 0 from top, 600 wide and 500 tall, with a white background. Gives the histogram an opaque background, so it doesn’t matter what the map is doing.

We render the Controls component just after </svg> because it’s not an SVG component – it uses normal HTML. Unlike other components, it needs our entire dataset as data . We use the updateDataFilter prop to say which callback function it should call when a new filter is ready.

If this seems roundabout … I’ve seen worse. The callbacks approach makes our app easier to componentize and keeps the code relatively unmessy. Imagine putting everything we’ve done so far in App ! :laughing:

s14e69 - Step 2: Build Controls component

Step 2: Build Controls component

The Controls component builds our filter function and filteredBy dictionary based on user choices.

Controls renders 3 rows of buttons and builds filtering out of the choice made on each row. That makes Controls kind of repetitive, but that’s okay.

To keep this book shorter, we’re going to build everything for a year filter first. Then I’ll explain how to add USstate and jobTitle filters on a higher level. Once you have one working, the rest follows that same pattern.

Make a Controls directory in src/components/ and let’s begin. The main Controls component goes in your index.js file.

Stub Controls

// src/components/Controls/index.js
import React from "react";

import ControlRow from "./ControlRow";

class Controls extends React.Component {
    state = {
        yearFilter: () => true,
        year: "*"
    };

    componentDidMount() {

    }

    updateYearFilter = (year, reset) => {
        let filter = d => d.submit_date.getFullYear() === year;

        if (reset || !year) {
            filter = () => true;
            year = "*";
        }

        this.setState(
            {
                yearFilter: filter,
                year: year
            },
            () => this.reportUpdateUpTheChain()
        );
    };

    render() {
        const { data } = this.props;
    }
}

export default Controls;

We start with some imports and a Controls class-based component. Inside, we define default state with an always-true yearFilter and an asterisk for year .

We also need an updateYearFilter function, which we’ll use to update the filter, a reportUpdateUpTheChain function, and a render method. We’re using reportUpdateUpTheChain to bubble updates to our parent component. It’s a simpler alternative to using React Context or a state management library.

Filter logic

// src/components/Controls/index.js
class Controls extends React.Component {
    // ...
    updateYearFilter = (year, reset) => {
        let filter = d => d.submit_date.getFullYear() === year;

        if (reset || !year) {
            filter = () => true;
            year = "*";
        }

        this.setState(
            {
                yearFilter: filter,
                year: year
            },
            () => this.reportUpdateUpTheChain()
        );
    };
}

updateYearFilter is a callback we pass into ControlRow . When a user picks a year, their action triggers this function.

When that happens, we create a new partial filter function. The App component uses it inside a .filter call like you saw earlier. We have to return true for elements we want to keep and false for elements we don’t.

Comparing submit_date.getFullYear() with year achieves that.

The reset argument lets us reset filters back to defaults. Enables users to unselect options.

When we have the year and filter , we update component state with this.setState . This triggers a re-render and calls reportUpdateUpTheChain afterwards. Great use-case for the little known setState callback :smiley:

reportUpdateUpTheChain then calls this.props.updateDataFilter , which is a callback method on App . We defined it earlier – it needs a new filter method and a filteredBy dictionary.

// src/components/Controls/index.js
class Controls extends React.Component {
// ...
reportUpdateUpTheChain() {
    window.location.hash = [
        this.state.year || "*"
    ].join("-"😉;

    this.props.updateDataFilter(
        (filters => {
            return d =>
                filters.yearFilter(d)
        })(this.state),
        {
            year: this.state.year
        }
    );
}
}

Building the filter method looks tricky because we’re composing multiple functions. The new arrow function takes a dictionary of filters as an argument and returns a new function that && s them all. We invoke it immediately with this.state as the argument.

It looks silly when there’s just one filter, but I promise it makes sense.

Render

Great, we have the filter logic. Let’s render those rows of controls we’ve been talking about.

// src/components/Controls/index.js
class Controls extends React.Component {
    // ...
    render() {
        const { data } = this.props;

        const years = new Set(data.map(d => d.submit_date.getFullYear()));

        return (
            <div>
                <ControlRow
                    data={data}
                    toggleNames={Array.from(years.values())}
                    picked={this.state.year}
                    updateDataFilter={this.updateYearFilter}
                />
            </div>
        );
    }
}

Once more, this is generalized code used for a single example: the year filter.

We build a Set of distinct years in our dataset, then render a ControlRow using props to give it our data , a set of toggleNames , a callback to update the filter, and which entry is picked right now. Also known as the controlled component pattern, it enables us to maintain the data-flows-down, events-bubble-up architecture.

If you don’t know about Set s, they’re an ES6 data structure that ensures every entry is unique. Just like a mathematical set. They’re pretty fast.

s14e70 - Step 3: Build ControlRow component

Step 3: Build ControlRow component

Let’s build the ControlRow component. It renders a row of controls and ensures only one at a time is selected.

We’ll start with a stub and go from there.

// src/components/Controls/ControlRow.js
import React from "react";

import Toggle from "./Toggle";

class ControlRow extends React.Component {
    makePick = (picked, newState) => {

    };

    _addToggle(name) {
    }

    render() {
        const { toggleNames } = this.props;
    }
}

export default ControlRow;

We start with imports, big surprise, then make a stub with 3 methods. Can you guess what they are?

  • makePick is the Toggle click callback
  • _addToggle is a rendering helper method
  • render renders a row of buttons
// src/components/Controls/ControlRow.js

class ControlRow extends React.Component {
    makePick = (picked, newState) => {
        // if newState is false, we want to reset
        this.props.updateDataFilter(picked, !newState);
    };

makePick calls the data filter update and passes in the new value and whether we want to unselect. Pretty simple right?

// src/components/Controls/ControlRow.js
class ControlRow extends React.Component {
    // ...

    _addToggle(name) {
        let key = `toggle-${name}`,
            label = name;

        if (this.props.capitalize) {
            label = label.toUpperCase();
        }

        return (
            <Toggle
                label={label}
                name={name}
                key={key}
                value={this.props.picked === name}
                onClick={this.makePick}
            />
        );
    }

    render() {
        const { toggleNames } = this.props;

        return (
            <div className="row">
                <div className="col-md-12">
                    {toggleNames.map(name => this._addToggle(name))}
                </div>
            </div>
        );
    }
}

Rendering comes in two functions: _addToggle , which is a helper, and render , which is the main render.

In render , we set up two div s with Bootstrap classes. The first is a row , and the second is a full-width column. I tried using a column for each button, but it was annoying to manage and used too much space.

Inside the divs, we map over all toggles and use _addToggle to render each of them.

_addToggle renders a Toggle component with a label , name , value and onClick callback. The label is just a prettier version of the name, which also serves as a key in our toggleValues dictionary. It’s going to be the picked attribute in makePick .

Your browser should continue showing an error, but it should change to talking about the Toggle component instead of ControlRow .

Let’s build it.

s14e71 - Step 4: Build Toggle component

Step 4: Build Toggle component

Last piece of the puzzle – the Toggle component. A button that turns on and off.

// src/components/Controls/Toggle.js
import React from "react";

const Toggle = ({ label, name, value, onClick }) => {
    let className = "btn btn-default";

    if (value) {
        className += " btn-primary";
    }

    return (
        <button className={className} onClick={() => onClick(name, !value)}>
            {label}
        </button>
    );
};

export default Toggle;

Import React to enable JSX and make a functional Toggle component. It’s fully controlled and takes event handlers as callbacks in props. No need for a class.

We set up a Bootstrap classname: btn and btn-default make an element look like a button, the conditional btn-primary makes it blue. If you’re not familiar with Bootstrap classes, you should check their documentation.

Then we render a <button> tag and, well, that’s it. A row of year filters appears in the browser. onClick passes

A row of year filters

Click on a button and the onClick handler passes a toggle’d value to its parent via the onClick callback. This triggers an update in Controls , which calls reportUpdateUpTheChain , which in turn updates state in App and re-renders our button with the new value toggling its color on or off.

If that didn’t work, consult this diff on GitHub.

s14e72 - Step 5: Add US state and Job Title filters

Step 5: Add US state and Job Title filters

With all that done, we can add two more filters: US states and job titles. Our App component is already set up to use them, so we just have to add them to the Controls component.

We’ll start with the render method, then handle the parts I said earlier would look repetitive.

// src/components/Controls/index.js
class Controls extends Component {
    // ...
    render() {
        const { data } = this.props;

        const years = new Set(data.map(d => d.submit_date.getFullYear())),
              // Insert the line(s) between here...
              jobTitles = new Set(data.map(d => d.clean_job_title)),
              USstates = new Set(data.map(d => d.USstate));
              // ...and here.

        return (
            <div>
                <ControlRow data={data}
                            toggleNames={Array.from(years.values())}
                            picked={this.state.year}
                            updateDataFilter={this.updateYearFilter}
                            />

            // Insert the line(s) between here...
                <ControlRow data={data}
                            toggleNames={Array.from(jobTitles.values())}
                            picked={this.state.jobTitle}
                            updateDataFilter={this.updateJobTitleFilter} />

                <ControlRow data={data}
                            toggleNames={Array.from(USstates.values())}
                            picked={this.state.USstate}
                            updateDataFilter={this.updateUSstateFilter}
                            capitalize="true" />
                // ...and here.
            </div>
        )
    }
}

Ok, this part is plenty repetitive, too.

We created new sets for jobTitles and USstates , then rendered two more ControlRow elements with appropriate attributes. They get toggleNames for building the buttons, picked to know which is active, an updateDataFilter callback, and we tell US states to render capitalized.

The implementations of those updateDataFilter callbacks follow the same pattern as updateYearFilter .

// src/components/Controls/index.js

class Controls extends React.Component {
    state = {
        yearFilter: () => true,
        jobTitleFilter: () => true,
        USstateFilter: () => true,
        year: "*",
        USstate: "*",
        jobTitle: "*"
    };

    updateJobTitleFilter = (title, reset) => {
        let filter = d => d.clean_job_title === title;

        if (reset || !title) {
            filter = () => true;
            title = "*";
        }

        this.setState(
            {
                jobTitleFilter: filter,
                jobTitle: title
            },
            () => this.reportUpdateUpTheChain()
        );
    };

    updateUSstateFilter = (USstate, reset) => {
        let filter = d => d.USstate === USstate;

        if (reset || !USstate) {
            filter = () => true;
            USstate = "*";
        }

        this.setState(
            {
                USstateFilter: filter,
                USstate: USstate
            },
            () => this.reportUpdateUpTheChain()
        );
    };

    // ..
}

export default Controls;

Yes, they’re basically the same as updateYearFilter . The only difference is a changed filter function and using different keys in setState() .

Why separate functions then? No need to get fancy. It would’ve made the code harder to read.

Our last step is to add these new keys to the reportUpdateUpTheChain function.

// src/components/Controls/index.js

class Controls extends React.Component {
    reportUpdateUpTheChain() {
        this.props.updateDataFilter(
            (filters => {
                return d =>
                    filters.yearFilter(d) &&
                    filters.jobTitleFilter(d) &&
                    filters.USstateFilter(d);
            })(this.state),
            {
                USstate: this.state.USstate,
                year: this.state.year,
                jobTitle: this.state.jobTitle
            }
        );
    }

We add them to the filter condition with && and expand the filteredBy argument.

Two more rows of filters show up.

All the filters

:clap:

Again, if it didn’t work, consult the diff on GitHub.

s15e73 - Add rudimentary routing

Rudimentary routing

Imagine this. A user finds your dataviz, clicks around, and finds an interesting insight. They share it with their friends.

“See! I was right! This link proves it.”

“Wtf are you talking about?”

“Oh… uuuuh… you have to click this and then that and then you’ll see. I’m legit winning our argument.”

“Wow! Kim Kardashian just posted a new snap with her dog.”

Let’s help our intrepid user out and make our dataviz linkable. We should store the current filteredBy state in the URL and be able to restore from a link.

There are many ways to achieve this. ReactRouter comes to mind, but the quickest is to implement our own rudimentary routing. We’ll add some logic to manipulate and read the URL hash.

The easiest place to put this logic is next to the existing filter logic inside the Controls component. Better places exist from a “low-down components shouldn’t play with global state” perspective, but that’s okay.

// src/components/Controls/index.js

class Controls extends React.Component {
    // ..

    componentDidMount() {
        let [year, USstate, jobTitle] = window.location.hash
            .replace("#", "")
            .split("-");

        if (year !== "*" && year) {
            this.updateYearFilter(Number(year));
        }
        if (USstate !== "*" && USstate) {
            this.updateUSstateFilter(USstate);
        }
        if (jobTitle !== "*" && jobTitle) {
            this.updateJobTitleFilter(jobTitle);
        }
    }

    // ..
    reportUpdateUpTheChain() {
        window.location.hash = [
            this.state.year || "*",
            this.state.USstate || "*",
            this.state.jobTitle || "*"
        ].join("-");

        // ..
    }

We use the componentDidMount lifecycle hook to read the URL when Controls first render on our page. Presumably when the page loads, but it could be later. It doesn’t really matter when , just that we update our filter the first chance we get.

window.location.hash gives us the hash part of the URL. We clean it up and split it into three parts: year , USstate , and jobTitle . If the URL is localhost:3000/#2013-CA-manager , then year becomes 2013 , USstate becomes CA , and jobTitle becomes manager .

We make sure each value is valid and use our existing filter update callbacks to update the visualization. Just as if it was the user clicking a button.

In reportUpdateUpTheChain , we make sure to update the URL hash. Assigning a new value to window.location.hash takes care of that.

You should see the URL changing as you click around.

Changing URL hash

There’s a bug with some combinations in 2013 that don’t have enough data. It will go away when we use the full dataset.

If it doesn’t work at all, consult the diff on Github.

s15e74 - Prep your app for launch

Prep for launch

You’ve built a great visualization. Congratz! Time to put it online and share with the world.

To do that, we’re going to use Github Pages because our app is a glorified static website. There’s no backend, so all we need is something to serve our HTML, JavaScript, and CSV. Github Pages is perfect for that.

It’s free, works well with create-react-app , and can withstand a lot of traffic. You don’t want to worry about traffic when your app gets to the top of HackerNews or Reddit.

There are a few things we should take care of:

  • setting up deployment
  • adding a page title
  • adding some copy
  • Twitter and Facebook cards
  • an SEO tweak for search engines
  • use the full dataset

Setting up deployment

You’ll need a Github repository. If you’re like me, writing all this code without version control or off-site backup made you nervous, so you already have one.

For everyone else, head over to Github, click the green New Repository button and give it a name. Then copy the commands it gives you and run them in your console.

It should be something like this:

$ git init
$ git commit -m "My entire dataviz"
$ git remote add origin git://github ...
$ git push origin -u master

If you’ve been git-ing locally without pushing, then you only need the git remote add and git push origin commands. This puts your code on Github. Great idea for anything you don’t want to lose if your computer craps out.

Every Github repository comes with an optional Github Pages setup. The easiest way for us to use it is with the gh-pages npm module.

Install it with this command:

$ npm install --save-dev gh-pages

Add two lines to package.json:

// package.json
// Insert the line(s) between here...
"homepage": "https://<your username>.github.io/<your repo name>"
// ...and here.
"scripts": {
  "eject": "react-scripts eject",
  // Insert the line(s) between here...
  "deploy": "npm run build && gh-pages -d build"
  // ...and here.
}

We’ve been ignoring the package.json file so far, but it’s a pretty important file. It specifies all of our project’s dependencies, meta data for npm, and scripts that we run locally. This is where npm start is defined, for instance.

We add a deploy script that runs build and a gh-pages deploy, and we specify a homepage URL. The URL tells our build script how to set up URLs for static files in index.html .

Github Pages URLs follow the schema:

https://<your username>.github.io/<your repo name>

For instance, mine is:

https://swizec.github.io/react-d3-walkthrough-livecode

Yours will be different.

You can deploy with npm run deploy . Make sure all changes are committed. We’ll do it together when we’re done setting up.

s15e75 - Twitter and Facebook cards and SEO

Twitter and Facebook cards and SEO

How your visualization looks on social media matters more than you’d think. Does it have a nice picture, a great description, and a title, or does it look like a random URL? Those things matter.

And they’re easy to set up. No excuse.

We’re going to poke around public/index.html for the first time. Add titles, Twitter cards, Facebook Open Graph things, and so on.

<!-- public/index.html -->
<head>
    <!-- //... -->
    // Insert the line(s) between here...
    <title>How much does an H1B in tech pay?</title>

    <link rel="canonical"
          href="https://swizec.github.io/react-d3-walkthrough-livecode/" />
    // ...and here.
</head>
<body>
    <!-- //... -->
    <div id="root">
        // Insert the line(s) between here...
         <h2>The average H1B in tech pays $86,164/year</h2>

   <😜 class="lead">
             Since 2012 the US tech industry has sponsored 176,075
             H1B work visas. Most of them paid <b>$60,660 to $111,668</b>
             per year (1 standard deviation). <span>The best city for
             an H1B is <b>Kirkland, WA</b> with an average individual
             salary <b>$39,465 above local household median</b>.
             Median household salary is a good proxy for cost of
             living in an area.</span>
         </p>
        // ...and here.
    </div>
</body>

We add a <title> and a canonical URL. Titles configure what shows up in browser tabs, and the canonical URL is there to tell search engines that this is the main and most important URL for this piece of content. This is especially important for when people copy-paste your stuff and put it on other websites.

In the body root tag, we add some copy-pasted text from our dataviz. You’ll recognize the default title and description.

As soon as React loads, these get overwritten with our preloader, but it’s good to have them here for any search engines that aren’t running JavaScript yet. I think both Google and Bing are capable of running our React app and getting text from there, but you never know.

To make social media embeds look great, we’ll use Twitter card and Facebook OpenGraph meta tags. I think most other websites just rely on these since most people use them. They go in the <head> of our HTML.

<!-- public/index.html -->
<head>
    <meta property="og:locale" content="en_US" />
    <meta property="og:type" content="article" />
    <meta property="og:title"
          content="The average H1B in tech pays $86,164/year" />
    <meta property="og:description"
          content="Since 2012 the US tech industry has sponsored
176,075 H1B work visas. With an average individual salary
up to $39,465 above median household income." />
    <meta property="og:url"
          content="https://swizec.github.io/react-d3-walkthrough-livecode" />
    <meta property="og:site_name" content="A geek with a hat" />
    <meta property="article:publisher"
          content="https://facebook.com/swizecpage" />
    <meta property="fb:admins" content="624156314" />
    <meta property="og:image"
          content="https://swizec.github.io/react-d3js-step-by-step/thumbnail.png" />

    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:description"
          content="Since 2012 the US tech industry has sponsored
176,075 H1B work visas. With an average individual salary
up to $39,465 above median household income." />
    <meta name="twitter:title"
          content="The average H1B in tech pays $86,164/year" />
    <meta name="twitter:site" content="@swizec" />
    <meta name="twitter:image"
          content="https://swizec.github.io/react-d3js-step-by-step/thumbnail.png" />
    <meta name="twitter:creator" content="@swizec" />
</head>

Much of this code is repetitive. Both Twitter and Facebook want the same info, but they’re stubborn and won’t read each other’s formats. You can copy all of this, but make sure to change og:url , og:site_name , article:publisher , fb:admins , og:image , twitter:site , twitter:image , and twitter:creator . They’re specific to you.

The URLs you should change to the homepage URL you used above. The rest you should change to use your name and Twitter/Facebook handles. I’m not sure why it matters, but I’m sure it does.

An important one is fb:admin . It enables admin features on your site if you add their social plugins. If you don’t, it probably doesn’t matter.

You’re also going to need a thumbnail image. I made mine by taking a screenshot of the final visualization, then I put it in public/thumbnail.png .

Now when somebody shares your dataviz on Twitter or Facebook, it’s going to look something like this:

Dataviz Twitter card

Dataviz Twitter card

s15e76 - Use full dataset

Full dataset

One more step left. Use the whole dataset!

Go into src/DataHandling.js and change one line:

// Example 4
//
// src/DataHandling.js
export const loadAllData = (callback = _.noop) => {
    d3.queue()
        // ..
        // Delete the line(s) between here...
        .defer(d3.csv, 'data/h1bs-2012-2016-shortened.csv', cleanSalary)
        // ...and here.
        // Insert the line(s) between here...
        .defer(d3.csv, 'data/h1bs-2012-2016.csv', cleanSalary)
        // ...and here.

We change the file name, and that’s that. Full dataset locked and loaded. Dataviz ready to go.

s15e77 - Launch!

Launch!

To show your dataviz to the world, make sure you’ve committed all changes. Using git status shows you anything you missed.

Then run:

$ npm run deploy

You’ll see a bunch of output:

Deploy output

And you’re ready to go. Your visualization is online. My URL is https://swizec.github.io/react-d3-walkthrough-livecode/ , yours is different. Visit it and you’ll see what you’ve built. Share it and others will see it too.

Deployed dataviz

Congratz! You’ve built and deployed your first React and D3 dataviz. You’re amazing \o/

Thanks for following along with the meaty part of my book. You’re now well equipped to build cool things.

In the next section, we’re going to look at building animations.