Skip to content

Commit

Permalink
Interactive map is working!
Browse files Browse the repository at this point in the history
  • Loading branch information
marcysutton committed Apr 7, 2023
1 parent 3aca666 commit 5f1495f
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 53 deletions.
24 changes: 1 addition & 23 deletions data/weathermap.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,6 @@ temps = {
springequinox1948_high: 11,
springequinox1948_low: -21
},
'Vancouver' :
{
springequinox2023_high: 51,
springequinox2023_low: 45,
springequinox2013_high: 55,
springequinox2013_low: 39,
springequinox1973_high: 51,
springequinox1973_low: 40,
springequinox1948_high: 44,
springequinox1948_low: 32
},
'Edmonton' :
{
springequinox2023_high: 34,
Expand Down Expand Up @@ -156,7 +145,7 @@ temps = {
springequinox1948_high: 58,
springequinox1948_low: 35
},
'San Diego' :
'Tijuana' :
{
springequinox2023_high: 63,
springequinox2023_low: 56,
Expand Down Expand Up @@ -255,17 +244,6 @@ temps = {
springequinox1948_high: 82,
springequinox1948_low: 72
},
'Jacksonville' :
{
springequinox2023_high: 62,
springequinox2023_low: 37,
springequinox2013_high: 64,
springequinox2013_low: 45,
springequinox1973_high: 76,
springequinox1973_low: 51,
springequinox1948_high: 88,
springequinox1948_low: 66
},
'Miami' :
{
springequinox2023_high: 77,
Expand Down
159 changes: 130 additions & 29 deletions src/components/Map.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
<script>
// import { onMount } from 'svelte';
import * as topojson from 'topojson-client';
import { geoMercator, geoPath } from 'd3-geo';
import { draw } from 'svelte/transition';
import { onMount } from 'svelte'
import * as topojson from 'topojson-client'
import { geoMercator, geoPath } from 'd3-geo'
export let data;
export let weatherData;
export let data
export let weatherData
const width = 960;
const height = 1160;
const width = 960
const height = 1160
const projection = geoMercator()
.center([100, 50])
Expand All @@ -18,19 +17,116 @@ const projection = geoMercator()
const path = geoPath().projection(projection)
const countries = topojson.feature(data, data.objects.subunits)
const places = topojson.feature(data, data.objects.places)
const weatherPlaces = weatherData
let boundaries = topojson.mesh(data, data.objects.subunits, (a, b) => { return a !== b; })
const transform = data.transform
let boundaries = topojson.mesh(data, data.objects.subunits, (a, b) => { return a !== b })
const transformPoint = (transform, position) => {
position = position.slice();
position[0] = position[0] * transform.scale[0]
+ transform.translate[0],
position[1] = position[1] * transform.scale[1]
+ transform.translate[1]
return position;
let seasonMap
let keyHandler
const DIRECTION = {
UP: 'ArrowUp', RIGHT: 'ArrowRight', DOWN: 'ArrowDown', LEFT: 'ArrowLeft'
}
onMount(() => {
let mapRect = seasonMap.getBoundingClientRect()
let mapWidth = mapRect.width
let mapHeight = mapRect.height
let currentBtnIndex = null
const getPlaceLabel = (pathEl) => {
if (!pathEl) {
return
}
let idRef = pathEl.getAttribute('aria-labelledby')
return document.getElementById(idRef).textContent
}
const activateBtn = (node) => {
node.setAttribute('tabindex', '0')
node.setAttribute('aria-checked', 'true')
console.log('active:', getPlaceLabel(node))
}
// collect rendered interactive path nodes
const pathBtns = Array.from(seasonMap.querySelectorAll('path[tabindex]'))
// make first button focusable
activateBtn(pathBtns[0])
// create array of DOMRects
const pathBtnCoords = pathBtns.map((btn, i) => {
// set a data attr with a numerical id for fast lookup
btn.setAttribute('data-placeId', i)
// store nodes with DOMRect coordinates
return btn.getBoundingClientRect()
})
const distance = (target, rect) => {
let result = Math.sqrt(Math.pow(target.x - rect.x, 2) + Math.pow(target.y - rect.y, 2))
// console.log('distance', result)
return result
}
const outOfBounds = (direction, targetRect, rect) => {
let excluded = false
switch (direction) {
// bail out if el is clearly out of bounds
case DIRECTION.LEFT:
if (rect.x > targetRect.x) excluded = true
break
case DIRECTION.UP:
if (rect.y > targetRect.y) excluded = true
break
case DIRECTION.RIGHT:
if (rect.x < targetRect.x) excluded = true
break
case DIRECTION.DOWN:
if(rect.y < targetRect.y) excluded = true
break
}
return excluded
}
const resetLastBtn = () => {
let lastBtn = seasonMap.querySelector('path[tabindex="0"]')
lastBtn.setAttribute('tabindex', '-1')
lastBtn.setAttribute('aria-checked', 'false')
}
const findClosestNeighbor = (target, direction) => {
const targetIndex = Number(target.dataset.placeId)
// grab computed boundingClientRect for event.target
const targetRect = pathBtnCoords[targetIndex]
let closestRect
let minDistance = 10000000
console.log('---target:---', getPlaceLabel(target))
for (var i = 0; i < pathBtnCoords.length; i++) {
let rect = pathBtnCoords[i]
let excluded = outOfBounds(direction, targetRect, rect) || i === targetIndex
let d = distance(targetRect, rect)
if (!excluded) {
if (d < minDistance) {
minDistance = d
currentBtnIndex = i
closestRect = rect
}
}
}
resetLastBtn()
let closest = pathBtns[currentBtnIndex]
activateBtn(closest)
closest.focus()
}
keyHandler = (event) => {
if (pathBtns.includes(event.target)) {
if (event.key === DIRECTION.UP || event.key === DIRECTION.DOWN) {
event.preventDefault()
}
switch (event.key) {
// idea: pre-compute closest points for faster interactions
case DIRECTION.LEFT:
case DIRECTION.RIGHT:
case DIRECTION.UP:
case DIRECTION.DOWN:
findClosestNeighbor(event.target, event.key)
break
}
}
}
})
</script>
<style>
.subunit { fill: #cccccc; }
Expand All @@ -49,35 +145,40 @@ const transformPoint = (transform, position) => {
font-weight: 600;
text-anchor: middle;
}
.place {
fill: none;
stroke: #484848;
stroke-width: 1;
}
.place-label {
font-size: 0.85rem;
}
</style>

<svg {height} {width}>
<svg {height} {width} on:keydown={keyHandler} bind:this={seasonMap}>
<title>Map of locations with temperature data</title>
{#each countries.features as country}
<path class={`subunit ${country.id}`} d={path(country)}></path>
{/each}
<path class="subunit-boundary" d={path(boundaries)}></path>
{#each places.features as place, i}
<!-- <path d={path(place)} class="place"></path> -->
{#if place.properties.name in weatherData}
<!-- exclude Vancouver, WA. early return would be easier! -->
{#if place.properties.name !== 'Vancouver'}
<text
class="place-label"
transform={`translate(${path.centroid(place)})`}
<path
aria-checked="false"
aria-labelledby={`place-label-${i}`}
class="place"
d={path(place)}
role="checkbox"
tabindex="-1"
>
{place.properties.name}
</text>
{:else if Math.floor(place.geometry.coordinates[1]) !== 45}
</path>
<text
class="place-label"
id={`place-label-${i}`}
transform={`translate(${path.centroid(place)})`}
>
{place.properties.name}
</text>
{/if}
{/if}
{/each}
</svg>
2 changes: 1 addition & 1 deletion src/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf8" />
<meta name="viewport" content="width=device-width" />
Expand Down

0 comments on commit 5f1495f

Please sign in to comment.