|
4 | 4 | <title>Demo Website</title> |
5 | 5 | <!-- linking css file--> |
6 | 6 | <link href="styles/styles.css" rel="stylesheet" /> |
| 7 | + <!-- D3.js library --> |
| 8 | + <script src="https://d3js.org/d3.v7.min.js"></script> |
7 | 9 |
|
8 | 10 | </head> |
9 | 11 | <body> |
|
135 | 137 |
|
136 | 138 | <div class="graph-container"> |
137 | 139 | <figure class="graph-figure"> |
138 | | - <img class="graph-img" src="data/payt_panels.png" alt="Pre-post effect of PAYT"> |
| 140 | + <div id="d3-graph"></div> |
139 | 141 | <figcaption>Data Sourced: Massachusetts Department of Environmental Protection's 2024 Municipal Solid Waste and Recycling Survey, Massachusetts Department of Revenue, & Massachusetts Municipal PAYT Programs June 2025 </figcaption> |
140 | 142 | </figure> |
141 | 143 | </div> |
@@ -221,6 +223,198 @@ <h3> |
221 | 223 | </div> |
222 | 224 | <script> |
223 | 225 | document.addEventListener('DOMContentLoaded', function () { |
| 226 | + // D3 Interactive Graph - Pre-Post PAYT Effect |
| 227 | + const graphData = [ |
| 228 | + // Chicopee (PAYT 2017, years_payt: -4 to 5) |
| 229 | + { municipality: 'Chicopee', year: -4, tonnage: 0.7393617, type: 'pre' }, |
| 230 | + { municipality: 'Chicopee', year: -3, tonnage: 0.7267905, type: 'pre' }, |
| 231 | + { municipality: 'Chicopee', year: -2, tonnage: 0.7201172, type: 'pre' }, |
| 232 | + { municipality: 'Chicopee', year: -1, tonnage: 0.7240520, type: 'pre' }, |
| 233 | + { municipality: 'Chicopee', year: 0, tonnage: 0.5886695, type: 'post' }, |
| 234 | + { municipality: 'Chicopee', year: 1, tonnage: 0.6204813, type: 'post' }, |
| 235 | + { municipality: 'Chicopee', year: 2, tonnage: 0.5576872, type: 'post' }, |
| 236 | + { municipality: 'Chicopee', year: 3, tonnage: 0.6272738, type: 'post' }, |
| 237 | + { municipality: 'Chicopee', year: 4, tonnage: 0.6468433, type: 'post' }, |
| 238 | + { municipality: 'Chicopee', year: 5, tonnage: 0.6151788, type: 'post' }, |
| 239 | + // Harvard (PAYT 2019, years_payt: -5 to 5) |
| 240 | + { municipality: 'Harvard', year: -5, tonnage: 0.8311209, type: 'pre' }, |
| 241 | + { municipality: 'Harvard', year: -4, tonnage: 0.8260870, type: 'pre' }, |
| 242 | + { municipality: 'Harvard', year: -3, tonnage: 0.9753580, type: 'pre' }, |
| 243 | + { municipality: 'Harvard', year: -2, tonnage: 0.8177323, type: 'pre' }, |
| 244 | + { municipality: 'Harvard', year: -1, tonnage: 0.8061971, type: 'pre' }, |
| 245 | + { municipality: 'Harvard', year: 0, tonnage: 0.6302390, type: 'post' }, |
| 246 | + { municipality: 'Harvard', year: 1, tonnage: 0.5599200, type: 'post' }, |
| 247 | + { municipality: 'Harvard', year: 2, tonnage: 0.5020960, type: 'post' }, |
| 248 | + { municipality: 'Harvard', year: 3, tonnage: 0.4425517, type: 'post' }, |
| 249 | + { municipality: 'Harvard', year: 4, tonnage: 0.4455224, type: 'post' }, |
| 250 | + { municipality: 'Harvard', year: 5, tonnage: 0.4975635, type: 'post' }, |
| 251 | + // Heath (PAYT 2017, years_payt: -5 to 5) |
| 252 | + { municipality: 'Heath', year: -5, tonnage: 0.9145299, type: 'pre' }, |
| 253 | + { municipality: 'Heath', year: -4, tonnage: 0.7803922, type: 'pre' }, |
| 254 | + { municipality: 'Heath', year: -3, tonnage: 0.7142857, type: 'pre' }, |
| 255 | + { municipality: 'Heath', year: -2, tonnage: 0.7811877, type: 'pre' }, |
| 256 | + { municipality: 'Heath', year: -1, tonnage: 0.6587726, type: 'pre' }, |
| 257 | + { municipality: 'Heath', year: 0, tonnage: 0.5249527, type: 'post' }, |
| 258 | + { municipality: 'Heath', year: 1, tonnage: 0.3610714, type: 'post' }, |
| 259 | + { municipality: 'Heath', year: 2, tonnage: 0.3556857, type: 'post' }, |
| 260 | + { municipality: 'Heath', year: 3, tonnage: 0.4209174, type: 'post' }, |
| 261 | + { municipality: 'Heath', year: 4, tonnage: 0.4009621, type: 'post' }, |
| 262 | + { municipality: 'Heath', year: 5, tonnage: 0.3579228, type: 'post' }, |
| 263 | + // Hinsdale (PAYT 2017, years_payt: -4 to 5) |
| 264 | + { municipality: 'Hinsdale', year: -4, tonnage: 0.8728448, type: 'pre' }, |
| 265 | + { municipality: 'Hinsdale', year: -3, tonnage: 0.9290323, type: 'pre' }, |
| 266 | + { municipality: 'Hinsdale', year: -2, tonnage: 0.9652903, type: 'pre' }, |
| 267 | + { municipality: 'Hinsdale', year: -1, tonnage: 0.9324731, type: 'pre' }, |
| 268 | + { municipality: 'Hinsdale', year: 0, tonnage: 0.6558077, type: 'post' }, |
| 269 | + { municipality: 'Hinsdale', year: 1, tonnage: 0.4879636, type: 'post' }, |
| 270 | + { municipality: 'Hinsdale', year: 2, tonnage: 0.4927273, type: 'post' }, |
| 271 | + { municipality: 'Hinsdale', year: 3, tonnage: 0.5397254, type: 'post' }, |
| 272 | + { municipality: 'Hinsdale', year: 4, tonnage: 0.5259774, type: 'post' }, |
| 273 | + { municipality: 'Hinsdale', year: 5, tonnage: 0.4343286, type: 'post' }, |
| 274 | + // Peru (PAYT 2017, years_payt: -5 to 4) |
| 275 | + { municipality: 'Peru', year: -5, tonnage: 0.6625000, type: 'pre' }, |
| 276 | + { municipality: 'Peru', year: -4, tonnage: 0.6781250, type: 'pre' }, |
| 277 | + { municipality: 'Peru', year: -3, tonnage: 0.6375000, type: 'pre' }, |
| 278 | + { municipality: 'Peru', year: -2, tonnage: 0.6742813, type: 'pre' }, |
| 279 | + { municipality: 'Peru', year: -1, tonnage: 0.5378307, type: 'pre' }, |
| 280 | + { municipality: 'Peru', year: 0, tonnage: 0.7028621, type: 'post' }, |
| 281 | + { municipality: 'Peru', year: 1, tonnage: 0.6156156, type: 'post' }, |
| 282 | + { municipality: 'Peru', year: 2, tonnage: 0.5544776, type: 'post' }, |
| 283 | + { municipality: 'Peru', year: 3, tonnage: 0.5881493, type: 'post' }, |
| 284 | + { municipality: 'Peru', year: 4, tonnage: 0.5265373, type: 'post' } |
| 285 | + ]; |
| 286 | + |
| 287 | + const margin = { top: 20, right: 30, bottom: 30, left: 60 }; |
| 288 | + const width = 900 - margin.left - margin.right; |
| 289 | + const height = 500 - margin.top - margin.bottom; |
| 290 | + |
| 291 | + const svg = d3.select('#d3-graph') |
| 292 | + .append('svg') |
| 293 | + .attr('width', width + margin.left + margin.right) |
| 294 | + .attr('height', height + margin.top + margin.bottom) |
| 295 | + .attr('viewBox', `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`) |
| 296 | + .attr('preserveAspectRatio', 'xMidYMid meet') |
| 297 | + .style('max-width', '100%') |
| 298 | + .style('height', 'auto') |
| 299 | + .append('g') |
| 300 | + .attr('transform', `translate(${margin.left},${margin.top})`); |
| 301 | + |
| 302 | + // Scales |
| 303 | + const xScale = d3.scaleLinear() |
| 304 | + .domain([-5, 5]) |
| 305 | + .range([0, width]); |
| 306 | + |
| 307 | + const yScale = d3.scaleLinear() |
| 308 | + .domain([0, 1.5]) |
| 309 | + .range([height, 0]); |
| 310 | + |
| 311 | + const colorScale = d3.scaleOrdinal() |
| 312 | + .domain(['Hinsdale', 'Peru', 'Heath', 'Chicopee', 'Harvard']) |
| 313 | + .range(['#091F2F', '#1871BD', '#FB4D42', '#51ACFF', '#45789C']); |
| 314 | + |
| 315 | + // Line generator |
| 316 | + const line = d3.line() |
| 317 | + .x(d => xScale(d.year)) |
| 318 | + .y(d => yScale(d.tonnage)); |
| 319 | + |
| 320 | + // Group data by municipality |
| 321 | + const municipalities = d3.group(graphData, d => d.municipality); |
| 322 | + |
| 323 | + // Draw lines for each municipality |
| 324 | + municipalities.forEach((data, municipality) => { |
| 325 | + svg.append('path') |
| 326 | + .datum(data) |
| 327 | + .attr('fill', 'none') |
| 328 | + .attr('stroke', colorScale(municipality)) |
| 329 | + .attr('stroke-width', 2.5) |
| 330 | + .attr('d', line) |
| 331 | + .attr('class', 'graph-line') |
| 332 | + .style('opacity', 0.7) |
| 333 | + .on('mouseover', function() { |
| 334 | + d3.select(this).style('opacity', 1).attr('stroke-width', 3.5); |
| 335 | + }) |
| 336 | + .on('mouseout', function() { |
| 337 | + d3.select(this).style('opacity', 0.7).attr('stroke-width', 2.5); |
| 338 | + }); |
| 339 | + |
| 340 | + // Draw circles for data points |
| 341 | + svg.selectAll(`.dots-${municipality}`) |
| 342 | + .data(data) |
| 343 | + .enter() |
| 344 | + .append('circle') |
| 345 | + .attr('cx', d => xScale(d.year)) |
| 346 | + .attr('cy', d => yScale(d.tonnage)) |
| 347 | + .attr('r', 4) |
| 348 | + .attr('fill', colorScale(municipality)) |
| 349 | + .attr('class', `dots-${municipality}`) |
| 350 | + .on('mouseover', function(event, d) { |
| 351 | + d3.select(this).attr('r', 6); |
| 352 | + svg.append('text') |
| 353 | + .attr('class', 'tooltip') |
| 354 | + .attr('x', xScale(d.year)) |
| 355 | + .attr('y', yScale(d.tonnage) - 10) |
| 356 | + .attr('text-anchor', 'middle') |
| 357 | + .attr('font-size', '12px') |
| 358 | + .attr('fill', '#333') |
| 359 | + .text(d.tonnage.toFixed(2) + ' tons'); |
| 360 | + }) |
| 361 | + .on('mouseout', function() { |
| 362 | + d3.select(this).attr('r', 4); |
| 363 | + svg.selectAll('.tooltip').remove(); |
| 364 | + }); |
| 365 | + }); |
| 366 | + |
| 367 | + // X-axis |
| 368 | + svg.append('g') |
| 369 | + .attr('transform', `translate(0,${height})`) |
| 370 | + .call(d3.axisBottom(xScale).tickValues([-5, -3, 0, 3, 5]).tickFormat(d => { |
| 371 | + if (d === 0) return 'Implementation'; |
| 372 | + if (d < 0) return `${Math.abs(d)}yr Pre`; |
| 373 | + return `${d}yr Post`; |
| 374 | + })) |
| 375 | + .append('text') |
| 376 | + .attr('x', width / 2) |
| 377 | + .attr('y', 40) |
| 378 | + .attr('fill', '#333') |
| 379 | + .attr('font-size', '14px') |
| 380 | + .attr('text-anchor', 'middle') |
| 381 | + .text('Timeline'); |
| 382 | + |
| 383 | + // Y-axis |
| 384 | + svg.append('g') |
| 385 | + .call(d3.axisLeft(yScale)) |
| 386 | + .append('text') |
| 387 | + .attr('transform', 'rotate(-90)') |
| 388 | + .attr('y', 0 - margin.left) |
| 389 | + .attr('x', 0 - (height / 2)) |
| 390 | + .attr('dy', '1em') |
| 391 | + .attr('fill', '#333') |
| 392 | + .attr('font-size', '14px') |
| 393 | + .attr('text-anchor', 'middle') |
| 394 | + .text('Tons per Household'); |
| 395 | + |
| 396 | + // Legend |
| 397 | + const legend = svg.selectAll('.legend') |
| 398 | + .data(['Hinsdale', 'Peru', 'Heath', 'Chicopee', 'Harvard']) |
| 399 | + .enter() |
| 400 | + .append('g') |
| 401 | + .attr('class', 'legend') |
| 402 | + .attr('transform', (d, i) => `translate(0,${-height - 10 - i * 20})`); |
| 403 | + |
| 404 | + legend.append('line') |
| 405 | + .attr('x1', 0) |
| 406 | + .attr('x2', 20) |
| 407 | + .attr('y1', 0) |
| 408 | + .attr('y2', 0) |
| 409 | + .attr('stroke', d => colorScale(d)) |
| 410 | + .attr('stroke-width', 2.5); |
| 411 | + |
| 412 | + legend.append('text') |
| 413 | + .attr('x', 25) |
| 414 | + .attr('y', 4) |
| 415 | + .attr('font-size', '12px') |
| 416 | + .text(d => d); |
| 417 | + |
224 | 418 | document.querySelectorAll('.method-toggle').forEach(function (btn) { |
225 | 419 | var content = btn.parentElement.querySelector('.method-content'); |
226 | 420 | btn.addEventListener('click', function () { |
|
0 commit comments