import { aq, op } from "@uwdata/arquero"
bucket_base = "https://d33173dkfxwa5x.cloudfront.net/www/"
FA = FileAttachment
import {aq as aq, op as op} from "@uwdata/arquero"
import { aq, op } from "@uwdata/arquero"
bucket_base = "https://d33173dkfxwa5x.cloudfront.net/www/"
FA = FileAttachment
import {aq as aq, op as op} from "@uwdata/arquero"
mutable myLocation = null
mutable geolocateStatus = "default"
getLocation = {
yield null
yield navigator.geolocation.getCurrentPosition(
pos => {
mutable myLocation = {
lat: pos.coords.latitude,
lon: pos.coords.longitude
}
mutable geolocateStatus = "granted"
},
e => {
console.log("Geolocation error", e),
mutable geolocateStatus = "error"
},
{
enableHighAccuracy: false
})
}
geolocateStatus == "default" ?
md`Allow location to see local station` :
geolocateStatus == "error" ?
md`Problem getting location` :
md``
Allow location to see local station
myLocation != null ?
md`Location is lat: ${myLocation.lat}, lon: ${myLocation.lon}` :
md`Location permission not granted`
Location permission not granted
// on the home page, get station id from:
// * _geolocator.qmd's myLocation, plus
// * _map.qmd's statsAll
stationId = {
// fallback to sydney?
if (myLocation == null) {
return "023000"
}
// otherwise, find closest station on statsAll
const closestStation = aq.from(statsAll)
.params({
currentLat: myLocation.lat,
currentLon: myLocation.lon,
})
.derive({
dist: (d, $) => Math.sqrt(
($.currentLat - d.isit_lat) ** 2 + ($.currentLon - d.isit_lon) ** 2)
})
.orderby("dist")
.slice(0, 1)
.array("id")
return closestStation[0]
}
op is not defined
op is not defined
Is it hot in
answers = ({
"Hell no!": {color: "white", fill1: "#5287d1", fill2: "#2166ac" },
"No!": {color: "white", fill1: "#a2cdeb", fill2: "#67a9cf" },
"Nope": {color: "#333333", fill1: "#f5f8fa", fill2: "#d1e5f0" },
"Not really": {color: "#333333", fill1: "#f7f7f7", fill2: "#ebebeb" },
"Yup": {color: "#333333", fill1: "#fddcca", fill2: "#f7cfb7" },
"Yeah!": {color: "white", fill1: "#de7b49", fill2: "#fc946a" },
"Hell yeah!": {color: "white", fill1: "#d1231d", fill2: "#b2182b" }
})
html`
<div class="answer-icon-and-label">
<span class="answer-icon" style="background-color: ${answers[statsPlace.isit_answer].fill2};">
</span>
<span>${statsPlace.isit_answer}</span>
</div>`
op is not defined
datefns = import("https://cdn.skypack.dev/date-fns@3.3.1?min")
tmaxWhen = new Date(Date.parse(statsPlace.isit_maximum_dt || null))
tminWhen = new Date(Date.parse(statsPlace.isit_minimum_dt || null))
tmaxWhenFriendly = isFinite(tmaxWhen) ?
datefns.formatRelative(tmaxWhen, new Date()) : ""
tminWhenFriendly = isFinite(tminWhen) ?
datefns.formatRelative(tminWhen, new Date()) : ""
percentRating = statsPlace.isit_average.toFixed(1) + "%"
op is not defined
op is not defined
op is not defined
op is not defined
op is not defined
html`
<div class="answer-icon-and-label">
<span class="answer-icon" style="background-color: ${answers[statsPlace.isit_answer].fill2};"></span>
<span>${statsPlace.isit_current.toFixed(1)} °C</span>
</div>`
op is not defined
op is not defined
This is the distribution of temperatures typical for this time of year, which we’ve defined as a seven day window either side of today. Just like a bell curve, the plot is higher at more common temperatures and lower at rare temperatures.
We’ve compared every day this year at
statsAllRaw = FA(bucket_base + "stats/stats_all.json").json()
// statsAllRaw is an object with keys by station id, but we
// need an array with an id field. let's move the ids in
statsAll = {
const statsArray = []
Object.keys(statsAllRaw).map(k => statsArray.push({...statsAllRaw[k], id: k}))
const tidiedStats = aq.from(statsArray)
.derive({
lat: d => op.parse_float(d.isit_lat),
lon: d => op.parse_float(d.isit_lon),
isit_average: d => op.parse_float(d.isit_average),
// current? (that's the actual avg temp)
isit_maximum: d => op.parse_float(d.isit_maximum),
isit_minimum: d => op.parse_float(d.isit_minimum),
max_when: d => op.parse_date(d.isit_maximum_dt),
min_when: d => op.parse_date(d.isit_minimum_dt),
slug: d =>
op.replace(
op.replace(
op.replace(
op.lower(d.isit_label),
/\s-\s/g, "-"),
/\s/g, "-"),
/[()]/g, "")
})
// mark as stale if latest obs are > 24 hours old
.derive({
max_stale:
d => ((d.max_when - op.now()) / (1000 * 60)) / (24 * 60) > 1,
min_stale:
d => ((d.min_when - op.now()) / (1000 * 60)) / (24 * 60) > 1
})
.objects()
return(tidiedStats)
}
op is not defined
currentStats = statsAll.filter(d => (!d.max_stale) && (!d.min_stale))
staleStats = statsAll.filter(d => d.max_stale || d.min_stale)
op is not defined
op is not defined
australia = FileAttachment("/assets/australia.geojson").json()
Plot = import("https://esm.run/@observablehq/plot")
answer_domain = [
"Hell no!",
"No!",
"Nope",
"Not really",
"Yup",
"Yeah!",
"Hell yeah!"]
Plot.plot({
projection: {
type: "equirectangular",
rotate: [-133, 28],
domain: d3.geoCircle().center([133, -28]).radius(18)()
},
marks: [
// Plot.graticule(),
Plot.geo(australia, {
fill: "#00000009",
stroke: "#dddddd"
}),
// replace stale locations with crosses
Plot.dot(staleStats, {
x: "lon",
y: "lat",
stroke: "lightgrey",
r: 6,
symbol: "times"
}),
Plot.dot(currentStats, {
x: "lon",
y: "lat",
fill: "isit_answer",
stroke: d =>
d.isit_answer == "Hell no!" || d.isit_answer == "Hell yeah!" ?
"black" :
"#00000099",
strokeWidth: 0.75,
r: 6,
}),
Plot.tip(currentStats, tipTemplate),
// enlarge dot you're pointing to
Plot.dot(currentStats, Plot.pointer({
x: "lon",
y: "lat",
fill: "isit_answer",
stroke: d =>
d.isit_answer == "Hell no!" || d.isit_answer == "Hell yeah!" ?
"black" :
"#00000099",
strokeWidth: 0.75,
r: 12,
})),
// enlargen clickable areas with invisible voronoi polys
Plot.voronoi(currentStats, {
x: "lon",
y: "lat",
fill: "transparent",
stroke: "transparent",
href: d => "/places/" + d.slug,
}),
],
color: {
type: "ordinal",
domain: answer_domain,
range: ["#2166acff",
"#67a9cfff",
"#d1e5f0ff",
"#f7f7f7ff",
"#fddbc7ff",
"#ef8a62ff",
"#b2182bff"]
},
height: 600
})
op is not defined
// breaking out the pointer layer for readability
// (and in case we decide to customise it later)
tipTemplate = Plot.pointer({
x: "lon",
y: "lat",
channels: {
place: {
label: "",
value: d => d.isit_label,
},
label: {
label: "",
value: d => d.isit_comment
},
separator: {
label: "",
value: d => ""
},
isit_maximum: {
label: "Max:",
value: d => d.isit_maximum + "°C"
},
isit_minimum: {
label: "Min:",
value: d => d.isit_minimum + "°C"
}
},
fontSize: 20,
fontFamily: "Roboto Condensed",
format: {
x: null,
y: null
},
lineWidth: 20,
lineHeight: 1.25,
pointerSize: 0,
fillOpacity: 0.8,
strokeOpacity: 0
})