`), then iterate through the data stored in `topData`, returning a table row for each element in the array. In the table row, you should have one cell (``) displaying the county, and the other cell displaying the value of the variable of interest for that county. To make it look nice, you can use the `.toFixed()` method to fix the number of decimals shown in the number.
48 |
--------------------------------------------------------------------------------
/06-state-exercise/css/styles.css:
--------------------------------------------------------------------------------
1 | /* @import url("https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"); */
2 | @import url("../../lib/bootstrap.min.css");
3 |
4 | /* Header */
5 | .container {
6 | text-align: center;
7 | }
8 |
9 | .jumbotron {
10 | padding:1rem 1rem;
11 | margin-bottom:20px;
12 | }
13 |
14 | .lead {
15 | margin-bottom:0px;
16 | }
17 |
18 | /* Controls */
19 | .custom-select {
20 | width:inherit;
21 | }
22 |
23 | .control-wrapper {
24 | display: inline-block;
25 | margin-left:10px;
26 | }
27 |
28 | .control-wrapper input[type="range"] {
29 | vertical-align: text-bottom;;
30 | }
31 |
32 | .control-container {
33 | border-bottom:1px solid #d3d3d3;
34 | margin-bottom:10px;
35 | padding-bottom:10px;
36 | }
37 |
38 | /* Buttons */
39 | .btn:focus {
40 | box-shadow: none !important;
41 | }
42 |
43 | .btn-group {
44 | margin-left:10px;
45 | }
--------------------------------------------------------------------------------
/06-state-exercise/data/.Rhistory:
--------------------------------------------------------------------------------
1 | library(dplyr)
2 | library(ggplot2)
3 | View(midwest)
4 | dim(midwest)
5 | ?midwest
6 | levels(midwest$state)
7 | unique(midwest$state)
8 | styler:::style_active_file()
9 | prepped <- midwest %>%
10 | mutate(
11 | state = replace(state, state == "IL", "Illinois"),
12 | state = replace(state, state == "IN", "Indiana"),
13 | state = replace(state, state == "MI", "Michigan"),
14 | state = replace(state, state == "OH", "Ohio"),
15 | state = replace(state, state == "WI", "Wisconsin")
16 | )
17 | View(prepped)
18 | library(tools)
19 | prepped <- midwest %>%
20 | mutate(
21 | state = replace(state, state == "IL", "Illinois"),
22 | state = replace(state, state == "IN", "Indiana"),
23 | state = replace(state, state == "MI", "Michigan"),
24 | state = replace(state, state == "OH", "Ohio"),
25 | state = replace(state, state == "WI", "Wisconsin"),
26 | county = toTitleCase(county)
27 | ) %>%
28 | select(county, state, %in% "pop")
29 | prepped <- midwest %>%
30 | mutate(
31 | state = replace(state, state == "IL", "Illinois"),
32 | state = replace(state, state == "IN", "Indiana"),
33 | state = replace(state, state == "MI", "Michigan"),
34 | state = replace(state, state == "OH", "Ohio"),
35 | state = replace(state, state == "WI", "Wisconsin"),
36 | county = toTitleCase(county)
37 | ) %>%
38 | select(county, state, inmetro, contains("per"))
39 | View(prepped)
40 | ?perchsd
41 | ?midwest
42 | prepped <- midwest %>%
43 | mutate(
44 | state = replace(state, state == "IL", "Illinois"),
45 | state = replace(state, state == "IN", "Indiana"),
46 | state = replace(state, state == "MI", "Michigan"),
47 | state = replace(state, state == "OH", "Ohio"),
48 | state = replace(state, state == "WI", "Wisconsin"),
49 | county = toTitleCase(county)
50 | ) %>%
51 | select(county, state, inmetro, contains("per"), -perchsd)
52 | midwest %>%
53 | mutate(
54 | state = replace(state, state == "IL", "Illinois"),
55 | state = replace(state, state == "IN", "Indiana"),
56 | state = replace(state, state == "MI", "Michigan"),
57 | state = replace(state, state == "OH", "Ohio"),
58 | state = replace(state, state == "WI", "Wisconsin"),
59 | county = toTitleCase(county)
60 | )
61 | toTitleCase(midwest$county)
62 | library(stringr)
63 | prepped <- midwest %>%
64 | mutate(
65 | state = replace(state, state == "IL", "Illinois"),
66 | state = replace(state, state == "IN", "Indiana"),
67 | state = replace(state, state == "MI", "Michigan"),
68 | state = replace(state, state == "OH", "Ohio"),
69 | state = replace(state, state == "WI", "Wisconsin"),
70 | county = str_to_title(county)
71 | ) %>%
72 | select(county, state, inmetro, contains("per"), -perchsd)
73 | setwd("~/Documents/react-d3/demo/data")
74 | # Data prep
75 | # Use built in `midwest` data from the `ggplot2` package
76 | library(ggplot2)
77 | library(dplyr)
78 | library(stringr)
79 | # Replace values with full state names
80 | prepped <- midwest %>%
81 | mutate(
82 | state = replace(state, state == "IL", "Illinois"),
83 | state = replace(state, state == "IN", "Indiana"),
84 | state = replace(state, state == "MI", "Michigan"),
85 | state = replace(state, state == "OH", "Ohio"),
86 | state = replace(state, state == "WI", "Wisconsin"),
87 | county = str_to_title(county)
88 | ) %>%
89 | select(county, state, inmetro, contains("per"), -perchsd)
90 | # Write data
91 | write.csv("midwest.csv")
92 | # Data prep
93 | # Use built in `midwest` data from the `ggplot2` package
94 | library(ggplot2)
95 | library(dplyr)
96 | library(stringr)
97 | # Replace values with full state names
98 | prepped <- midwest %>%
99 | mutate(
100 | state = replace(state, state == "IL", "Illinois"),
101 | state = replace(state, state == "IN", "Indiana"),
102 | state = replace(state, state == "MI", "Michigan"),
103 | state = replace(state, state == "OH", "Ohio"),
104 | state = replace(state, state == "WI", "Wisconsin"),
105 | county = str_to_title(county)
106 | ) %>%
107 | select(county, state, inmetro, contains("per"), -perchsd)
108 | # Write data
109 | write.csv(prepped, "midwest.csv", row.names = F)
110 |
--------------------------------------------------------------------------------
/06-state-exercise/data/prep_data.R:
--------------------------------------------------------------------------------
1 | # Data prep
2 |
3 | # Use built in `midwest` data from the `ggplot2` package
4 | library(ggplot2)
5 | library(dplyr)
6 | library(stringr)
7 |
8 | # Replace values with full state names
9 | prepped <- midwest %>%
10 | mutate(
11 | state = replace(state, state == "IL", "Illinois"),
12 | state = replace(state, state == "IN", "Indiana"),
13 | state = replace(state, state == "MI", "Michigan"),
14 | state = replace(state, state == "OH", "Ohio"),
15 | state = replace(state, state == "WI", "Wisconsin"),
16 | county = str_to_title(county)
17 | ) %>%
18 | select(county, state, inmetro, contains("per"), -perchsd)
19 |
20 | # Write data
21 | write.csv(prepped, "midwest.csv", row.names = F)
--------------------------------------------------------------------------------
/06-state-exercise/img/complete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkfreeman/react-d3/47f9cfbd783c059ff752b0e3cd2bce90cccf816a/06-state-exercise/img/complete.png
--------------------------------------------------------------------------------
/06-state-exercise/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 06-state-exercise
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/06-state-exercise/js/App.js:
--------------------------------------------------------------------------------
1 | // Application file
2 | class App extends React.Component {
3 |
4 | constructor(props) {
5 | super(props);
6 |
7 | // Set initial state
8 | this.state = {
9 | data: [],
10 | variable: "percollege",
11 | nShow: 10,
12 | sort:"ascending"
13 | };
14 | }
15 |
16 | componentDidMount() {
17 | // Load data when the component mounts
18 |
19 | }
20 | render() {
21 | // Create a variable `options` storing the list of possible variables to show.
22 | // These should be the **object keys** from the first element in your data, excluding "county" and "state".
23 |
24 |
25 | // Create a variable `allData` in which you store the current value of interest
26 | // (based on `this.state.variable`), as well as the state and county
27 |
28 |
29 | // Store the mean of the current value in a variable `mean`
30 |
31 |
32 | // Store the top N values (based on `this.state.nShow`) from the data in a variable.
33 | // Observatiosn should be sorted by the current sorting variable (`this.state.variable`)
34 | // in either ascending or descending order (`this.state.sort`).
35 |
36 |
37 | // Return an HTML node to render
38 | return (
39 |
40 |
41 |
42 |
43 | {/* Create a Select Menu to determine which variable is shown in the table */}
44 |
45 |
46 | {/*Create a Slider to control how many rows are shown in the table */}
47 |
48 |
49 | {/*Create a Button Group to control if rows in the table are shown in ascending or descending order */}
50 |
51 |
52 |
53 | {/* Display the average value (`mean`) in a paragraph element */}
54 |
55 |
56 | {/* Show a table of the top N counties */}
57 |
58 |
59 | )
60 | }
61 | }
62 |
63 | // Render application
64 | ReactDOM.render(
65 | ,
66 | document.getElementById('root')
67 | );
--------------------------------------------------------------------------------
/06-state-exercise/js/solution.js:
--------------------------------------------------------------------------------
1 | // Application file
2 | class App extends React.Component {
3 |
4 | constructor(props) {
5 | super(props);
6 |
7 | // Set initial state
8 | this.state = {
9 | data: [],
10 | variable: "percollege",
11 | nShow: 10,
12 | sort:"ascending"
13 | };
14 | }
15 |
16 | componentDidMount() {
17 | // Load data when the component mounts
18 | d3.csv("data/midwest.csv", (err, data) => {
19 | this.setState({ data: data });
20 | });
21 | }
22 | render() {
23 | // Create a variable `options` storing the list of possible variables to show.
24 | // These should be the **object keys** from the first element in your data, excluding "county" and "state".
25 | let options = this.state.data.length === 0 ? [] : Object.keys(this.state.data[0]);
26 | options = options.filter((d) => d != "county" && d != "state");
27 |
28 | // Create a variable `allData` in which you store the current value of interest
29 | // (based on `this.state.variable`), as well as the state and county
30 | let allData = this.state.data.map((d) => {
31 | return {
32 | value: +d[this.state.variable],
33 | state: d.state,
34 | county: d.county
35 | };
36 | });
37 |
38 | // Store the mean of the current value in a variable `mean`
39 | let mean = d3.mean(allData, (d) => d.value) || 0;
40 |
41 | // Store the top N values (based on `this.state.nShow`) from the data in a variable.
42 | // Observatiosn should be sorted by the current sorting variable (`this.state.variable`)
43 | // in either ascending or descending order (`this.state.sort`).
44 | let topData = allData.sort((a, b) => {
45 | return this.state.sort == "ascending" ? a.value - b.value : b.value - a.value;
46 | }).filter((d, i) => i < this.state.nShow)
47 |
48 |
49 | // Return an HTML node to render
50 | return (
51 |
52 |
53 |
54 |
55 | {/* Create a Select Menu to determine which variable is shown in the table */}
56 |
57 | Variable:
58 | this.setState({ variable: d.target.value })}>
59 | {options.map((d) => {
60 | return {d}
61 | })}
62 |
63 |
64 |
65 | {/*Create a Slider to control how many rows are shown in the table */}
66 |
67 | Show the top {this.state.nShow} Counties:
68 | this.setState({ nShow: d.target.value })} />
69 |
70 |
71 | {/*Create a Button Group to control if rows in the table are shown in ascending or descending order */}
72 |
73 | {["ascending", "descending"].map((d) =>{
74 | return this.setState({ sort: d })}>{d}
77 |
78 | })}
79 |
80 |
81 |
82 | {/* Display the average value (`mean`) in a paragraph element */}
83 |
Average {this.state.variable} : {mean.toFixed(1) + "%"}
84 |
85 | {/* Show a table of the top N counties */}
86 |
87 |
88 |
89 | County
90 | {this.state.variable}
91 |
92 | {topData.map((d, i) => {
93 | return (
94 |
95 | {d.county + ", " + d.state}
96 | {d.value.toFixed(1) + "%"}
97 |
98 | )
99 | })
100 |
101 | }
102 |
103 |
104 |
105 |
106 | )
107 | }
108 | }
109 |
110 | // Render application
111 | ReactDOM.render(
112 | ,
113 | document.getElementById('root')
114 | );
--------------------------------------------------------------------------------
/07-d3-demo/README.md:
--------------------------------------------------------------------------------
1 | # 05-state-demo
2 | This project uses D3 to draw data on a DOM node that is rendered by a React component. It uses a React component's state to track the number of circles to render, and then draws them in a `` element using D3 whenever the state changes:
3 |
4 | 
--------------------------------------------------------------------------------
/07-d3-demo/css/styles.css:
--------------------------------------------------------------------------------
1 | /* @import url("https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"); */
2 | @import url("../../lib/bootstrap.min.css");
3 |
4 | /* Header */
5 | .container {
6 | text-align: center;
7 | }
8 |
9 | .jumbotron {
10 | padding:1rem 1rem;
11 | margin-bottom:20px;
12 | }
13 |
14 | .lead {
15 | margin-bottom:0px;
16 | }
17 | /* Controls */
18 | .custom-select {
19 | width:inherit;
20 | }
21 |
22 | .control-wrapper {
23 | display: inline-block;
24 | margin-left:10px;
25 | }
26 |
27 | .control-wrapper input[type="range"] {
28 | vertical-align: text-bottom;;
29 | }
30 |
31 | .control-container {
32 | border-bottom:1px solid #d3d3d3;
33 | margin-bottom:10px;
34 | padding-bottom:10px;
35 | }
36 |
37 | /* Scatter plot */
38 | circle {
39 | cursor:pointer;
40 | }
41 | .axis-label {
42 | text-anchor:middle;
43 | font-size:10px;
44 | }
45 |
46 | .chart-wrapper {
47 | display: inline-block;
48 | }
49 |
50 |
51 | /* Tooltips */
52 | .d3-tip {
53 | line-height: 1;
54 | font-weight: bold;
55 | padding: 12px;
56 | background: rgba(0, 0, 0, 0.8);
57 | color: #fff;
58 | border-radius: 2px;
59 | pointer-events: none;
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/07-d3-demo/data/.Rhistory:
--------------------------------------------------------------------------------
1 | library(dplyr)
2 | library(ggplot2)
3 | View(midwest)
4 | dim(midwest)
5 | ?midwest
6 | levels(midwest$state)
7 | unique(midwest$state)
8 | styler:::style_active_file()
9 | prepped <- midwest %>%
10 | mutate(
11 | state = replace(state, state == "IL", "Illinois"),
12 | state = replace(state, state == "IN", "Indiana"),
13 | state = replace(state, state == "MI", "Michigan"),
14 | state = replace(state, state == "OH", "Ohio"),
15 | state = replace(state, state == "WI", "Wisconsin")
16 | )
17 | View(prepped)
18 | library(tools)
19 | prepped <- midwest %>%
20 | mutate(
21 | state = replace(state, state == "IL", "Illinois"),
22 | state = replace(state, state == "IN", "Indiana"),
23 | state = replace(state, state == "MI", "Michigan"),
24 | state = replace(state, state == "OH", "Ohio"),
25 | state = replace(state, state == "WI", "Wisconsin"),
26 | county = toTitleCase(county)
27 | ) %>%
28 | select(county, state, %in% "pop")
29 | prepped <- midwest %>%
30 | mutate(
31 | state = replace(state, state == "IL", "Illinois"),
32 | state = replace(state, state == "IN", "Indiana"),
33 | state = replace(state, state == "MI", "Michigan"),
34 | state = replace(state, state == "OH", "Ohio"),
35 | state = replace(state, state == "WI", "Wisconsin"),
36 | county = toTitleCase(county)
37 | ) %>%
38 | select(county, state, inmetro, contains("per"))
39 | View(prepped)
40 | ?perchsd
41 | ?midwest
42 | prepped <- midwest %>%
43 | mutate(
44 | state = replace(state, state == "IL", "Illinois"),
45 | state = replace(state, state == "IN", "Indiana"),
46 | state = replace(state, state == "MI", "Michigan"),
47 | state = replace(state, state == "OH", "Ohio"),
48 | state = replace(state, state == "WI", "Wisconsin"),
49 | county = toTitleCase(county)
50 | ) %>%
51 | select(county, state, inmetro, contains("per"), -perchsd)
52 | midwest %>%
53 | mutate(
54 | state = replace(state, state == "IL", "Illinois"),
55 | state = replace(state, state == "IN", "Indiana"),
56 | state = replace(state, state == "MI", "Michigan"),
57 | state = replace(state, state == "OH", "Ohio"),
58 | state = replace(state, state == "WI", "Wisconsin"),
59 | county = toTitleCase(county)
60 | )
61 | toTitleCase(midwest$county)
62 | library(stringr)
63 | prepped <- midwest %>%
64 | mutate(
65 | state = replace(state, state == "IL", "Illinois"),
66 | state = replace(state, state == "IN", "Indiana"),
67 | state = replace(state, state == "MI", "Michigan"),
68 | state = replace(state, state == "OH", "Ohio"),
69 | state = replace(state, state == "WI", "Wisconsin"),
70 | county = str_to_title(county)
71 | ) %>%
72 | select(county, state, inmetro, contains("per"), -perchsd)
73 | setwd("~/Documents/react-d3/demo/data")
74 | # Data prep
75 | # Use built in `midwest` data from the `ggplot2` package
76 | library(ggplot2)
77 | library(dplyr)
78 | library(stringr)
79 | # Replace values with full state names
80 | prepped <- midwest %>%
81 | mutate(
82 | state = replace(state, state == "IL", "Illinois"),
83 | state = replace(state, state == "IN", "Indiana"),
84 | state = replace(state, state == "MI", "Michigan"),
85 | state = replace(state, state == "OH", "Ohio"),
86 | state = replace(state, state == "WI", "Wisconsin"),
87 | county = str_to_title(county)
88 | ) %>%
89 | select(county, state, inmetro, contains("per"), -perchsd)
90 | # Write data
91 | write.csv("midwest.csv")
92 | # Data prep
93 | # Use built in `midwest` data from the `ggplot2` package
94 | library(ggplot2)
95 | library(dplyr)
96 | library(stringr)
97 | # Replace values with full state names
98 | prepped <- midwest %>%
99 | mutate(
100 | state = replace(state, state == "IL", "Illinois"),
101 | state = replace(state, state == "IN", "Indiana"),
102 | state = replace(state, state == "MI", "Michigan"),
103 | state = replace(state, state == "OH", "Ohio"),
104 | state = replace(state, state == "WI", "Wisconsin"),
105 | county = str_to_title(county)
106 | ) %>%
107 | select(county, state, inmetro, contains("per"), -perchsd)
108 | # Write data
109 | write.csv(prepped, "midwest.csv", row.names = F)
110 |
--------------------------------------------------------------------------------
/07-d3-demo/data/prep_data.R:
--------------------------------------------------------------------------------
1 | # Data prep
2 |
3 | # Use built in `midwest` data from the `ggplot2` package
4 | library(ggplot2)
5 | library(dplyr)
6 | library(stringr)
7 |
8 | # Replace values with full state names
9 | prepped <- midwest %>%
10 | mutate(
11 | state = replace(state, state == "IL", "Illinois"),
12 | state = replace(state, state == "IN", "Indiana"),
13 | state = replace(state, state == "MI", "Michigan"),
14 | state = replace(state, state == "OH", "Ohio"),
15 | state = replace(state, state == "WI", "Wisconsin"),
16 | county = str_to_title(county)
17 | ) %>%
18 | select(county, state, inmetro, contains("per"), -perchsd)
19 |
20 | # Write data
21 | write.csv(prepped, "midwest.csv", row.names = F)
--------------------------------------------------------------------------------
/07-d3-demo/img/complete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkfreeman/react-d3/47f9cfbd783c059ff752b0e3cd2bce90cccf816a/07-d3-demo/img/complete.png
--------------------------------------------------------------------------------
/07-d3-demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 07-d3-demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
07-d3-demo
40 |
A basic demonstration of React + D3
41 |
(all exercises)
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/07-d3-demo/js/App.js:
--------------------------------------------------------------------------------
1 | // Application file
2 | class App extends React.Component {
3 | // Set initial state in the `constructor()` function
4 | constructor(props) {
5 | super(props);
6 |
7 | // Set initial state
8 | this.state = {
9 | numPoints:10
10 | };
11 | }
12 |
13 | // When the component mounts, run the `updatePoints()` method
14 | componentDidMount() {
15 | this.updatePoints()
16 | }
17 |
18 | // When the component updates, run the `updatePoints()` method
19 | componentDidUpdate() {
20 | this.updatePoints()
21 | }
22 |
23 | // Method to update the cirlces using D3
24 | updatePoints() {
25 | // Randomly generate points based on the `width` and `height` props
26 | let data = d3.range(this.state.numPoints).map((d) => {
27 | return {x: Math.random() * this.props.width, y: Math.random() * this.props.height}
28 | });
29 |
30 | // Select all the circles within thje element
31 | let circles = d3.select(this.chartArea).selectAll('circle').data(data);
32 |
33 | // Use the .enter() method to get your entering elements, and assign their positions
34 | circles.enter().append('circle')
35 | .attr('r', (d) => 3)
36 | .attr('fill', (d) => "blue")
37 | .attr('cx', (d) => d.x)
38 | .attr('cy', (d) => d.y);
39 | }
40 |
41 | // Render method
42 | render() {
43 |
44 | // Return HTML elements to hold the chart
45 | return (
46 |
47 |
48 | this.setState({numPoints:this.state.numPoints +10})}>+ 10 points
49 |
50 |
51 |
52 | { this.chartArea = node; }}
53 | transform={`translate(${this.props.margin.left}, ${this.props.margin.top})`} />
54 |
55 |
56 |
57 | )
58 | }
59 | }
60 |
61 | // Set default properties
62 | App.defaultProps = {
63 | margin: {
64 | left: 0,
65 | right: 10,
66 | top: 20,
67 | bottom: 50
68 | }
69 | };
70 |
71 | // Render application
72 | ReactDOM.render(
73 | ,
74 | document.getElementById('root')
75 | );
76 |
77 |
--------------------------------------------------------------------------------
/08-scatter-exercise/README.md:
--------------------------------------------------------------------------------
1 | # 08-scatter-exercise
2 | In this exercise, you'll follow the instructions below to build an interactive scatterplot in React:
3 |
4 | 
5 |
6 | In doing so, you'll create a React `` component in the `ScatterPlot.js` file. The component that you create will be rendered by your `` component created in the `App.js` file. See the complete code in the `ScatterPlot_solution.js` and `App_solution.js` files.
7 |
8 | ## Instructions
9 | The necessary CSS and HTML code are already written for you in this exercise, as is _some of_ the JavaScript code. The current structure of the application has an `` component rendering a `` component, each defined in their respective files. You'll need to edit _both files_ to complete this exercises.
10 |
11 | ### The `` Component
12 | The `` component, defined in `ScatterPlot.js`, is the component that defines a scatterplot -- it is currently being rendered by the `` component. You'll need to edit the following methods to have the component properly render a ScatterPlot.
13 |
14 | #### `updateScales()` Method
15 | In this method, you'll define the functions for `this.xScale()` and `this.yScale()`, used to draw your scatter plot. To do this, you'll need to:
16 |
17 | - **Calculate limits**: find the minimum/maximum x and y values in the data (i.e., `xMin`, `xMax`, etc).
18 | - **Define scales**: set the values of `this.xScale()` and `this.yScale()` using the `d3.scaleLinear()` method and the limits calculated in the previous step. You'll also want to use `this.drawWidth` and `this.drawHeight` to appropriately set the ranges of your scales.
19 |
20 | #### `updatePoints()` Method
21 | In the `updatePoints()` method of your component, you should update the position of the circles using the D3 data-join process (i.e., _enter_, _exit_, and _update_). To do so, follow these steps:
22 |
23 | - **Bind data**: select the `this.chartArea` element, then select all of the circle elements inside of it and bind your data (`this.props.data`) to the selection.
24 | - **Append and position elements**: using `enter()` and `merge()`, append new elements (circles), and set the visual attributes of all elements (i.e., `cx`, `cy`, `radius`, `fill`, etc.). You can use any transitions you typically would using D3. If you want to use the `d3-tip` library for hovers, use the following code:
25 |
26 | ```javascript
27 | circles.attr('label', (d) => d.label)
28 | .on('mouseover', tip.show)
29 | .on('mouseout', tip.hide)
30 |
31 | // Add hovers using the d3-tip library
32 | d3.select(this.chartArea).call(tip);
33 | ```
34 | - **Exit and remove elements**: to complete the D3 update pattern, you can `exit()` and `remove()` any elements that are no longer present in the dataset.
35 |
36 |
37 | #### `updateAxes()` Method
38 | In your `updateAxes()` method, you'll select the visual elements (i.e., `` elements) rendered by React, and call your D3 axis functions on those ``s.
39 |
40 | - **Define axis functions**: using `d3.axisBottom()` and `d3.axisLeft()`, create _functions_ that describe how to draw your _axes_ using your _scales_ (`this.xScale()` and `this.yScale()`).
41 | - **Draw axes**: use D3 to _select_ your axes (`` elements rendered by React), and call the axis functions defined above to draw your axes.
42 |
43 |
44 | #### `update()` Method
45 | Because you want to re-render your chart when your component _mounts_, as well as whenever your component _updates_, it makes sense to wrap the above 3 functions in a single `update()` method.
46 |
47 | - In your `update()` method, call your `updateScales()`, `updateAxes()`, and `updatePoints()` methods
48 |
49 | #### `render()` Method
50 | The `render()` method returns the building blocks of your chart. While these are already returned in the proper locations, they **are not** exposed as variables for D3 to manipulate. For each axis `` and your plotting `` do the following:
51 |
52 | - **Expose the DOM node**: in order to have D3 manipulate each DOM element, you'll need to expose it as a variable. To do so, use the following syntax (where `VARNAME` is the name of the element you wish to expose, such as `xAxis`):
53 |
54 | ```javascript
55 | { this.VARNAME = node; }}
56 | ```
57 |
58 | ### The `` Component
59 | Your `` component (in your `App.js` file) is already fairly structured; however there are a number of pieces that you'll need to complete.
60 |
61 | #### `componentDidMount()` Method
62 | In your `componentDidMount()` method, you'll need to do the following:
63 |
64 | - Load your data from the `data/midwest.csv` file, and, when you have successfully loaded the data, **set your state** of the `data` property to the loaded data.
65 |
66 | #### `render()` Method
67 | In your `render` method, you'll compute data to pass to the components that you render.
68 |
69 | - **Compute `allData`**: Create an _array of objects_ `allData` that stores data for your `` component. Each object should have an `x`, `y`, and `label` property that you create using the current state of your App (i.e., using `this.state.xVar`, etc.). You should do this by iterating through `this.state.data`.
70 |
71 | Then, in your `return` statement, make the following changes:
72 | - **Y Variable Select**: mirroring the X variable select, create a `` menu that changes the state of the current Y variable.
73 | - Render a `` component, passing in the following properties:
74 | - `xTitle`: an X title for the scatterplot. This should be the value in your state controlled by the X select menu.
75 | - `yTitle`: a Y title for the scatterplot. This should be the value in your state controlled by the Y select menu.
76 | - `data`: the data for the scatterplot, stored in your `allData` variable.
77 |
78 |
79 |
--------------------------------------------------------------------------------
/08-scatter-exercise/css/styles.css:
--------------------------------------------------------------------------------
1 | /* @import url("https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"); */
2 | @import url("../../lib/bootstrap.min.css");
3 |
4 | /* Header */
5 | .container {
6 | text-align: center;
7 | }
8 |
9 | .jumbotron {
10 | padding:1rem 1rem;
11 | margin-bottom:20px;
12 | }
13 |
14 | .lead {
15 | margin-bottom:0px;
16 | }
17 |
18 | /* Controls */
19 | .custom-select {
20 | width:inherit;
21 | }
22 |
23 | .control-wrapper {
24 | display: inline-block;
25 | margin-left:10px;
26 | }
27 |
28 | .control-wrapper input[type="range"] {
29 | vertical-align: text-bottom;;
30 | }
31 |
32 | .control-container {
33 | border-bottom:1px solid #d3d3d3;
34 | margin-bottom:10px;
35 | padding-bottom:10px;
36 | }
37 |
38 | /* Scatter plot */
39 | circle {
40 | cursor:pointer;
41 | }
42 | .axis-label {
43 | text-anchor:middle;
44 | font-size:10px;
45 | }
46 |
47 | .chart-wrapper {
48 | display: inline-block;
49 | }
50 |
51 |
52 | /* Tooltips */
53 | .d3-tip {
54 | line-height: 1;
55 | font-weight: bold;
56 | padding: 12px;
57 | background: rgba(0, 0, 0, 0.8);
58 | color: #fff;
59 | border-radius: 2px;
60 | pointer-events: none;
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/08-scatter-exercise/data/.Rhistory:
--------------------------------------------------------------------------------
1 | library(dplyr)
2 | library(ggplot2)
3 | View(midwest)
4 | dim(midwest)
5 | ?midwest
6 | levels(midwest$state)
7 | unique(midwest$state)
8 | styler:::style_active_file()
9 | prepped <- midwest %>%
10 | mutate(
11 | state = replace(state, state == "IL", "Illinois"),
12 | state = replace(state, state == "IN", "Indiana"),
13 | state = replace(state, state == "MI", "Michigan"),
14 | state = replace(state, state == "OH", "Ohio"),
15 | state = replace(state, state == "WI", "Wisconsin")
16 | )
17 | View(prepped)
18 | library(tools)
19 | prepped <- midwest %>%
20 | mutate(
21 | state = replace(state, state == "IL", "Illinois"),
22 | state = replace(state, state == "IN", "Indiana"),
23 | state = replace(state, state == "MI", "Michigan"),
24 | state = replace(state, state == "OH", "Ohio"),
25 | state = replace(state, state == "WI", "Wisconsin"),
26 | county = toTitleCase(county)
27 | ) %>%
28 | select(county, state, %in% "pop")
29 | prepped <- midwest %>%
30 | mutate(
31 | state = replace(state, state == "IL", "Illinois"),
32 | state = replace(state, state == "IN", "Indiana"),
33 | state = replace(state, state == "MI", "Michigan"),
34 | state = replace(state, state == "OH", "Ohio"),
35 | state = replace(state, state == "WI", "Wisconsin"),
36 | county = toTitleCase(county)
37 | ) %>%
38 | select(county, state, inmetro, contains("per"))
39 | View(prepped)
40 | ?perchsd
41 | ?midwest
42 | prepped <- midwest %>%
43 | mutate(
44 | state = replace(state, state == "IL", "Illinois"),
45 | state = replace(state, state == "IN", "Indiana"),
46 | state = replace(state, state == "MI", "Michigan"),
47 | state = replace(state, state == "OH", "Ohio"),
48 | state = replace(state, state == "WI", "Wisconsin"),
49 | county = toTitleCase(county)
50 | ) %>%
51 | select(county, state, inmetro, contains("per"), -perchsd)
52 | midwest %>%
53 | mutate(
54 | state = replace(state, state == "IL", "Illinois"),
55 | state = replace(state, state == "IN", "Indiana"),
56 | state = replace(state, state == "MI", "Michigan"),
57 | state = replace(state, state == "OH", "Ohio"),
58 | state = replace(state, state == "WI", "Wisconsin"),
59 | county = toTitleCase(county)
60 | )
61 | toTitleCase(midwest$county)
62 | library(stringr)
63 | prepped <- midwest %>%
64 | mutate(
65 | state = replace(state, state == "IL", "Illinois"),
66 | state = replace(state, state == "IN", "Indiana"),
67 | state = replace(state, state == "MI", "Michigan"),
68 | state = replace(state, state == "OH", "Ohio"),
69 | state = replace(state, state == "WI", "Wisconsin"),
70 | county = str_to_title(county)
71 | ) %>%
72 | select(county, state, inmetro, contains("per"), -perchsd)
73 | setwd("~/Documents/react-d3/demo/data")
74 | # Data prep
75 | # Use built in `midwest` data from the `ggplot2` package
76 | library(ggplot2)
77 | library(dplyr)
78 | library(stringr)
79 | # Replace values with full state names
80 | prepped <- midwest %>%
81 | mutate(
82 | state = replace(state, state == "IL", "Illinois"),
83 | state = replace(state, state == "IN", "Indiana"),
84 | state = replace(state, state == "MI", "Michigan"),
85 | state = replace(state, state == "OH", "Ohio"),
86 | state = replace(state, state == "WI", "Wisconsin"),
87 | county = str_to_title(county)
88 | ) %>%
89 | select(county, state, inmetro, contains("per"), -perchsd)
90 | # Write data
91 | write.csv("midwest.csv")
92 | # Data prep
93 | # Use built in `midwest` data from the `ggplot2` package
94 | library(ggplot2)
95 | library(dplyr)
96 | library(stringr)
97 | # Replace values with full state names
98 | prepped <- midwest %>%
99 | mutate(
100 | state = replace(state, state == "IL", "Illinois"),
101 | state = replace(state, state == "IN", "Indiana"),
102 | state = replace(state, state == "MI", "Michigan"),
103 | state = replace(state, state == "OH", "Ohio"),
104 | state = replace(state, state == "WI", "Wisconsin"),
105 | county = str_to_title(county)
106 | ) %>%
107 | select(county, state, inmetro, contains("per"), -perchsd)
108 | # Write data
109 | write.csv(prepped, "midwest.csv", row.names = F)
110 |
--------------------------------------------------------------------------------
/08-scatter-exercise/data/prep_data.R:
--------------------------------------------------------------------------------
1 | # Data prep
2 |
3 | # Use built in `midwest` data from the `ggplot2` package
4 | library(ggplot2)
5 | library(dplyr)
6 | library(stringr)
7 |
8 | # Replace values with full state names
9 | prepped <- midwest %>%
10 | mutate(
11 | state = replace(state, state == "IL", "Illinois"),
12 | state = replace(state, state == "IN", "Indiana"),
13 | state = replace(state, state == "MI", "Michigan"),
14 | state = replace(state, state == "OH", "Ohio"),
15 | state = replace(state, state == "WI", "Wisconsin"),
16 | county = str_to_title(county)
17 | ) %>%
18 | select(county, state, inmetro, contains("per"), -perchsd)
19 |
20 | # Write data
21 | write.csv(prepped, "midwest.csv", row.names = F)
--------------------------------------------------------------------------------
/08-scatter-exercise/img/complete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkfreeman/react-d3/47f9cfbd783c059ff752b0e3cd2bce90cccf816a/08-scatter-exercise/img/complete.png
--------------------------------------------------------------------------------
/08-scatter-exercise/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 08-scatter-exercise
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
08-scatter-exercise
41 |
Making a D3 scatterplot in React
42 |
(all exercises)
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/08-scatter-exercise/js/App.js:
--------------------------------------------------------------------------------
1 | // Application file
2 | class App extends React.Component {
3 | constructor(props) {
4 | super(props);
5 |
6 | // Set initial state
7 | this.state = {
8 | data: [],
9 | xVar: "percollege",
10 | yVar: "percbelowpoverty"
11 | };
12 | }
13 | // Load data and set statewhen the component mounts
14 | componentDidMount() {
15 |
16 | }
17 | render() {
18 | // Get list of possible x and y variables
19 | let options = this.state.data.length === 0 ? [] : Object.keys(this.state.data[0]);
20 | options = options.filter((d) => d != "county" && d != "state");
21 |
22 | // Compute `allData`: an array of objects for your scatterplot
23 |
24 | return (
25 |
26 |
27 |
28 | {/* X Variable Select Menu */}
29 |
30 | X Variable:
31 | this.setState({ xVar: d.target.value })}>
32 | {options.map((d) => {
33 | return {d}
34 | })}
35 |
36 |
37 |
38 | {/* Create a Y Variable Select Menu */}
39 |
40 |
41 |
42 | {/* Render a `
` plot */}
43 |
44 |
45 | )
46 | }
47 | }
48 |
49 | // Render application
50 | ReactDOM.render(
51 | ,
52 | document.getElementById('root')
53 | );
--------------------------------------------------------------------------------
/08-scatter-exercise/js/App_solution.js:
--------------------------------------------------------------------------------
1 | // Application file
2 | class App extends React.Component {
3 | constructor(props) {
4 | super(props);
5 |
6 | // Set initial state
7 | this.state = {
8 | data: [],
9 | xVar: "percollege",
10 | yVar: "percbelowpoverty"
11 | };
12 | }
13 | componentDidMount() {
14 | // Load data when the component mounts
15 | d3.csv("data/midwest.csv", (err, data) => {
16 | this.setState({ data: data });
17 | });
18 | }
19 | render() {
20 | // Get list of possible x and y variables
21 | let options = this.state.data.length === 0 ? [] : Object.keys(this.state.data[0]);
22 | options = options.filter((d) => d != "county" && d != "state");
23 |
24 | // Store all of the data to be plotted
25 | let allData = this.state.data.map((d) => {
26 | return {
27 | x: d[this.state.xVar],
28 | y: d[this.state.yVar],
29 | label: d.county + ", " + d.state
30 | };
31 | });
32 |
33 | return (
34 |
35 |
36 |
37 | {/* X Variable Select Menu */}
38 |
39 | X Variable:
40 | this.setState({ xVar: d.target.value })}>
41 | {options.map((d) => {
42 | return {d}
43 | })}
44 |
45 |
46 |
47 | {/* Y Variable Select Menu */}
48 |
49 | Y Variable:
50 | this.setState({ yVar: d.target.value })}>
51 | {options.map((d) => {
52 | return {d}
53 | })}
54 |
55 |
56 |
57 |
58 | {/* Render scatter plot */}
59 |
64 |
65 | )
66 | }
67 | }
68 |
69 | // Render application
70 | ReactDOM.render(
71 | ,
72 | document.getElementById('root')
73 | );
--------------------------------------------------------------------------------
/08-scatter-exercise/js/ScatterPlot.js:
--------------------------------------------------------------------------------
1 | // Scatterplot
2 | class ScatterPlot extends React.Component {
3 | constructor(props) {
4 | super(props);
5 | // Graph width and height - accounting for margins
6 | this.drawWidth = this.props.width - this.props.margin.left - this.props.margin.right;
7 | this.drawHeight = this.props.height - this.props.margin.top - this.props.margin.bottom;
8 |
9 | }
10 |
11 | // When the component mounts, call the `update()` method
12 | componentDidMount() {
13 | this.update();
14 | }
15 |
16 | // Whenever the component updates, call the `update()` method
17 | componentDidUpdate() {
18 | this.update();
19 | }
20 |
21 | // Define the functions for `this.xScale()` and `this.yScale()` based on current data
22 | updateScales() {
23 | // Calculate limits: minimum/maximum x and y values in the data
24 |
25 |
26 | // Define scales `this.xScale()` and `this.yScale()` using `d3.scaleLinear()`
27 |
28 | }
29 |
30 | // Update the position of the circles
31 | updatePoints() {
32 | // Define hovers
33 | // Add tip
34 | let tip = d3.tip().attr('class', 'd3-tip').html(function (d) {
35 | return d.label;
36 | });
37 |
38 | // Bind data: select all circles and bind data
39 |
40 |
41 | // Append and position elements: using `enter()` and `merge()`
42 |
43 |
44 | // Exit and remove elements
45 |
46 |
47 | // Add hovers using the d3-tip library
48 |
49 | }
50 |
51 | // Update axes
52 | updateAxes() {
53 | // Define axis functions
54 |
55 |
56 | // Draw axes: select your axes and call the axis functions defined above
57 |
58 | }
59 |
60 | // Update function: call `updateScales()`, `updateAxes()`, and `updatePoints()`
61 | update() {
62 |
63 | }
64 |
65 | // Render method
66 | render() {
67 | return (
68 |
69 |
70 | {this.props.title}
71 |
72 |
73 | {/* Axes */}
74 |
75 |
76 |
77 | {/* Axis labels */}
78 | {this.props.xTitle}
80 |
81 | {this.props.yTitle}
83 |
84 |
85 |
86 | )
87 | }
88 | }
89 |
90 | ScatterPlot.defaultProps = {
91 | data: [{ x: 10, y: 20 }, { x: 15, y: 35 }],
92 | width: 300,
93 | height: 300,
94 | radius: 5,
95 | color: "blue",
96 | margin: {
97 | left: 50,
98 | right: 10,
99 | top: 20,
100 | bottom: 50
101 | },
102 | xTitle: "X Title",
103 | yTitle: "Y Title",
104 | title:"Chart Title"
105 | };
--------------------------------------------------------------------------------
/08-scatter-exercise/js/ScatterPlot_solution.js:
--------------------------------------------------------------------------------
1 | // Scatterplot
2 | class ScatterPlot extends React.Component {
3 | constructor(props) {
4 | super(props);
5 | // Graph width and height - accounting for margins
6 | this.drawWidth = this.props.width - this.props.margin.left - this.props.margin.right;
7 | this.drawHeight = this.props.height - this.props.margin.top - this.props.margin.bottom;
8 |
9 | }
10 | componentDidMount() {
11 | this.update();
12 | }
13 | // Whenever the component updates, select the from the DOM, and use D3 to manipulte circles
14 | componentDidUpdate() {
15 | this.update();
16 | }
17 | updateScales() {
18 | // Calculate limits
19 | let xMin = d3.min(this.props.data, (d) => +d.x * .9);
20 | let xMax = d3.max(this.props.data, (d) => +d.x * 1.1);
21 | let yMin = d3.min(this.props.data, (d) => +d.y * .9);
22 | let yMax = d3.max(this.props.data, (d) => +d.y * 1.1);
23 |
24 | // Define scales
25 | this.xScale = d3.scaleLinear().domain([xMin, xMax]).range([0, this.drawWidth])
26 | this.yScale = d3.scaleLinear().domain([yMax, yMin]).range([0, this.drawHeight])
27 | }
28 | updatePoints() {
29 | // Define hovers
30 | // Add tip
31 | let tip = d3.tip().attr('class', 'd3-tip').html(function (d) {
32 | return d.label;
33 | });
34 |
35 | // Select all circles and bind data
36 | let circles = d3.select(this.chartArea).selectAll('circle').data(this.props.data);
37 |
38 | // Use the .enter() method to get your entering elements, and assign their positions
39 | circles.enter().append('circle')
40 | .merge(circles)
41 | .attr('r', (d) => this.props.radius)
42 | .attr('fill', (d) => this.props.color)
43 | .attr('label', (d) => d.label)
44 | .on('mouseover', tip.show)
45 | .on('mouseout', tip.hide)
46 | .style('fill-opacity', 0.3)
47 | .transition().duration(500)
48 | .attr('cx', (d) => this.xScale(d.x))
49 | .attr('cy', (d) => this.yScale(d.y))
50 | .style('stroke', "black")
51 | .style('stroke-width', (d) => d.selected == true ? "3px" : "0px")
52 |
53 |
54 | // Use the .exit() and .remove() methods to remove elements that are no longer in the data
55 | circles.exit().remove();
56 |
57 | // Add hovers using the d3-tip library
58 | d3.select(this.chartArea).call(tip);
59 | }
60 | updateAxes() {
61 | let xAxisFunction = d3.axisBottom()
62 | .scale(this.xScale)
63 | .ticks(5, 's');
64 |
65 | let yAxisFunction = d3.axisLeft()
66 | .scale(this.yScale)
67 | .ticks(5, 's');
68 |
69 | d3.select(this.xAxis)
70 | .call(xAxisFunction);
71 |
72 | d3.select(this.yAxis)
73 | .call(yAxisFunction);
74 | }
75 | update() {
76 | this.updateScales();
77 | this.updateAxes();
78 | this.updatePoints();
79 | }
80 | render() {
81 | return (
82 |
83 |
84 | {this.props.title}
85 | { this.chartArea = node; }}
86 | transform={`translate(${this.props.margin.left}, ${this.props.margin.top})`} />
87 |
88 | {/* Axes */}
89 | { this.xAxis = node; }}
90 | transform={`translate(${this.props.margin.left}, ${this.props.height - this.props.margin.bottom})`}>
91 | { this.yAxis = node; }}
92 | transform={`translate(${this.props.margin.left}, ${this.props.margin.top})`}>
93 |
94 | {/* Axis labels */}
95 | {this.props.xTitle}
97 |
98 | {this.props.yTitle}
100 |
101 |
102 |
103 | )
104 | }
105 | }
106 |
107 | ScatterPlot.defaultProps = {
108 | data: [{ x: 10, y: 20 }, { x: 15, y: 35 }],
109 | width: 300,
110 | height: 300,
111 | radius: 5,
112 | color: "blue",
113 | margin: {
114 | left: 50,
115 | right: 10,
116 | top: 20,
117 | bottom: 50
118 | },
119 | xTitle: "X Title",
120 | yTitle: "Y Title",
121 | };
--------------------------------------------------------------------------------
/09-small-multiples-exercise/README.md:
--------------------------------------------------------------------------------
1 | # 09-small-multiples
2 |
3 | In this exercise, you'll follow the instructions below to create a small multiples visualization layout using React:
4 |
5 | 
6 |
7 | In doing so, you'll use `d3.nest()` to organize your data to pass to your `` components. See the `App_solution.js` file for solutions.
8 |
9 | ## Instructions
10 | The majority of the code has been written for this exercise. The core challenge of this exercise is to figure out the proper **data structure** for creating small multiples. More specifically, you'll create a `` component for each _state_ in the dataset. To do so, make the following changes in your `App.js` file.
11 |
12 | ### `render()` Method
13 | As noted above, this exercise is really about getting the right _data structure_. Because the intention is to render a `` component for each _state_ in the dataset, you'll need to create an array of data, one object for each state present. Luckily, the `d3.nest()` function is a direct solution to this challenge:
14 |
15 | - **Create `nestedData`**: create a variable `nestedData` by using the `d3.nest()` method, indicating that the `key` for each group should be dictated by the `group` property. This is because in the `allData` array, the generic term `group` is used, rather than something specific to this dataset (like "`state`"). This should store an **array of objects**, each of which will have a _key_ (the state name), and a set of _values_ (the array of objects to be visualized in the scatter plot)
16 | - **Return `` Components**: at the end of your `return()` statement, you should iterate through your `nestedData` object, and return a `` component in each iteration. When you return the ``, you'll need to set the following _properties_:
17 | - `title`: the title for the chart (stored in the `.key` property of your iterant).
18 | - `xTitle`: a label for your x axis in the scatterplot. This will be the same for each plot, and is stored in `this.state.xVar`.
19 | - `yTitle`: a label for your y axis in the scatterplot. This will be the same for each plot, and is stored in `this.state.yVar`.
20 | - `radius`: a radius for each point, stored in `this.state.radius`.
21 | - `color`: the color for each point, stored in `this.state.color`.
22 | - `data`: the data for the plot, stored in the `.vales` property of your iterant.
23 | - `key`: a unique key for the plot, so React can easily _diff_ the DOM. You could use the index of the iterant, or perhaps more appropriately, the `.key` property of your iterant.
24 |
--------------------------------------------------------------------------------
/09-small-multiples-exercise/css/styles.css:
--------------------------------------------------------------------------------
1 | /* @import url("https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"); */
2 | @import url("../../lib/bootstrap.min.css");
3 |
4 | /* Header */
5 | .container {
6 | text-align: center;
7 | }
8 |
9 | .lead {
10 | margin-bottom:0px;
11 | }
12 |
13 | .jumbotron {
14 | padding:1rem 1rem;
15 | margin-bottom:20px;
16 | }
17 |
18 | /* Controls */
19 | .custom-select {
20 | width:inherit;
21 | }
22 |
23 | .control-wrapper {
24 | display: inline-block;
25 | margin-left:10px;
26 | }
27 |
28 | .control-wrapper input[type="range"] {
29 | vertical-align: text-bottom;;
30 | }
31 |
32 | .control-container {
33 | border-bottom:1px solid #d3d3d3;
34 | margin-bottom:10px;
35 | padding-bottom:10px;
36 | }
37 |
38 | /* Scatter plot */
39 | circle {
40 | cursor:pointer;
41 | }
42 | .axis-label {
43 | text-anchor:middle;
44 | font-size:10px;
45 | }
46 |
47 | .chart-wrapper {
48 | display: inline-block;
49 | }
50 |
51 |
52 | /* Tooltips */
53 | .d3-tip {
54 | line-height: 1;
55 | font-weight: bold;
56 | padding: 12px;
57 | background: rgba(0, 0, 0, 0.8);
58 | color: #fff;
59 | border-radius: 2px;
60 | pointer-events: none;
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/09-small-multiples-exercise/data/.Rhistory:
--------------------------------------------------------------------------------
1 | library(dplyr)
2 | library(ggplot2)
3 | View(midwest)
4 | dim(midwest)
5 | ?midwest
6 | levels(midwest$state)
7 | unique(midwest$state)
8 | styler:::style_active_file()
9 | prepped <- midwest %>%
10 | mutate(
11 | state = replace(state, state == "IL", "Illinois"),
12 | state = replace(state, state == "IN", "Indiana"),
13 | state = replace(state, state == "MI", "Michigan"),
14 | state = replace(state, state == "OH", "Ohio"),
15 | state = replace(state, state == "WI", "Wisconsin")
16 | )
17 | View(prepped)
18 | library(tools)
19 | prepped <- midwest %>%
20 | mutate(
21 | state = replace(state, state == "IL", "Illinois"),
22 | state = replace(state, state == "IN", "Indiana"),
23 | state = replace(state, state == "MI", "Michigan"),
24 | state = replace(state, state == "OH", "Ohio"),
25 | state = replace(state, state == "WI", "Wisconsin"),
26 | county = toTitleCase(county)
27 | ) %>%
28 | select(county, state, %in% "pop")
29 | prepped <- midwest %>%
30 | mutate(
31 | state = replace(state, state == "IL", "Illinois"),
32 | state = replace(state, state == "IN", "Indiana"),
33 | state = replace(state, state == "MI", "Michigan"),
34 | state = replace(state, state == "OH", "Ohio"),
35 | state = replace(state, state == "WI", "Wisconsin"),
36 | county = toTitleCase(county)
37 | ) %>%
38 | select(county, state, inmetro, contains("per"))
39 | View(prepped)
40 | ?perchsd
41 | ?midwest
42 | prepped <- midwest %>%
43 | mutate(
44 | state = replace(state, state == "IL", "Illinois"),
45 | state = replace(state, state == "IN", "Indiana"),
46 | state = replace(state, state == "MI", "Michigan"),
47 | state = replace(state, state == "OH", "Ohio"),
48 | state = replace(state, state == "WI", "Wisconsin"),
49 | county = toTitleCase(county)
50 | ) %>%
51 | select(county, state, inmetro, contains("per"), -perchsd)
52 | midwest %>%
53 | mutate(
54 | state = replace(state, state == "IL", "Illinois"),
55 | state = replace(state, state == "IN", "Indiana"),
56 | state = replace(state, state == "MI", "Michigan"),
57 | state = replace(state, state == "OH", "Ohio"),
58 | state = replace(state, state == "WI", "Wisconsin"),
59 | county = toTitleCase(county)
60 | )
61 | toTitleCase(midwest$county)
62 | library(stringr)
63 | prepped <- midwest %>%
64 | mutate(
65 | state = replace(state, state == "IL", "Illinois"),
66 | state = replace(state, state == "IN", "Indiana"),
67 | state = replace(state, state == "MI", "Michigan"),
68 | state = replace(state, state == "OH", "Ohio"),
69 | state = replace(state, state == "WI", "Wisconsin"),
70 | county = str_to_title(county)
71 | ) %>%
72 | select(county, state, inmetro, contains("per"), -perchsd)
73 | setwd("~/Documents/react-d3/demo/data")
74 | # Data prep
75 | # Use built in `midwest` data from the `ggplot2` package
76 | library(ggplot2)
77 | library(dplyr)
78 | library(stringr)
79 | # Replace values with full state names
80 | prepped <- midwest %>%
81 | mutate(
82 | state = replace(state, state == "IL", "Illinois"),
83 | state = replace(state, state == "IN", "Indiana"),
84 | state = replace(state, state == "MI", "Michigan"),
85 | state = replace(state, state == "OH", "Ohio"),
86 | state = replace(state, state == "WI", "Wisconsin"),
87 | county = str_to_title(county)
88 | ) %>%
89 | select(county, state, inmetro, contains("per"), -perchsd)
90 | # Write data
91 | write.csv("midwest.csv")
92 | # Data prep
93 | # Use built in `midwest` data from the `ggplot2` package
94 | library(ggplot2)
95 | library(dplyr)
96 | library(stringr)
97 | # Replace values with full state names
98 | prepped <- midwest %>%
99 | mutate(
100 | state = replace(state, state == "IL", "Illinois"),
101 | state = replace(state, state == "IN", "Indiana"),
102 | state = replace(state, state == "MI", "Michigan"),
103 | state = replace(state, state == "OH", "Ohio"),
104 | state = replace(state, state == "WI", "Wisconsin"),
105 | county = str_to_title(county)
106 | ) %>%
107 | select(county, state, inmetro, contains("per"), -perchsd)
108 | # Write data
109 | write.csv(prepped, "midwest.csv", row.names = F)
110 |
--------------------------------------------------------------------------------
/09-small-multiples-exercise/data/prep_data.R:
--------------------------------------------------------------------------------
1 | # Data prep
2 |
3 | # Use built in `midwest` data from the `ggplot2` package
4 | library(ggplot2)
5 | library(dplyr)
6 | library(stringr)
7 |
8 | # Replace values with full state names
9 | prepped <- midwest %>%
10 | mutate(
11 | state = replace(state, state == "IL", "Illinois"),
12 | state = replace(state, state == "IN", "Indiana"),
13 | state = replace(state, state == "MI", "Michigan"),
14 | state = replace(state, state == "OH", "Ohio"),
15 | state = replace(state, state == "WI", "Wisconsin"),
16 | county = str_to_title(county)
17 | ) %>%
18 | select(county, state, inmetro, contains("per"), -perchsd)
19 |
20 | # Write data
21 | write.csv(prepped, "midwest.csv", row.names = F)
--------------------------------------------------------------------------------
/09-small-multiples-exercise/img/complete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkfreeman/react-d3/47f9cfbd783c059ff752b0e3cd2bce90cccf816a/09-small-multiples-exercise/img/complete.png
--------------------------------------------------------------------------------
/09-small-multiples-exercise/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 09-small-multiples-exercise
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
09-small-multiples-exercise
41 |
Building small multiples with React + D3
42 |
(all exercises)
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/09-small-multiples-exercise/js/App.js:
--------------------------------------------------------------------------------
1 | // Application file
2 | class App extends React.Component {
3 | constructor(props) {
4 | super(props);
5 |
6 | // Set initial state
7 | this.state = {
8 | data: [],
9 | xVar: "percollege",
10 | yVar: "percbelowpoverty",
11 | search: '',
12 | radius: 3,
13 | color: '#081AFF'
14 | };
15 | }
16 | componentDidMount() {
17 | // Load data when the component mounts
18 | d3.csv("data/midwest.csv", (err, data) => {
19 | this.setState({ data: data });
20 | });
21 | }
22 | render() {
23 | // Get list of possible x and y variables
24 | let options = this.state.data.length === 0 ? [] : Object.keys(this.state.data[0]);
25 | options = options.filter((d) => d != "county" && d != "state");
26 |
27 | // Store all of the data to be plotted
28 | let allData = this.state.data.map((d) => {
29 | return {
30 | x: d[this.state.xVar],
31 | y: d[this.state.yVar],
32 | label: d.county + ", " + d.state,
33 | group: d.state,
34 | selected: d.county.toLowerCase().match(this.state.search.toLowerCase()) != null && this.state.search !== ''
35 | };
36 | });
37 |
38 | // Nest the data to create different charts by state
39 |
40 |
41 | return (
42 |
43 |
44 |
45 | {/* X Variable Select Menu */}
46 |
47 | X Variable:
48 | this.setState({ xVar: d.target.value })}>
49 | {options.map((d) => {
50 | return {d}
51 | })}
52 |
53 |
54 |
55 | {/* Y Variable Select Menu */}
56 |
57 | Y Variable:
58 | this.setState({ yVar: d.target.value })}>
59 | {options.map((d) => {
60 | return {d}
61 | })}
62 |
63 |
64 |
65 | {/* Radius Slider */}
66 |
67 | Radius:
68 | this.setState({ radius: d.target.value })} />
69 |
70 |
71 | {/* Color Picker */}
72 |
73 | Color:
74 | this.setState({ color: d.target.value })} />
75 |
76 |
77 | {/* Search Input */}
78 |
79 | this.setState({ search: d.target.value })} />
80 |
81 |
82 |
83 | {/* Render scatter plots */}
84 | {/* iterate through `nestedData` here */
85 |
86 | }
87 |
88 | )
89 | }
90 | }
91 |
92 | // Render application
93 | ReactDOM.render(
94 | ,
95 | document.getElementById('root')
96 | );
--------------------------------------------------------------------------------
/09-small-multiples-exercise/js/App_solution.js:
--------------------------------------------------------------------------------
1 | // Application file
2 | class App extends React.Component {
3 | constructor(props) {
4 | super(props);
5 |
6 | // Set initial state
7 | this.state = {
8 | data: [],
9 | xVar: "percollege",
10 | yVar: "percbelowpoverty",
11 | search: '',
12 | radius: 3,
13 | color: '#081AFF'
14 | };
15 | }
16 | componentDidMount() {
17 | // Load data when the component mounts
18 | d3.csv("data/midwest.csv", (err, data) => {
19 | this.setState({ data: data });
20 | });
21 | }
22 | render() {
23 | // Get list of possible x and y variables
24 | let options = this.state.data.length === 0 ? [] : Object.keys(this.state.data[0]);
25 | options = options.filter((d) => d != "county" && d != "state");
26 |
27 | // Store all of the data to be plotted
28 | let allData = this.state.data.map((d) => {
29 | return {
30 | x: d[this.state.xVar],
31 | y: d[this.state.yVar],
32 | label: d.county + ", " + d.state,
33 | group: d.state,
34 | selected: d.county.toLowerCase().match(this.state.search.toLowerCase()) != null && this.state.search !== ''
35 | };
36 | });
37 |
38 | // Nest the data to create different charts by state
39 | let nestedData = d3.nest().key((d) => d.group)
40 | .entries(allData);
41 |
42 | return (
43 |
44 |
45 |
09-small-multiples-exercise
46 |
Building small multiples with React + D3
47 |
(all exercises)
48 |
49 |
50 |
51 |
52 | {/* X Variable Select Menu */}
53 |
54 | X Variable:
55 | this.setState({ xVar: d.target.value })}>
56 | {options.map((d) => {
57 | return {d}
58 | })}
59 |
60 |
61 |
62 | {/* Y Variable Select Menu */}
63 |
64 | Y Variable:
65 | this.setState({ yVar: d.target.value })}>
66 | {options.map((d) => {
67 | return {d}
68 | })}
69 |
70 |
71 |
72 | {/* Radius Slider */}
73 |
74 | Radius:
75 | this.setState({ radius: d.target.value })} />
76 |
77 |
78 | {/* Color Picker */}
79 |
80 | Color:
81 | this.setState({ color: d.target.value })} />
82 |
83 |
84 | {/* Search Input */}
85 |
86 | this.setState({ search: d.target.value })} />
87 |
88 |
89 |
90 | {/* Render scatter plots */}
91 | {
92 | nestedData.map((group) => {
93 | return
101 | })
102 | }
103 |
104 |
105 | )
106 | }
107 | }
108 |
109 | // Render application
110 | ReactDOM.render(
111 | ,
112 | document.getElementById('root')
113 | );
--------------------------------------------------------------------------------
/09-small-multiples-exercise/js/ScatterPlot.js:
--------------------------------------------------------------------------------
1 | // Scatterplot
2 | class ScatterPlot extends React.Component {
3 | constructor(props) {
4 | super(props);
5 | // Graph width and height - accounting for margins
6 | this.drawWidth = this.props.width - this.props.margin.left - this.props.margin.right;
7 | this.drawHeight = this.props.height - this.props.margin.top - this.props.margin.bottom;
8 |
9 | }
10 | componentDidMount() {
11 | this.update();
12 | }
13 | // Whenever the component updates, select the from the DOM, and use D3 to manipulte circles
14 | componentDidUpdate() {
15 | this.update();
16 | }
17 | updateScales() {
18 | // Calculate limits
19 | let xMin = d3.min(this.props.data, (d) => +d.x * .9);
20 | let xMax = d3.max(this.props.data, (d) => +d.x * 1.1);
21 | let yMin = d3.min(this.props.data, (d) => +d.y * .9);
22 | let yMax = d3.max(this.props.data, (d) => +d.y * 1.1);
23 |
24 | // Define scales
25 | this.xScale = d3.scaleLinear().domain([xMin, xMax]).range([0, this.drawWidth])
26 | this.yScale = d3.scaleLinear().domain([yMax, yMin]).range([0, this.drawHeight])
27 | }
28 | updatePoints() {
29 | // Define hovers
30 | // Add tip
31 | let tip = d3.tip().attr('class', 'd3-tip').html(function (d) {
32 | return d.label;
33 | });
34 |
35 | // Select all circles and bind data
36 | let circles = d3.select(this.chartArea).selectAll('circle').data(this.props.data);
37 |
38 | // Use the .enter() method to get your entering elements, and assign their positions
39 | circles.enter().append('circle')
40 | .merge(circles)
41 | .attr('r', (d) => this.props.radius)
42 | .attr('fill', (d) => this.props.color)
43 | .attr('label', (d) => d.label)
44 | .on('mouseover', tip.show)
45 | .on('mouseout', tip.hide)
46 | .style('fill-opacity', 0.3)
47 | .transition().duration(500)
48 | .attr('cx', (d) => this.xScale(d.x))
49 | .attr('cy', (d) => this.yScale(d.y))
50 | .style('stroke', "black")
51 | .style('stroke-width', (d) => d.selected == true ? "3px" : "0px")
52 |
53 |
54 | // Use the .exit() and .remove() methods to remove elements that are no longer in the data
55 | circles.exit().remove();
56 |
57 | // Add hovers using the d3-tip library
58 | d3.select(this.chartArea).call(tip);
59 | }
60 | updateAxes() {
61 | let xAxisFunction = d3.axisBottom()
62 | .scale(this.xScale)
63 | .ticks(5, 's');
64 |
65 | let yAxisFunction = d3.axisLeft()
66 | .scale(this.yScale)
67 | .ticks(5, 's');
68 |
69 | d3.select(this.xAxis)
70 | .call(xAxisFunction);
71 |
72 | d3.select(this.yAxis)
73 | .call(yAxisFunction);
74 | }
75 | update() {
76 | this.updateScales();
77 | this.updateAxes();
78 | this.updatePoints();
79 | }
80 | render() {
81 | return (
82 |
83 |
84 | {this.props.title}
85 | { this.chartArea = node; }}
86 | transform={`translate(${this.props.margin.left}, ${this.props.margin.top})`} />
87 |
88 | {/* Axes */}
89 | { this.xAxis = node; }}
90 | transform={`translate(${this.props.margin.left}, ${this.props.height - this.props.margin.bottom})`}>
91 | { this.yAxis = node; }}
92 | transform={`translate(${this.props.margin.left}, ${this.props.margin.top})`}>
93 |
94 | {/* Axis labels */}
95 | {this.props.xTitle}
97 |
98 | {this.props.yTitle}
100 |
101 |
102 |
103 | )
104 | }
105 | }
106 |
107 | ScatterPlot.defaultProps = {
108 | data: [{ x: 10, y: 20 }, { x: 15, y: 35 }],
109 | width: 300,
110 | height: 300,
111 | radius: 5,
112 | color: "blue",
113 | margin: {
114 | left: 50,
115 | right: 10,
116 | top: 20,
117 | bottom: 50
118 | },
119 | xTitle: "X Title",
120 | yTitle: "Y Title",
121 | };
--------------------------------------------------------------------------------
/10-lifting-up-state-exercise/README.md:
--------------------------------------------------------------------------------
1 | # 10-lifting-up-state-exercise
2 |
3 | In this exercise, you'll follow the instructions below to **lift up state** in a React application, adding interactivity to the following charts:
4 |
5 | 
6 |
7 | In doing so, you'll use **pass functions as props** to your React components. See the `*_solution.js` files for solutions.
8 |
9 | ## Instructions
10 | The majority of the code has been written for this exercise. The core challenge of this exercise is to figure out how to _pass functions as props_ so that you can _lift up state_. More specifically, you'll be able to **hover** on your scatterplot to change the selected county, or **click** on a row of your table to change the `y` axis. See below for the changes to make in each file.
11 |
12 | ### `` Component
13 | All changes to your application will be tracked in the **state** of your `` component. This component will need methods to update its state (i.e., `this.updateXvar()`) so that they can be easily _passed to its child components_. Importantly, you'll need to **bind the scope** to each of these methods in the `constructor()` method. To achieve this, follow these steps:
14 |
15 | - Create a new method `updateXvar()` for your component. It should take in a parameter (a _string_ of the new X variable to display), then update the state of `xVar` given that target's value.
16 | - Use the `updateXvar()` method in your `` element for your X variable. You should still use an **anonymous arrow function** as the callback, in which you pass a parmaeter (i.e., `d`) to your `this.updateXvar()` function.
17 | - Pass a prop `update` to your `` component: this property should be an **anonymous function** in which you call your `this.updateXvar()` method.
18 |
19 | Then, **repeat the above steps** for a method to select the _selected location_ shown in the bar chart. This method should control the state of the selected location (`this.state.selected`) by the _id_ of each point.
20 |
21 | ### `` Component
22 | Your `` componet will accept as a prop an `update()` function. To use this to _lift up state_, do the following:
23 |
24 | - Assign a `mouseover()` event to each bar that passes the value of the data point to the `this.props.update()` function.
25 |
26 |
27 | ### `` Component
28 | Your `` componet will accept as a prop an `update()` function. To use this to _lift up state_, do the following:
29 |
30 | - Assign a `mouseover()` event to each circle that passes the `id` of the data point to the `this.props.update()` function.
--------------------------------------------------------------------------------
/10-lifting-up-state-exercise/css/styles.css:
--------------------------------------------------------------------------------
1 | /* @import url("https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"); */
2 | @import url("../../lib/bootstrap.min.css");
3 |
4 | /* Header */
5 | .container {
6 | text-align: center;
7 | }
8 |
9 | .lead {
10 | margin-bottom:0px;
11 | }
12 |
13 | .jumbotron {
14 | padding:1rem 1rem;
15 | margin-bottom:20px;
16 | }
17 |
18 | /* Controls */
19 | .custom-select {
20 | width:inherit;
21 | }
22 |
23 | .control-wrapper {
24 | display: inline-block;
25 | margin-left:10px;
26 | }
27 |
28 | .control-wrapper input[type="range"] {
29 | vertical-align: text-bottom;;
30 | }
31 |
32 | .control-container {
33 | border-bottom:1px solid #d3d3d3;
34 | margin-bottom:10px;
35 | padding-bottom:10px;
36 | }
37 |
38 | /* Scatter plot */
39 | circle {
40 | cursor:pointer;
41 | }
42 | .axis-label {
43 | text-anchor:middle;
44 | font-size:10px;
45 | }
46 |
47 | .chart-wrapper {
48 | display: inline-block;
49 | }
50 |
51 |
52 | /* Tooltips */
53 | .d3-tip {
54 | line-height: 1;
55 | font-weight: bold;
56 | padding: 12px;
57 | background: rgba(0, 0, 0, 0.8);
58 | color: #fff;
59 | border-radius: 2px;
60 | pointer-events: none;
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/10-lifting-up-state-exercise/data/.Rhistory:
--------------------------------------------------------------------------------
1 | library(dplyr)
2 | library(ggplot2)
3 | View(midwest)
4 | dim(midwest)
5 | ?midwest
6 | levels(midwest$state)
7 | unique(midwest$state)
8 | styler:::style_active_file()
9 | prepped <- midwest %>%
10 | mutate(
11 | state = replace(state, state == "IL", "Illinois"),
12 | state = replace(state, state == "IN", "Indiana"),
13 | state = replace(state, state == "MI", "Michigan"),
14 | state = replace(state, state == "OH", "Ohio"),
15 | state = replace(state, state == "WI", "Wisconsin")
16 | )
17 | View(prepped)
18 | library(tools)
19 | prepped <- midwest %>%
20 | mutate(
21 | state = replace(state, state == "IL", "Illinois"),
22 | state = replace(state, state == "IN", "Indiana"),
23 | state = replace(state, state == "MI", "Michigan"),
24 | state = replace(state, state == "OH", "Ohio"),
25 | state = replace(state, state == "WI", "Wisconsin"),
26 | county = toTitleCase(county)
27 | ) %>%
28 | select(county, state, %in% "pop")
29 | prepped <- midwest %>%
30 | mutate(
31 | state = replace(state, state == "IL", "Illinois"),
32 | state = replace(state, state == "IN", "Indiana"),
33 | state = replace(state, state == "MI", "Michigan"),
34 | state = replace(state, state == "OH", "Ohio"),
35 | state = replace(state, state == "WI", "Wisconsin"),
36 | county = toTitleCase(county)
37 | ) %>%
38 | select(county, state, inmetro, contains("per"))
39 | View(prepped)
40 | ?perchsd
41 | ?midwest
42 | prepped <- midwest %>%
43 | mutate(
44 | state = replace(state, state == "IL", "Illinois"),
45 | state = replace(state, state == "IN", "Indiana"),
46 | state = replace(state, state == "MI", "Michigan"),
47 | state = replace(state, state == "OH", "Ohio"),
48 | state = replace(state, state == "WI", "Wisconsin"),
49 | county = toTitleCase(county)
50 | ) %>%
51 | select(county, state, inmetro, contains("per"), -perchsd)
52 | midwest %>%
53 | mutate(
54 | state = replace(state, state == "IL", "Illinois"),
55 | state = replace(state, state == "IN", "Indiana"),
56 | state = replace(state, state == "MI", "Michigan"),
57 | state = replace(state, state == "OH", "Ohio"),
58 | state = replace(state, state == "WI", "Wisconsin"),
59 | county = toTitleCase(county)
60 | )
61 | toTitleCase(midwest$county)
62 | library(stringr)
63 | prepped <- midwest %>%
64 | mutate(
65 | state = replace(state, state == "IL", "Illinois"),
66 | state = replace(state, state == "IN", "Indiana"),
67 | state = replace(state, state == "MI", "Michigan"),
68 | state = replace(state, state == "OH", "Ohio"),
69 | state = replace(state, state == "WI", "Wisconsin"),
70 | county = str_to_title(county)
71 | ) %>%
72 | select(county, state, inmetro, contains("per"), -perchsd)
73 | setwd("~/Documents/react-d3/demo/data")
74 | # Data prep
75 | # Use built in `midwest` data from the `ggplot2` package
76 | library(ggplot2)
77 | library(dplyr)
78 | library(stringr)
79 | # Replace values with full state names
80 | prepped <- midwest %>%
81 | mutate(
82 | state = replace(state, state == "IL", "Illinois"),
83 | state = replace(state, state == "IN", "Indiana"),
84 | state = replace(state, state == "MI", "Michigan"),
85 | state = replace(state, state == "OH", "Ohio"),
86 | state = replace(state, state == "WI", "Wisconsin"),
87 | county = str_to_title(county)
88 | ) %>%
89 | select(county, state, inmetro, contains("per"), -perchsd)
90 | # Write data
91 | write.csv("midwest.csv")
92 | # Data prep
93 | # Use built in `midwest` data from the `ggplot2` package
94 | library(ggplot2)
95 | library(dplyr)
96 | library(stringr)
97 | # Replace values with full state names
98 | prepped <- midwest %>%
99 | mutate(
100 | state = replace(state, state == "IL", "Illinois"),
101 | state = replace(state, state == "IN", "Indiana"),
102 | state = replace(state, state == "MI", "Michigan"),
103 | state = replace(state, state == "OH", "Ohio"),
104 | state = replace(state, state == "WI", "Wisconsin"),
105 | county = str_to_title(county)
106 | ) %>%
107 | select(county, state, inmetro, contains("per"), -perchsd)
108 | # Write data
109 | write.csv(prepped, "midwest.csv", row.names = F)
110 |
--------------------------------------------------------------------------------
/10-lifting-up-state-exercise/data/prep_data.R:
--------------------------------------------------------------------------------
1 | # Data prep
2 |
3 | # Use built in `midwest` data from the `ggplot2` package
4 | library(ggplot2)
5 | library(dplyr)
6 | library(stringr)
7 |
8 | # Replace values with full state names
9 | prepped <- midwest %>%
10 | mutate(
11 | state = replace(state, state == "IL", "Illinois"),
12 | state = replace(state, state == "IN", "Indiana"),
13 | state = replace(state, state == "MI", "Michigan"),
14 | state = replace(state, state == "OH", "Ohio"),
15 | state = replace(state, state == "WI", "Wisconsin"),
16 | county = str_to_title(county)
17 | ) %>%
18 | select(county, state, inmetro, contains("per"), -perchsd)
19 |
20 | # Write data
21 | write.csv(prepped, "midwest.csv", row.names = F)
--------------------------------------------------------------------------------
/10-lifting-up-state-exercise/img/complete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkfreeman/react-d3/47f9cfbd783c059ff752b0e3cd2bce90cccf816a/10-lifting-up-state-exercise/img/complete.png
--------------------------------------------------------------------------------
/10-lifting-up-state-exercise/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 10-lifting-up-state-exercise
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
10-lifting-up-state-exercise
42 |
Adding interactivity across charts
43 |
(all exercises)
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/10-lifting-up-state-exercise/js/App.js:
--------------------------------------------------------------------------------
1 | // Application file
2 | class App extends React.Component {
3 | constructor(props) {
4 | super(props);
5 |
6 | // Bind `this` to each update function
7 |
8 |
9 | // Set initial state
10 | this.state = {
11 | data: [],
12 | xVar: "percollege",
13 | yVar: "percbelowpoverty",
14 | search: '',
15 | radius: 3,
16 | color: '#081AFF',
17 | selected: 1
18 | };
19 | }
20 | componentDidMount() {
21 | // Load data when the component mounts
22 | d3.csv("data/midwest.csv", (err, data) => {
23 | this.setState({ data: data });
24 | });
25 | }
26 |
27 | // Add method to update the x variable
28 |
29 |
30 | // Add method to update the selected county for the bar chart
31 |
32 | render() {
33 | // Get list of possible x and y variables
34 | let options = this.state.data.length === 0 ? [] : Object.keys(this.state.data[0]);
35 | options = options.filter((d) => d != "county" && d != "state");
36 |
37 | // Store all of the data to be plotted
38 | let allData = this.state.data.map((d, i) => {
39 | return {
40 | x: d[this.state.xVar],
41 | y: d[this.state.yVar],
42 | id: i,
43 | label: d.county + ", " + d.state,
44 | group: d.state,
45 | selected: d.county.toLowerCase().match(this.state.search.toLowerCase()) != null && this.state.search !== ''
46 | };
47 | });
48 |
49 | // Store data for the barchart
50 | let barObservation = this.state.data.filter((d, i) => {
51 | return i === this.state.selected;
52 | })[0] || {};
53 |
54 | let barData = Object.keys(barObservation)
55 | .filter((d) => !isNaN(+barObservation[d]))
56 | .map((d) => {
57 | return {
58 | label: d,
59 | value: +barObservation[d]
60 | }
61 | });
62 |
63 | // Barchart title
64 | let barTitle = barObservation.county + ", " + barObservation.state
65 | return (
66 |
67 |
68 |
69 | {/* X Variable Select Menu */}
70 |
71 | X Variable:
72 |
73 | {options.map((d) => {
74 | return {d}
75 | })}
76 |
77 |
78 |
79 | {/* Y Variable Select Menu */}
80 |
81 | Y Variable:
82 | this.setState({ yVar: d.target.value })}>
83 | {options.map((d) => {
84 | return {d}
85 | })}
86 |
87 |
88 |
89 | {/* Radius Slider */}
90 |
91 | Radius:
92 | this.setState({ radius: d.target.value })} />
93 |
94 |
95 | {/* Color Picker */}
96 |
97 | Color:
98 | this.setState({ color: d.target.value })} />
99 |
100 |
101 | {/* Search Input */}
102 |
103 | this.setState({ search: d.target.value })} />
104 |
105 |
106 |
107 | {/* Render scatter plots */}
108 |
116 |
117 |
118 | {/* Render bar chart */}
119 |
127 |
128 |
129 | )
130 | }
131 | }
132 |
133 | // Render application
134 | ReactDOM.render(
135 | ,
136 | document.getElementById('root')
137 | );
--------------------------------------------------------------------------------
/10-lifting-up-state-exercise/js/App_solution.js:
--------------------------------------------------------------------------------
1 | // Application file
2 | class App extends React.Component {
3 | constructor(props) {
4 | super(props);
5 |
6 | // Set initial state
7 | this.state = {
8 | data: [],
9 | xVar: "percollege",
10 | yVar: "percbelowpoverty",
11 | search: '',
12 | radius: 3,
13 | color: '#081AFF',
14 | selected: 1
15 | };
16 | }
17 | componentDidMount() {
18 | // Load data when the component mounts
19 | d3.csv("data/midwest.csv", (err, data) => {
20 | this.setState({ data: data });
21 | });
22 | }
23 |
24 | // Add method to update the x variable
25 | updateXvar(d) {
26 | this.setState({ xVar: d})
27 | }
28 |
29 | // Add method to update the selected county for the bar chart
30 | updateSelected(d) {
31 | console.log('update!')
32 | this.setState({ selected: d})
33 | }
34 | render() {
35 | // Get list of possible x and y variables
36 | let options = this.state.data.length === 0 ? [] : Object.keys(this.state.data[0]);
37 | options = options.filter((d) => d != "county" && d != "state");
38 |
39 | // Store all of the data to be plotted
40 | let allData = this.state.data.map((d, i) => {
41 | return {
42 | x: d[this.state.xVar],
43 | y: d[this.state.yVar],
44 | id: i,
45 | label: d.county + ", " + d.state,
46 | group: d.state,
47 | selected: d.county.toLowerCase().match(this.state.search.toLowerCase()) != null && this.state.search !== ''
48 | };
49 | });
50 |
51 | // Store data for the barchart
52 | let barObservation = this.state.data.filter((d, i) => {
53 | return i === this.state.selected;
54 | })[0] || {};
55 |
56 | let barData = Object.keys(barObservation)
57 | .filter((d) => !isNaN(+barObservation[d]))
58 | .map((d) => {
59 | return {
60 | label: d,
61 | value: +barObservation[d]
62 | }
63 | });
64 |
65 | // Barchart title
66 | let barTitle = barObservation.county + ", " + barObservation.state
67 | return (
68 |
69 |
70 |
71 | {/* X Variable Select Menu */}
72 |
73 | X Variable:
74 | this.updateXvar(d.target.value)}>
75 | {options.map((d) => {
76 | return {d}
77 | })}
78 |
79 |
80 |
81 | {/* Y Variable Select Menu */}
82 |
83 | Y Variable:
84 | this.setState({ yVar: d.target.value })}>
85 | {options.map((d) => {
86 | return {d}
87 | })}
88 |
89 |
90 |
91 | {/* Radius Slider */}
92 |
93 | Radius:
94 | this.setState({ radius: d.target.value })} />
95 |
96 |
97 | {/* Color Picker */}
98 |
99 | Color:
100 | this.setState({ color: d.target.value })} />
101 |
102 |
103 | {/* Search Input */}
104 |
105 | this.setState({ search: d.target.value })} />
106 |
107 |
108 |
109 | {/* Render scatter plots */}
110 |
this.updateSelected(d)}
118 | />
119 |
120 |
121 | {/* Render bar chart */}
122 | this.updateXvar(d)}
130 | />
131 |
132 |
133 | )
134 | }
135 | }
136 |
137 | // Render application
138 | ReactDOM.render(
139 | ,
140 | document.getElementById('root')
141 | );
--------------------------------------------------------------------------------
/10-lifting-up-state-exercise/js/BarChart.js:
--------------------------------------------------------------------------------
1 | // Scatterplot
2 | class BarChart extends React.Component {
3 | constructor(props) {
4 | super(props);
5 | // Graph width and height - accounting for margins
6 | this.drawWidth = this.props.width - this.props.margin.left - this.props.margin.right;
7 | this.drawHeight = this.props.height - this.props.margin.top - this.props.margin.bottom;
8 |
9 | }
10 | componentDidMount() {
11 | this.update();
12 | }
13 | // Whenever the component updates, select the from the DOM, and use D3 to manipulte circles
14 | componentDidUpdate() {
15 | this.update();
16 | }
17 | updateScales() {
18 | // Calculate limits
19 | let xMin = d3.min(this.props.data, (d) => +d.value * .9);
20 | let xMax = d3.max(this.props.data, (d) => +d.value * 1.1);
21 |
22 | // Define scales
23 | this.xScale = d3.scaleLinear().domain([xMin, xMax]).range([0, this.drawWidth]);
24 | this.yScale = d3.scaleBand().rangeRound([0, this.drawHeight]).padding(0.1).domain(this.props.data.map((d) => d.label));
25 | }
26 | updatePoints() {
27 | // Define hovers
28 | // Add tip
29 | let tip = d3.tip().attr('class', 'd3-tip').html(function (d) {
30 | return d.label;
31 | });
32 |
33 | // Select all rects and bind data
34 | let rects = d3.select(this.chartArea).selectAll('rect').data(this.props.data);
35 |
36 | // Use the .enter() method to get your entering elements, and assign their positions
37 | rects.enter().append('rect')
38 | .merge(rects)
39 | .attr('fill', (d) => this.props.color)
40 | .attr('label', (d) => d.label)
41 | .style('fill-opacity', 0.3)
42 | .attr('width', (d) => this.xScale(d.value))
43 | .attr('height', this.yScale.bandwidth())
44 | .attr('y', (d) => this.yScale(d.label));
45 |
46 | // Use the .exit() and .remove() methods to remove elements that are no longer in the data
47 | rects.exit().remove();
48 |
49 | // Add hovers using the d3-tip library
50 | d3.select(this.chartArea).call(tip);
51 | }
52 | updateAxes() {
53 | let xAxisFunction = d3.axisBottom()
54 | .scale(this.xScale)
55 | .ticks(5, 's');
56 |
57 | let yAxisFunction = d3.axisLeft()
58 | .scale(this.yScale)
59 | .ticks(5, 's');
60 |
61 | d3.select(this.xAxis)
62 | .call(xAxisFunction);
63 |
64 | d3.select(this.yAxis)
65 | .call(yAxisFunction);
66 | }
67 | update() {
68 | this.updateScales();
69 | this.updateAxes();
70 | this.updatePoints();
71 | }
72 | render() {
73 | return (
74 |
75 |
76 | {this.props.title}
77 | { this.chartArea = node; }}
78 | transform={`translate(${this.props.margin.left}, ${this.props.margin.top})`} />
79 |
80 | {/* Axes */}
81 | { this.xAxis = node; }}
82 | transform={`translate(${this.props.margin.left}, ${this.props.height - this.props.margin.bottom})`}>
83 | { this.yAxis = node; }}
84 | transform={`translate(${this.props.margin.left}, ${this.props.margin.top})`}>
85 |
86 | {/* Axis labels */}
87 | {this.props.xTitle}
89 |
90 |
91 |
92 | )
93 | }
94 | }
95 |
96 | BarChart.defaultProps = {
97 | data: [{ x: 10, y: 20 }, { x: 15, y: 35 }],
98 | width: 300,
99 | height: 300,
100 | radius: 5,
101 | color: "blue",
102 | margin: {
103 | left: 120,
104 | right: 10,
105 | top: 20,
106 | bottom: 50
107 | },
108 | xTitle: "X Title",
109 | yTitle: "Y Title",
110 | };
--------------------------------------------------------------------------------
/10-lifting-up-state-exercise/js/BarChart_solution.js:
--------------------------------------------------------------------------------
1 | // Scatterplot
2 | class BarChart extends React.Component {
3 | constructor(props) {
4 | super(props);
5 | // Graph width and height - accounting for margins
6 | this.drawWidth = this.props.width - this.props.margin.left - this.props.margin.right;
7 | this.drawHeight = this.props.height - this.props.margin.top - this.props.margin.bottom;
8 |
9 | }
10 | componentDidMount() {
11 | this.update();
12 | }
13 | // Whenever the component updates, select the from the DOM, and use D3 to manipulte circles
14 | componentDidUpdate() {
15 | this.update();
16 | }
17 | updateScales() {
18 | // Calculate limits
19 | let xMin = d3.min(this.props.data, (d) => +d.value * .9);
20 | let xMax = d3.max(this.props.data, (d) => +d.value * 1.1);
21 |
22 | // Define scales
23 | this.xScale = d3.scaleLinear().domain([xMin, xMax]).range([0, this.drawWidth]);
24 | this.yScale = d3.scaleBand().rangeRound([0, this.drawHeight]).padding(0.1).domain(this.props.data.map((d) => d.label));
25 | }
26 | updatePoints() {
27 | // Define hovers
28 | // Add tip
29 | let tip = d3.tip().attr('class', 'd3-tip').html(function (d) {
30 | return d.label;
31 | });
32 |
33 | // Select all rects and bind data
34 | let rects = d3.select(this.chartArea).selectAll('rect').data(this.props.data);
35 |
36 | // Use the .enter() method to get your entering elements, and assign their positions
37 | rects.enter().append('rect')
38 | .on('mouseover', (d) => {
39 | this.props.update(d.label)
40 | })
41 | .merge(rects)
42 | .attr('fill', (d) => this.props.color)
43 | .attr('label', (d) => d.label)
44 | .style('fill-opacity', 0.3)
45 | .attr('width', (d) => this.xScale(d.value))
46 | .attr('height', this.yScale.bandwidth())
47 | .attr('y', (d) => this.yScale(d.label));
48 |
49 | // Use the .exit() and .remove() methods to remove elements that are no longer in the data
50 | rects.exit().remove();
51 |
52 | // Add hovers using the d3-tip library
53 | d3.select(this.chartArea).call(tip);
54 | }
55 | updateAxes() {
56 | let xAxisFunction = d3.axisBottom()
57 | .scale(this.xScale)
58 | .ticks(5, 's');
59 |
60 | let yAxisFunction = d3.axisLeft()
61 | .scale(this.yScale)
62 | .ticks(5, 's');
63 |
64 | d3.select(this.xAxis)
65 | .call(xAxisFunction);
66 |
67 | d3.select(this.yAxis)
68 | .call(yAxisFunction);
69 | }
70 | update() {
71 | this.updateScales();
72 | this.updateAxes();
73 | this.updatePoints();
74 | }
75 | render() {
76 | return (
77 |
78 |
79 | {this.props.title}
80 | { this.chartArea = node; }}
81 | transform={`translate(${this.props.margin.left}, ${this.props.margin.top})`} />
82 |
83 | {/* Axes */}
84 | { this.xAxis = node; }}
85 | transform={`translate(${this.props.margin.left}, ${this.props.height - this.props.margin.bottom})`}>
86 | { this.yAxis = node; }}
87 | transform={`translate(${this.props.margin.left}, ${this.props.margin.top})`}>
88 |
89 | {/* Axis labels */}
90 | {this.props.xTitle}
92 |
93 |
94 |
95 | )
96 | }
97 | }
98 |
99 | BarChart.defaultProps = {
100 | data: [{ x: 10, y: 20 }, { x: 15, y: 35 }],
101 | width: 300,
102 | height: 300,
103 | radius: 5,
104 | color: "blue",
105 | margin: {
106 | left: 120,
107 | right: 10,
108 | top: 20,
109 | bottom: 50
110 | },
111 | xTitle: "X Title",
112 | yTitle: "Y Title",
113 | };
--------------------------------------------------------------------------------
/10-lifting-up-state-exercise/js/ScatterPlot.js:
--------------------------------------------------------------------------------
1 | // Scatterplot
2 | class ScatterPlot extends React.Component {
3 | constructor(props) {
4 | super(props);
5 | // Graph width and height - accounting for margins
6 | this.drawWidth = this.props.width - this.props.margin.left - this.props.margin.right;
7 | this.drawHeight = this.props.height - this.props.margin.top - this.props.margin.bottom;
8 |
9 | }
10 | componentDidMount() {
11 | this.update();
12 | }
13 | // Whenever the component updates, select the from the DOM, and use D3 to manipulte circles
14 | componentDidUpdate() {
15 | this.update();
16 | }
17 | updateScales() {
18 | // Calculate limits
19 | let xMin = d3.min(this.props.data, (d) => +d.x * .9);
20 | let xMax = d3.max(this.props.data, (d) => +d.x * 1.1);
21 | let yMin = d3.min(this.props.data, (d) => +d.y * .9);
22 | let yMax = d3.max(this.props.data, (d) => +d.y * 1.1);
23 |
24 | // Define scales
25 | this.xScale = d3.scaleLinear().domain([xMin, xMax]).range([0, this.drawWidth])
26 | this.yScale = d3.scaleLinear().domain([yMax, yMin]).range([0, this.drawHeight])
27 | }
28 | updatePoints() {
29 | // Define hovers
30 | // Add tip
31 | let tip = d3.tip().attr('class', 'd3-tip').html(function (d) {
32 | return d.label;
33 | });
34 |
35 | // Select all circles and bind data
36 | let circles = d3.select(this.chartArea).selectAll('circle').data(this.props.data);
37 |
38 | // Use the .enter() method to get your entering elements, and assign their positions
39 | circles.enter().append('circle')
40 | .merge(circles)
41 | .attr('r', (d) => this.props.radius)
42 | .attr('fill', (d) => this.props.color)
43 | .attr('label', (d) => d.label)
44 | .style('fill-opacity', 0.3)
45 | .transition().duration(500)
46 | .attr('cx', (d) => this.xScale(d.x))
47 | .attr('cy', (d) => this.yScale(d.y))
48 | .style('stroke', "black")
49 | .style('stroke-width', (d) => d.selected == true ? "3px" : "0px")
50 |
51 |
52 | // Use the .exit() and .remove() methods to remove elements that are no longer in the data
53 | circles.exit().remove();
54 |
55 | // Add hovers using the d3-tip library
56 | d3.select(this.chartArea).call(tip);
57 | }
58 | updateAxes() {
59 | let xAxisFunction = d3.axisBottom()
60 | .scale(this.xScale)
61 | .ticks(5, 's');
62 |
63 | let yAxisFunction = d3.axisLeft()
64 | .scale(this.yScale)
65 | .ticks(5, 's');
66 |
67 | d3.select(this.xAxis)
68 | .call(xAxisFunction);
69 |
70 | d3.select(this.yAxis)
71 | .call(yAxisFunction);
72 | }
73 | update() {
74 | this.updateScales();
75 | this.updateAxes();
76 | this.updatePoints();
77 | }
78 | render() {
79 | return (
80 |
81 |
82 | {this.props.title}
83 | { this.chartArea = node; }}
84 | transform={`translate(${this.props.margin.left}, ${this.props.margin.top})`} />
85 |
86 | {/* Axes */}
87 | { this.xAxis = node; }}
88 | transform={`translate(${this.props.margin.left}, ${this.props.height - this.props.margin.bottom})`}>
89 | { this.yAxis = node; }}
90 | transform={`translate(${this.props.margin.left}, ${this.props.margin.top})`}>
91 |
92 | {/* Axis labels */}
93 | {this.props.xTitle}
95 |
96 | {this.props.yTitle}
98 |
99 |
100 |
101 | )
102 | }
103 | }
104 |
105 | ScatterPlot.defaultProps = {
106 | data: [{ x: 10, y: 20 }, { x: 15, y: 35 }],
107 | width: 300,
108 | height: 300,
109 | radius: 5,
110 | color: "blue",
111 | margin: {
112 | left: 50,
113 | right: 10,
114 | top: 20,
115 | bottom: 50
116 | },
117 | xTitle: "X Title",
118 | yTitle: "Y Title",
119 | };
--------------------------------------------------------------------------------
/10-lifting-up-state-exercise/js/ScatterPlot_solution.js:
--------------------------------------------------------------------------------
1 | // Scatterplot
2 | class ScatterPlot extends React.Component {
3 | constructor(props) {
4 | super(props);
5 | // Graph width and height - accounting for margins
6 | this.drawWidth = this.props.width - this.props.margin.left - this.props.margin.right;
7 | this.drawHeight = this.props.height - this.props.margin.top - this.props.margin.bottom;
8 |
9 | }
10 | componentDidMount() {
11 | this.update();
12 | }
13 | // Whenever the component updates, select the from the DOM, and use D3 to manipulte circles
14 | componentDidUpdate() {
15 | this.update();
16 | }
17 | updateScales() {
18 | // Calculate limits
19 | let xMin = d3.min(this.props.data, (d) => +d.x * .9);
20 | let xMax = d3.max(this.props.data, (d) => +d.x * 1.1);
21 | let yMin = d3.min(this.props.data, (d) => +d.y * .9);
22 | let yMax = d3.max(this.props.data, (d) => +d.y * 1.1);
23 |
24 | // Define scales
25 | this.xScale = d3.scaleLinear().domain([xMin, xMax]).range([0, this.drawWidth])
26 | this.yScale = d3.scaleLinear().domain([yMax, yMin]).range([0, this.drawHeight])
27 | }
28 | updatePoints() {
29 | // Define hovers
30 | // Add tip
31 | let tip = d3.tip().attr('class', 'd3-tip').html(function (d) {
32 | return d.label;
33 | });
34 |
35 | // Select all circles and bind data
36 | let circles = d3.select(this.chartArea).selectAll('circle').data(this.props.data);
37 |
38 | // Use the .enter() method to get your entering elements, and assign their positions
39 | circles.enter().append('circle')
40 | .merge(circles)
41 | .attr('r', (d) => this.props.radius)
42 | .attr('fill', (d) => this.props.color)
43 | .attr('label', (d) => d.label)
44 | .on('mouseover', (d) => this.props.update(d.id))
45 | .style('fill-opacity', 0.3)
46 | .transition().duration(500)
47 | .attr('cx', (d) => this.xScale(d.x))
48 | .attr('cy', (d) => this.yScale(d.y))
49 | .style('stroke', "black")
50 | .style('stroke-width', (d) => d.selected == true ? "3px" : "0px")
51 |
52 |
53 | // Use the .exit() and .remove() methods to remove elements that are no longer in the data
54 | circles.exit().remove();
55 |
56 | // Add hovers using the d3-tip library
57 | d3.select(this.chartArea).call(tip);
58 | }
59 | updateAxes() {
60 | let xAxisFunction = d3.axisBottom()
61 | .scale(this.xScale)
62 | .ticks(5, 's');
63 |
64 | let yAxisFunction = d3.axisLeft()
65 | .scale(this.yScale)
66 | .ticks(5, 's');
67 |
68 | d3.select(this.xAxis)
69 | .call(xAxisFunction);
70 |
71 | d3.select(this.yAxis)
72 | .call(yAxisFunction);
73 | }
74 | update() {
75 | this.updateScales();
76 | this.updateAxes();
77 | this.updatePoints();
78 | }
79 | render() {
80 | return (
81 |
82 |
83 | {this.props.title}
84 | { this.chartArea = node; }}
85 | transform={`translate(${this.props.margin.left}, ${this.props.margin.top})`} />
86 |
87 | {/* Axes */}
88 | { this.xAxis = node; }}
89 | transform={`translate(${this.props.margin.left}, ${this.props.height - this.props.margin.bottom})`}>
90 | { this.yAxis = node; }}
91 | transform={`translate(${this.props.margin.left}, ${this.props.margin.top})`}>
92 |
93 | {/* Axis labels */}
94 | {this.props.xTitle}
96 |
97 | {this.props.yTitle}
99 |
100 |
101 |
102 | )
103 | }
104 | }
105 |
106 | ScatterPlot.defaultProps = {
107 | data: [{ x: 10, y: 20 }, { x: 15, y: 35 }],
108 | width: 300,
109 | height: 300,
110 | radius: 5,
111 | color: "blue",
112 | margin: {
113 | left: 50,
114 | right: 10,
115 | top: 20,
116 | bottom: 50
117 | },
118 | xTitle: "X Title",
119 | yTitle: "Y Title",
120 | };
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Michael Freeman
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React for D3 Users
2 | These resources are designed to teach D3 users how to scaffold their projects using React. To see the hosted versions of these demos/exercises, visit [this page](http://mfviz.com/react-d3). **Demos** are completed demonstrations of certain techniques, while **exercises** contain instructions for building a particular page (a `*_solution.js` file is also included). See accompanying slides [here](https://docs.google.com/presentation/d/1x2Wz56-3FfZpwi5gEuppMWQmotj1MoTYXCqbkKm4aAc/edit?usp=sharing).
3 |
4 | These resources were developed for a workshop at
5 | OpenvisConf 2018 . Built by Michael Freeman .
6 |
7 | ## Prerequisites
8 | This workshop assumes that you are comfortable building visualizations with D3 and using underlying programming languages (HTML, CSS, and JavaScript). **No prior React knowledge** is assumed or needed. The only technology you will need on your machine is the ability to **run a local server**. Any local server is fine, but in case you don't have one installed, here are a few suggestions:
9 |
10 |
11 | - Use Python's [`SimpleHTTPServer`](https://docs.python.org/2/library/simplehttpserver.html) (for Python 2) or [`http.server`](https://docs.python.org/3/library/http.server.html) (for Python 3)
12 | - The `npm` package [`live-server`](https://www.npmjs.com/package/live-server)
13 | - Download a local server program such as [MAMP](https://www.mamp.info/en/) (Mac) or [WAMP])( (Windows))
14 |
15 | Additionally, you may find it helpful to install the [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) Chrome Extension for debugging React.
16 |
17 | ## Getting Started
18 | To use these materials, you should `fork` and `clone` this repository to your machine (if you don't have a GitHub account, you can simply **download** them to your machine).
19 |
20 | Then, I suggest you start running a local server in the _root_ of the repository to view the set of available exercises and their descriptions ( you can see them online [here](http://mfviz.com/react-d3)). Then, you can **explore** the code for any given repository `*-demo`, or work through the instructions in any exercise (repos with the suffix `-exercise`).
21 |
22 | ## Feedback + Additional Resources
23 | If you like these resources, share them far and wide (credit to [@mf_viz](https://twitter.com/mf_viz) is appreciated)! Feel free to submit a _pull request_ for any errors, or create an _issue_ for something you believe is incorrect.
24 |
25 | For additional resources, see this [online course book](http://info343.github.io/) I co-authored on **Client Side Web Development**.
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | .header {
2 | font-weight: 300;
3 | font-size: 50px;
4 | border-bottom: 1px solid #d3d3d3;
5 | }
6 |
7 | h3 {
8 | font-weight: 300;
9 | font-size:24px;
10 | padding-bottom:0px;
11 | margin-bottom:0px;
12 | }
13 |
14 | p {
15 | opacity:.7;
16 | margin-top:0px;
17 | }
18 |
19 | strong {
20 | font-weight: 700;
21 | }
--------------------------------------------------------------------------------
/data/demos.csv:
--------------------------------------------------------------------------------
1 | title,description
2 | 01-basic-d3-demo,A demonstration of building a scatterplot using D3. This is the level of background participants are expected to have before moving through these materials.
3 | 02-class-demo,This page demonstrates how to leverage Classes in JavaScript.
4 | 03-component-demo,"In this demo, you can see how to create React components that accept information via props."
5 | 04-react-intro-exercise,"In this exercise, participants will walk through creating and rendering React components"
6 | 05-state-demo,This page demonstrates how to keep track of the state of an application.
7 | 06-state-exercise,This exercise challenges you to leverage the state of an application to keep track of information that changes through user interactions.
8 | 07-d3-demo,"Here, you can see how to leverage D3 to interact with components that are created using React."
9 | 08-scatter-exercise,"In this exercise, you will create a fully functional scatterplot using D3 and React in combination with one another"
10 | 09-small-multiples-exercise,"In this final exercise, you will see how simple it is to use React to render small multiples fo D3 visualizations."
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | React + D3
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | These resources are designed to teach D3 users how to scaffold their projects using React. Demos are completed demonstrations of certain techniques, while exercises contain instructions for building a particular page (a *_solution.js
file is also included). These were originally developed for a workshop
52 | at
53 | OpenvisConf 2018 . All code is available
54 | on GitHub . Built by
55 | Michael Freeman . See accompanying slides here
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/lib/d3-tip.js:
--------------------------------------------------------------------------------
1 | // d3.tip
2 | // Copyright (c) 2013 Justin Palmer
3 | //
4 | // Tooltips for d3.js SVG visualizations
5 |
6 | (function (root, factory) {
7 | if (typeof define === 'function' && define.amd) {
8 | // AMD. Register as an anonymous module with d3 as a dependency.
9 | define(['d3'], factory)
10 | } else if (typeof module === 'object' && module.exports) {
11 | // CommonJS
12 | var d3 = require('d3')
13 | module.exports = factory(d3)
14 | } else {
15 | // Browser global.
16 | root.d3.tip = factory(root.d3)
17 | }
18 | }(this, function (d3) {
19 |
20 | // Public - contructs a new tooltip
21 | //
22 | // Returns a tip
23 | return function() {
24 | var direction = d3_tip_direction,
25 | offset = d3_tip_offset,
26 | html = d3_tip_html,
27 | node = initNode(),
28 | svg = null,
29 | point = null,
30 | target = null
31 |
32 | function tip(vis) {
33 | svg = getSVGNode(vis)
34 | point = svg.createSVGPoint()
35 | document.body.appendChild(node)
36 | }
37 |
38 | // Public - show the tooltip on the screen
39 | //
40 | // Returns a tip
41 | tip.show = function() {
42 | var args = Array.prototype.slice.call(arguments)
43 | if(args[args.length - 1] instanceof SVGElement) target = args.pop()
44 |
45 | var content = html.apply(this, args),
46 | poffset = offset.apply(this, args),
47 | dir = direction.apply(this, args),
48 | nodel = getNodeEl(),
49 | i = directions.length,
50 | coords,
51 | scrollTop = document.documentElement.scrollTop || document.body.scrollTop,
52 | scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
53 |
54 | nodel.html(content)
55 | .style('opacity', 1).style('pointer-events', 'all')
56 |
57 | while(i--) nodel.classed(directions[i], false)
58 | coords = direction_callbacks.get(dir).apply(this)
59 | nodel.classed(dir, true)
60 | .style('top', (coords.top + poffset[0]) + scrollTop + 'px')
61 | .style('left', (coords.left + poffset[1]) + scrollLeft + 'px')
62 |
63 | return tip;
64 | };
65 |
66 | // Public - hide the tooltip
67 | //
68 | // Returns a tip
69 | tip.hide = function() {
70 | var nodel = getNodeEl()
71 | nodel.style('opacity', 0).style('pointer-events', 'none')
72 | return tip
73 | }
74 |
75 | // Public: Proxy attr calls to the d3 tip container. Sets or gets attribute value.
76 | //
77 | // n - name of the attribute
78 | // v - value of the attribute
79 | //
80 | // Returns tip or attribute value
81 | tip.attr = function(n, v) {
82 | if (arguments.length < 2 && typeof n === 'string') {
83 | return getNodeEl().attr(n)
84 | } else {
85 | var args = Array.prototype.slice.call(arguments)
86 | d3.selection.prototype.attr.apply(getNodeEl(), args)
87 | }
88 |
89 | return tip
90 | }
91 |
92 | // Public: Proxy style calls to the d3 tip container. Sets or gets a style value.
93 | //
94 | // n - name of the property
95 | // v - value of the property
96 | //
97 | // Returns tip or style property value
98 | tip.style = function(n, v) {
99 | if (arguments.length < 2 && typeof n === 'string') {
100 | return getNodeEl().style(n)
101 | } else {
102 | var args = Array.prototype.slice.call(arguments)
103 | d3.selection.prototype.style.apply(getNodeEl(), args)
104 | }
105 |
106 | return tip
107 | }
108 |
109 | // Public: Set or get the direction of the tooltip
110 | //
111 | // v - One of n(north), s(south), e(east), or w(west), nw(northwest),
112 | // sw(southwest), ne(northeast) or se(southeast)
113 | //
114 | // Returns tip or direction
115 | tip.direction = function(v) {
116 | if (!arguments.length) return direction
117 | direction = v == null ? v : functor(v)
118 |
119 | return tip
120 | }
121 |
122 | // Public: Sets or gets the offset of the tip
123 | //
124 | // v - Array of [x, y] offset
125 | //
126 | // Returns offset or
127 | tip.offset = function(v) {
128 | if (!arguments.length) return offset
129 | offset = v == null ? v : functor(v)
130 |
131 | return tip
132 | }
133 |
134 | // Public: sets or gets the html value of the tooltip
135 | //
136 | // v - String value of the tip
137 | //
138 | // Returns html value or tip
139 | tip.html = function(v) {
140 | if (!arguments.length) return html
141 | html = v == null ? v : functor(v)
142 |
143 | return tip
144 | }
145 |
146 | // Public: destroys the tooltip and removes it from the DOM
147 | //
148 | // Returns a tip
149 | tip.destroy = function() {
150 | if(node) {
151 | getNodeEl().remove();
152 | node = null;
153 | }
154 | return tip;
155 | }
156 |
157 | function d3_tip_direction() { return 'n' }
158 | function d3_tip_offset() { return [0, 0] }
159 | function d3_tip_html() { return ' ' }
160 |
161 | var direction_callbacks = d3.map({
162 | n: direction_n,
163 | s: direction_s,
164 | e: direction_e,
165 | w: direction_w,
166 | nw: direction_nw,
167 | ne: direction_ne,
168 | sw: direction_sw,
169 | se: direction_se
170 | }),
171 |
172 | directions = direction_callbacks.keys()
173 |
174 | function direction_n() {
175 | var bbox = getScreenBBox()
176 | return {
177 | top: bbox.n.y - node.offsetHeight,
178 | left: bbox.n.x - node.offsetWidth / 2
179 | }
180 | }
181 |
182 | function direction_s() {
183 | var bbox = getScreenBBox()
184 | return {
185 | top: bbox.s.y,
186 | left: bbox.s.x - node.offsetWidth / 2
187 | }
188 | }
189 |
190 | function direction_e() {
191 | var bbox = getScreenBBox()
192 | return {
193 | top: bbox.e.y - node.offsetHeight / 2,
194 | left: bbox.e.x
195 | }
196 | }
197 |
198 | function direction_w() {
199 | var bbox = getScreenBBox()
200 | return {
201 | top: bbox.w.y - node.offsetHeight / 2,
202 | left: bbox.w.x - node.offsetWidth
203 | }
204 | }
205 |
206 | function direction_nw() {
207 | var bbox = getScreenBBox()
208 | return {
209 | top: bbox.nw.y - node.offsetHeight,
210 | left: bbox.nw.x - node.offsetWidth
211 | }
212 | }
213 |
214 | function direction_ne() {
215 | var bbox = getScreenBBox()
216 | return {
217 | top: bbox.ne.y - node.offsetHeight,
218 | left: bbox.ne.x
219 | }
220 | }
221 |
222 | function direction_sw() {
223 | var bbox = getScreenBBox()
224 | return {
225 | top: bbox.sw.y,
226 | left: bbox.sw.x - node.offsetWidth
227 | }
228 | }
229 |
230 | function direction_se() {
231 | var bbox = getScreenBBox()
232 | return {
233 | top: bbox.se.y,
234 | left: bbox.e.x
235 | }
236 | }
237 |
238 | function initNode() {
239 | var node = d3.select(document.createElement('div'));
240 | node.style('position', 'absolute').style('top', 0).style('opacity', 0)
241 | .style('pointer-events', 'none').style('box-sizing', 'border-box')
242 |
243 | return node.node()
244 | }
245 |
246 | function getSVGNode(el) {
247 | el = el.node()
248 | if(el.tagName.toLowerCase() === 'svg')
249 | return el
250 |
251 | return el.ownerSVGElement
252 | }
253 |
254 | function getNodeEl() {
255 | if(node === null) {
256 | node = initNode();
257 | // re-add node to DOM
258 | document.body.appendChild(node);
259 | };
260 | return d3.select(node);
261 | }
262 |
263 | // Private - gets the screen coordinates of a shape
264 | //
265 | // Given a shape on the screen, will return an SVGPoint for the directions
266 | // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest),
267 | // sw(southwest).
268 | //
269 | // +-+-+
270 | // | |
271 | // + +
272 | // | |
273 | // +-+-+
274 | //
275 | // Returns an Object {n, s, e, w, nw, sw, ne, se}
276 | function getScreenBBox() {
277 | var targetel = target || d3.event.target;
278 |
279 | while ('undefined' === typeof targetel.getScreenCTM && 'undefined' === targetel.parentNode) {
280 | targetel = targetel.parentNode;
281 | }
282 |
283 | var bbox = {},
284 | matrix = targetel.getScreenCTM(),
285 | tbbox = targetel.getBBox(),
286 | width = tbbox.width,
287 | height = tbbox.height,
288 | x = tbbox.x,
289 | y = tbbox.y
290 |
291 | point.x = x
292 | point.y = y
293 | bbox.nw = point.matrixTransform(matrix)
294 | point.x += width
295 | bbox.ne = point.matrixTransform(matrix)
296 | point.y += height
297 | bbox.se = point.matrixTransform(matrix)
298 | point.x -= width
299 | bbox.sw = point.matrixTransform(matrix)
300 | point.y -= height / 2
301 | bbox.w = point.matrixTransform(matrix)
302 | point.x += width
303 | bbox.e = point.matrixTransform(matrix)
304 | point.x -= width / 2
305 | point.y -= height / 2
306 | bbox.n = point.matrixTransform(matrix)
307 | point.y += height
308 | bbox.s = point.matrixTransform(matrix)
309 |
310 | return bbox
311 | }
312 |
313 | // Private - replace D3JS 3.X d3.functor() function
314 | function functor(v) {
315 | return typeof v === "function" ? v : function() {
316 | return v
317 | }
318 | }
319 |
320 | return tip
321 | };
322 |
323 | }));
324 |
--------------------------------------------------------------------------------
/lib/example-styles.css:
--------------------------------------------------------------------------------
1 | .d3-tip {
2 | line-height: 1;
3 | font-weight: bold;
4 | padding: 12px;
5 | background: rgba(0, 0, 0, 0.8);
6 | color: #fff;
7 | border-radius: 2px;
8 | pointer-events: none;
9 | }
10 |
11 | /* Creates a small triangle extender for the tooltip */
12 | .d3-tip:after {
13 | box-sizing: border-box;
14 | display: inline;
15 | font-size: 10px;
16 | width: 100%;
17 | line-height: 1;
18 | color: rgba(0, 0, 0, 0.8);
19 | position: absolute;
20 | pointer-events: none;
21 | }
22 |
23 | /* Northward tooltips */
24 | .d3-tip.n:after {
25 | content: "\25BC";
26 | margin: -1px 0 0 0;
27 | top: 100%;
28 | left: 0;
29 | text-align: center;
30 | }
31 |
32 | /* Eastward tooltips */
33 | .d3-tip.e:after {
34 | content: "\25C0";
35 | margin: -4px 0 0 0;
36 | top: 50%;
37 | left: -8px;
38 | }
39 |
40 | /* Southward tooltips */
41 | .d3-tip.s:after {
42 | content: "\25B2";
43 | margin: 0 0 1px 0;
44 | top: -8px;
45 | left: 0;
46 | text-align: center;
47 | }
48 |
49 | /* Westward tooltips */
50 | .d3-tip.w:after {
51 | content: "\25B6";
52 | margin: -4px 0 0 -1px;
53 | top: 50%;
54 | left: 100%;
55 | }
56 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | // Data to pass to our List elements
4 | class Demos extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | demos: []
9 | };
10 | }
11 |
12 | componentDidMount() {
13 | d3.csv('data/demos.csv', (data) => {
14 | this.setState({ demos: data });
15 | });
16 | }
17 |
18 | render() {
19 | return (
20 |
21 | {this.state.demos.map(function (d, i) {
22 | return (
23 |
24 |
25 |
{d.description}
26 |
27 |
28 | )
29 | })}
30 |
31 | );
32 | }
33 | }
34 |
35 | // Render your component in the `main` section
36 | ReactDOM.render( ,
37 | document.querySelector('#examples')
38 | );
--------------------------------------------------------------------------------