├── .gitignore
├── README.md
├── animated-bar-graph
├── bartransitions.gif
├── index.html
├── styles.css
└── updating-bars2.js
├── bargraph
├── bargraph.png
├── draw-bars-2.js
├── index.html
└── styles.css
├── linechart
├── chart.js
├── index.html
└── linechart.png
└── scatterplot
├── draw-scatter.js
├── index.html
├── scatterplot.png
└── styles.css
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fullstack-D3-and-Data-Visualization
2 |
3 | Code for completing Amelia Wattenberger's Fullstack D3 book
4 |
5 | 1. Line Chart
6 |
7 |
8 | 2. Scatter Plot
9 |
10 |
11 | 3. Bar Graph
12 |
13 |
14 | 4. Bar Graph With Transitions
15 |
16 |
17 |
--------------------------------------------------------------------------------
/animated-bar-graph/bartransitions.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aligg/Fullstack-D3-and-Data-Visualization/fc42940427f744b439bed4e332063ed1fb3f5a8a/animated-bar-graph/bartransitions.gif
--------------------------------------------------------------------------------
/animated-bar-graph/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | My Updating Chart
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/animated-bar-graph/styles.css:
--------------------------------------------------------------------------------
1 | .bin rect {
2 | fill: cornflowerblue;
3 | }
4 | .bin text {
5 | text-anchor: middle;
6 | fill: darkgrey;
7 | font-size: 12px;
8 | font-family: sans-serif;
9 | }
10 |
11 | .mean {
12 | stroke: maroon;
13 | stroke-dasharray: 2px 4px;
14 | }
15 |
16 | .x-axis-label {
17 | fill: black;
18 | font-size: 1.4em;
19 | text-transform: capitalize;
20 | }
21 |
22 | body {
23 | display: flex;
24 | align-items: center;
25 | justify-content: center;
26 | padding: 2em;
27 | }
28 |
29 | button {
30 | font-size: 1.2em;
31 | margin-left: 1em;
32 | padding: 0.5em 1em;
33 | appearance: none;
34 | -webkit-appearance: none;
35 | background:darkseagreen;
36 | color: white;
37 | border: none;
38 | box-shadow: 0 5px 0 0 seagreen;
39 | border-radius: 6px;
40 | font-weight: 600;
41 | outline: none;
42 | cursor: pointer;
43 | transition: all 0.1s ease-out;
44 | }
45 |
46 | button:hover,
47 | button:focus {
48 | background: #73b173;
49 | box-shadow: 0 4px 0 0 seagreen;
50 | transform: translateY(1px);
51 | }
52 |
53 | button:hover:active,
54 | button:focus:active,
55 | button:active {
56 | box-shadow: 0 1px 0 0 seagreen;
57 | transform: translateY(4px);
58 | }
--------------------------------------------------------------------------------
/animated-bar-graph/updating-bars2.js:
--------------------------------------------------------------------------------
1 | async function drawBars() {
2 | // 1. Access data
3 | const dataset = await d3.json("./../../my_weather_data.json");
4 |
5 | // 2. Create chart dimensions
6 |
7 | const width = 500;
8 | let dimensions = {
9 | width: width,
10 | height: width * 0.6,
11 | margin: {
12 | top: 30,
13 | right: 10,
14 | bottom: 50,
15 | left: 50,
16 | },
17 | };
18 | dimensions.boundedWidth =
19 | dimensions.width - dimensions.margin.left - dimensions.margin.right;
20 | dimensions.boundedHeight =
21 | dimensions.height - dimensions.margin.top - dimensions.margin.bottom;
22 |
23 | // 3. Draw canvas
24 |
25 | const wrapper = d3
26 | .select("#wrapper")
27 | .append("svg")
28 | .attr("width", dimensions.width)
29 | .attr("height", dimensions.height);
30 |
31 | const bounds = wrapper
32 | .append("g")
33 | .style(
34 | "transform",
35 | `translate(${dimensions.margin.left}px, ${dimensions.margin.top}px)`,
36 | );
37 |
38 | // init static elements
39 | bounds.append("g").attr("class", "bins");
40 | bounds.append("line").attr("class", "mean");
41 | bounds
42 | .append("g")
43 | .attr("class", "x-axis")
44 | .style("transform", `translateY(${dimensions.boundedHeight}px)`)
45 | .append("text")
46 | .attr("class", "x-axis-label")
47 | .attr("x", dimensions.boundedWidth / 2)
48 | .attr("y", dimensions.margin.bottom - 10);
49 |
50 | const drawHistogram = (metric) => {
51 | const metricAccessor = (d) => d[metric];
52 | const yAccessor = (d) => d.length;
53 |
54 | // 4. Create scales
55 |
56 | const xScale = d3
57 | .scaleLinear()
58 | .domain(d3.extent(dataset, metricAccessor))
59 | .range([0, dimensions.boundedWidth])
60 | .nice();
61 |
62 | const binsGenerator = d3
63 | .histogram()
64 | .domain(xScale.domain())
65 | .value(metricAccessor)
66 | .thresholds(12);
67 |
68 | const bins = binsGenerator(dataset);
69 |
70 | const yScale = d3
71 | .scaleLinear()
72 | .domain([0, d3.max(bins, yAccessor)])
73 | .range([dimensions.boundedHeight, 0])
74 | .nice();
75 |
76 | // 5. Draw data
77 |
78 | const exitTransition = d3.transition().duration(600);
79 |
80 | const updateTransition = exitTransition.transition().duration(600);
81 |
82 | const barPadding = 1;
83 |
84 | let binGroups = bounds.select(".bins").selectAll(".bin").data(bins);
85 |
86 | const oldBinGroups = binGroups.exit();
87 | oldBinGroups
88 | .selectAll("rect")
89 | .style("fill", "orangered")
90 | .transition(exitTransition)
91 | .attr("y", dimensions.boundedHeight)
92 | .attr("height", 0);
93 |
94 | oldBinGroups
95 | .selectAll("text")
96 | .transition(exitTransition)
97 | .attr("y", dimensions.boundedHeight);
98 |
99 | oldBinGroups.transition(exitTransition).remove();
100 |
101 | const newBinGroups = binGroups.enter().append("g").attr("class", "bin");
102 |
103 | newBinGroups
104 | .append("rect")
105 | .attr("height", 0)
106 | .attr("x", (d) => xScale(d.x0) + barPadding)
107 | .attr("y", dimensions.boundedHeight)
108 | .attr("width", (d) =>
109 | d3.max([0, xScale(d.x1) - xScale(d.x0) - barPadding]),
110 | )
111 | .style("fill", "yellowgreen");
112 |
113 | newBinGroups
114 | .append("text")
115 | .attr("x", (d) => xScale(d.x0) + (xScale(d.x1) - xScale(d.x0)) / 2)
116 | .attr("y", dimensions.boundedHeight);
117 |
118 | // update binGroups to include new points
119 | binGroups = newBinGroups.merge(binGroups);
120 |
121 | const barRects = binGroups
122 | .select("rect")
123 | .transition(updateTransition)
124 | .attr("x", (d) => xScale(d.x0) + barPadding)
125 | .attr("y", (d) => yScale(yAccessor(d)))
126 | .attr(
127 | "height",
128 | (d) => dimensions.boundedHeight - yScale(yAccessor(d)),
129 | )
130 | .attr("width", (d) =>
131 | d3.max([0, xScale(d.x1) - xScale(d.x0) - barPadding]),
132 | )
133 | .transition()
134 | .style("fill", "cornflowerblue");
135 |
136 | const barText = binGroups
137 | .select("text")
138 | .transition(updateTransition)
139 | .attr("x", (d) => xScale(d.x0) + (xScale(d.x1) - xScale(d.x0)) / 2)
140 | .attr("y", (d) => yScale(yAccessor(d)) - 5)
141 | .text((d) => yAccessor(d) || "");
142 |
143 | const mean = d3.mean(dataset, metricAccessor);
144 |
145 | const meanLine = bounds
146 | .selectAll(".mean")
147 | .transition(updateTransition)
148 | .attr("x1", xScale(mean))
149 | .attr("x2", xScale(mean))
150 | .attr("y1", -20)
151 | .attr("y2", dimensions.boundedHeight);
152 |
153 | // 6. Draw peripherals
154 |
155 | const xAxisGenerator = d3.axisBottom().scale(xScale);
156 |
157 | const xAxis = bounds
158 | .select(".x-axis")
159 | .transition(updateTransition)
160 | .call(xAxisGenerator);
161 |
162 | const xAxisLabel = xAxis.select(".x-axis-label").text(metric);
163 | };
164 |
165 | const metrics = [
166 | "windSpeed",
167 | "moonPhase",
168 | "dewPoint",
169 | "humidity",
170 | "uvIndex",
171 | "windBearing",
172 | "temperatureMin",
173 | "temperatureMax",
174 | ];
175 | let selectedMetricIndex = 0;
176 | drawHistogram(metrics[selectedMetricIndex]);
177 |
178 | const button = d3.select("body").append("button").text("Change metric");
179 |
180 | button.node().addEventListener("click", onClick);
181 | function onClick() {
182 | selectedMetricIndex = (selectedMetricIndex + 1) % metrics.length;
183 | drawHistogram(metrics[selectedMetricIndex]);
184 | }
185 | }
186 | drawBars();
187 |
--------------------------------------------------------------------------------
/bargraph/bargraph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aligg/Fullstack-D3-and-Data-Visualization/fc42940427f744b439bed4e332063ed1fb3f5a8a/bargraph/bargraph.png
--------------------------------------------------------------------------------
/bargraph/draw-bars-2.js:
--------------------------------------------------------------------------------
1 | async function drawBars() {
2 | // 1. Access data
3 | const dataset = await d3.json("./../../my_weather_data.json");
4 |
5 | const metricAccessor = (d) => d.humidity;
6 | const yAccessor = (d) => d.length;
7 |
8 | // 2. Create chart dimensions
9 |
10 | const width = 600;
11 | let dimensions = {
12 | width: width,
13 | height: width * 0.6,
14 | margin: {
15 | top: 30,
16 | right: 10,
17 | bottom: 50,
18 | left: 50,
19 | },
20 | };
21 | dimensions.boundedWidth =
22 | dimensions.width - dimensions.margin.left - dimensions.margin.right;
23 | dimensions.boundedHeight =
24 | dimensions.height - dimensions.margin.top - dimensions.margin.bottom;
25 |
26 | // 3. Draw canvas
27 |
28 | const wrapper = d3
29 | .select("#wrapper")
30 | .append("svg")
31 | .attr("width", dimensions.width)
32 | .attr("height", dimensions.height);
33 |
34 | const bounds = wrapper
35 | .append("g")
36 | .style(
37 | "transform",
38 | `translate(${dimensions.margin.left}px, ${dimensions.margin.top}px)`,
39 | );
40 |
41 | // 4. Create scales
42 |
43 | const xScale = d3
44 | .scaleLinear()
45 | .domain(d3.extent(dataset, metricAccessor))
46 | .range([0, dimensions.boundedWidth])
47 | .nice();
48 |
49 | const binsGenerator = d3
50 | .histogram()
51 | .domain(xScale.domain())
52 | .value(metricAccessor)
53 | .thresholds(12);
54 |
55 | const bins = binsGenerator(dataset);
56 |
57 | const yScale = d3
58 | .scaleLinear()
59 | .domain([0, d3.max(bins, yAccessor)])
60 | .range([dimensions.boundedHeight, 0])
61 | .nice();
62 |
63 | // 5. Draw data
64 |
65 | const binsGroup = bounds.append("g");
66 |
67 | const binGroups = binsGroup.selectAll("g").data(bins).enter().append("g");
68 |
69 | const barPadding = 1;
70 | const barRects = binGroups
71 | .append("rect")
72 | .attr("x", (d) => xScale(d.x0) + barPadding / 2)
73 | .attr("y", (d) => yScale(yAccessor(d)))
74 | .attr("width", (d) =>
75 | d3.max([0, xScale(d.x1) - xScale(d.x0) - barPadding]),
76 | )
77 | .attr("height", (d) => dimensions.boundedHeight - yScale(yAccessor(d)))
78 | .attr("fill", "cornflowerblue");
79 |
80 | const barText = binGroups
81 | .filter(yAccessor)
82 | .append("text")
83 | .attr("x", (d) => xScale(d.x0) + (xScale(d.x1) - xScale(d.x0)) / 2)
84 | .attr("y", (d) => yScale(yAccessor(d)) - 5)
85 | .text(yAccessor)
86 | .style("text-anchor", "middle")
87 | .attr("fill", "darkgrey")
88 | .style("font-size", "12px")
89 | .style("font-family", "sans-serif");
90 |
91 | const mean = d3.mean(dataset, metricAccessor);
92 | const meanLine = bounds
93 | .append("line")
94 | .attr("x1", xScale(mean))
95 | .attr("x2", xScale(mean))
96 | .attr("y1", -15)
97 | .attr("y2", dimensions.boundedHeight)
98 | .attr("stroke", "maroon")
99 | .attr("stroke-dasharray", "2px 4px");
100 |
101 | const meanLabel = bounds
102 | .append("text")
103 | .attr("x", xScale(mean))
104 | .attr("y", -20)
105 | .text("mean")
106 | .attr("fill", "maroon")
107 | .style("font-size", "12px")
108 | .style("text-anchor", "middle");
109 |
110 | // 6. Draw peripherals
111 |
112 | const xAxisGenerator = d3.axisBottom().scale(xScale);
113 |
114 | const xAxis = bounds
115 | .append("g")
116 | .call(xAxisGenerator)
117 | .style("transform", `translateY(${dimensions.boundedHeight}px)`);
118 |
119 | const xAxisLabel = xAxis
120 | .append("text")
121 | .attr("x", dimensions.boundedWidth / 2)
122 | .attr("y", dimensions.margin.bottom - 10)
123 | .attr("fill", "black")
124 | .style("font-size", "1.4em")
125 | .text("Humidity")
126 | .style("text-transform", "capitalize");
127 | }
128 | drawBars();
129 |
--------------------------------------------------------------------------------
/bargraph/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | My Histogram
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/bargraph/styles.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Inter var';
3 | src: url('./../../Inter.var.woff2');
4 | }
5 |
6 | body {
7 | display: flex;
8 | flex-direction: column;
9 | align-items: center;
10 | padding: 2em;
11 | letter-spacing: -0.011em;
12 | font-family: 'Inter var', sans-serif;
13 | font-size: 16px;
14 | color: #34495e;
15 | background: #f8f9fa;
16 | }
17 |
18 | svg {
19 | fill: #34495e;
20 | }
--------------------------------------------------------------------------------
/linechart/chart.js:
--------------------------------------------------------------------------------
1 | async function drawLineChart() {
2 | // 1. Access data
3 | const dataset = await d3.json("./../../my_weather_data.json");
4 |
5 | const yAccessor = (d) => d.temperatureMax;
6 | const dateParser = d3.timeParse("%Y-%m-%d");
7 | const xAccessor = (d) => dateParser(d.date);
8 |
9 | // 2. Create chart dimensions
10 |
11 | let dimensions = {
12 | width: window.innerWidth * 0.9,
13 | height: 400,
14 | margin: {
15 | top: 15,
16 | right: 15,
17 | bottom: 40,
18 | left: 60,
19 | },
20 | };
21 | dimensions.boundedWidth =
22 | dimensions.width - dimensions.margin.left - dimensions.margin.right;
23 | dimensions.boundedHeight =
24 | dimensions.height - dimensions.margin.top - dimensions.margin.bottom;
25 |
26 | // 3. Draw canvas
27 |
28 | const wrapper = d3
29 | .select("#wrapper")
30 | .append("svg")
31 | .attr("width", dimensions.width)
32 | .attr("height", dimensions.height);
33 |
34 | const bounds = wrapper
35 | .append("g")
36 | .style(
37 | "transform",
38 | `translate(${dimensions.margin.left}px, ${dimensions.margin.top}px)`,
39 | );
40 | // 4. Create scales
41 |
42 | const yScale = d3
43 | .scaleLinear()
44 | .domain(d3.extent(dataset, yAccessor))
45 | .range([dimensions.boundedHeight, 0]);
46 |
47 | const freezingTemperaturePlacement = yScale(32);
48 | const freezingTemperatures = bounds
49 | .append("rect")
50 | .attr("x", 0)
51 | .attr("width", dimensions.boundedWidth)
52 | .attr("y", freezingTemperaturePlacement)
53 | .attr("height", dimensions.boundedHeight - freezingTemperaturePlacement)
54 | .attr("fill", "#e0f3f3");
55 |
56 | const xScale = d3
57 | .scaleTime()
58 | .domain(d3.extent(dataset, xAccessor))
59 | .range([0, dimensions.boundedWidth]);
60 |
61 | const lineGenerator = d3
62 | .line()
63 | .x((d) => xScale(xAccessor(d)))
64 | .y((d) => yScale(yAccessor(d)));
65 |
66 | const line = bounds
67 | .append("path")
68 | .attr("d", lineGenerator(dataset))
69 | .attr("fill", "none")
70 | .attr("stroke", "#af9358")
71 | .attr("stroke-width", 2);
72 |
73 | const yAxisGenerator = d3.axisLeft().scale(yScale);
74 | const yAxis = bounds.append("g").call(yAxisGenerator);
75 | const xAxisGenerator = d3.axisBottom().scale(xScale);
76 | const xAxis = bounds
77 | .append("g")
78 | .call(xAxisGenerator)
79 | .style("transform", `translateY(${dimensions.boundedHeight}px)`);
80 | }
81 |
82 | drawLineChart();
83 |
--------------------------------------------------------------------------------
/linechart/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | My Timeline
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/linechart/linechart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aligg/Fullstack-D3-and-Data-Visualization/fc42940427f744b439bed4e332063ed1fb3f5a8a/linechart/linechart.png
--------------------------------------------------------------------------------
/scatterplot/draw-scatter.js:
--------------------------------------------------------------------------------
1 | async function drawScatter() {
2 | // 1. Access data
3 | let dataset = await d3.json("./../../my_weather_data.json");
4 |
5 | const xAccessor = (d) => d.dewPoint;
6 | const yAccessor = (d) => d.humidity;
7 | const colorAccessor = (d) => d.cloudCover;
8 |
9 | // 2. Create chart dimensions
10 |
11 | const width = d3.min([window.innerWidth * 0.9, window.innerHeight * 0.9]);
12 | let dimensions = {
13 | width: width,
14 | height: width,
15 | margin: {
16 | top: 10,
17 | right: 10,
18 | bottom: 50,
19 | left: 50,
20 | },
21 | };
22 | dimensions.boundedWidth =
23 | dimensions.width - dimensions.margin.left - dimensions.margin.right;
24 | dimensions.boundedHeight =
25 | dimensions.height - dimensions.margin.top - dimensions.margin.bottom;
26 |
27 | // 3. Draw canvas
28 |
29 | const wrapper = d3
30 | .select("#wrapper")
31 | .append("svg")
32 | .attr("width", dimensions.width)
33 | .attr("height", dimensions.height);
34 |
35 | const bounds = wrapper
36 | .append("g")
37 | .style(
38 | "transform",
39 | `translate(${dimensions.margin.left}px, ${dimensions.margin.top}px)`,
40 | );
41 |
42 | // 4. Create scales
43 |
44 | const xScale = d3
45 | .scaleLinear()
46 | .domain(d3.extent(dataset, xAccessor))
47 | .range([0, dimensions.boundedWidth])
48 | .nice();
49 |
50 | const yScale = d3
51 | .scaleLinear()
52 | .domain(d3.extent(dataset, yAccessor))
53 | .range([dimensions.boundedHeight, 0])
54 | .nice();
55 |
56 | const colorScale = d3
57 | .scaleLinear()
58 | .domain(d3.extent(dataset, colorAccessor))
59 | .range(["skyblue", "darkslategrey"]);
60 |
61 | // 5. Draw data
62 |
63 | const dots = bounds
64 | .selectAll("circle")
65 | .data(dataset)
66 | .enter()
67 | .append("circle")
68 | .attr("cx", (d) => xScale(xAccessor(d)))
69 | .attr("cy", (d) => yScale(yAccessor(d)))
70 | .attr("r", 4)
71 | .attr("fill", (d) => colorScale(colorAccessor(d)))
72 | .attr("tabindex", "0");
73 |
74 | // 6. Draw peripherals
75 |
76 | const xAxisGenerator = d3.axisBottom().scale(xScale);
77 |
78 | const xAxis = bounds
79 | .append("g")
80 | .call(xAxisGenerator)
81 | .style("transform", `translateY(${dimensions.boundedHeight}px)`);
82 |
83 | const xAxisLabel = xAxis
84 | .append("text")
85 | .attr("x", dimensions.boundedWidth / 2)
86 | .attr("y", dimensions.margin.bottom - 10)
87 | .attr("fill", "black")
88 | .style("font-size", "1.4em")
89 | .html("Dew point (°F)");
90 |
91 | const yAxisGenerator = d3.axisLeft().scale(yScale).ticks(4);
92 |
93 | const yAxis = bounds.append("g").call(yAxisGenerator);
94 |
95 | const yAxisLabel = yAxis
96 | .append("text")
97 | .attr("x", -dimensions.boundedHeight / 2)
98 | .attr("y", -dimensions.margin.left + 10)
99 | .attr("fill", "black")
100 | .style("font-size", "1.4em")
101 | .text("Relative humidity")
102 | .style("transform", "rotate(-90deg)")
103 | .style("text-anchor", "middle");
104 | }
105 | drawScatter();
106 |
--------------------------------------------------------------------------------
/scatterplot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | My Scatterplot
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/scatterplot/scatterplot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aligg/Fullstack-D3-and-Data-Visualization/fc42940427f744b439bed4e332063ed1fb3f5a8a/scatterplot/scatterplot.png
--------------------------------------------------------------------------------
/scatterplot/styles.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Inter var';
3 | src: url('./../../Inter.var.woff2');
4 | }
5 |
6 | body {
7 | display: flex;
8 | flex-direction: column;
9 | align-items: center;
10 | padding: 2em;
11 | letter-spacing: -0.011em;
12 | font-family: 'Inter var', sans-serif;
13 | font-size: 16px;
14 | color: #34495e;
15 | background: #f8f9fa;
16 | }
17 |
18 | svg {
19 | fill: #34495e;
20 | }
--------------------------------------------------------------------------------