├── .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 | } --------------------------------------------------------------------------------