2024-02-29 17:24:33 -06:00
<!DOCTYPE html>
< html lang = "en" class = "h-100" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
2024-07-06 15:25:57 -05:00
< title > Name All MN's Lakes< / title >
2024-02-29 17:24:33 -06:00
< link href = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel = "stylesheet" integrity = "sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin = "anonymous" >
< / head >
< body class = "h-100 d-flex flex-column" >
2024-03-07 00:28:54 -06:00
< div v-scope @ vue:mounted = "mounted" class = "container vh-100" >
2024-07-06 15:25:57 -05:00
< h1 > Name All MN's Lakes< / h1 >
< div class = "row" >
2024-03-01 00:48:54 -06:00
< div class = "col-4" >
< div class = "mb-3" >
2024-07-06 15:25:57 -05:00
< label for = "lake-guess" class = "form-label" > Lake:< / label >
2024-03-01 00:48:54 -06:00
< div class = "input-group" >
2024-07-06 15:25:57 -05:00
< input class = "form-control" id = "lake-guess" v-model = "lake_guess" @ keyup . enter = "guess" >
2024-03-01 00:48:54 -06:00
< button class = "btn btn-primary" type = "button" @ click = "guess" > Guess< / button >
< / div >
< / div >
< div v-if = "message != ''" class = "alert alert-secondary" role = "alert" >
{{ message }}
< / div >
< div >
< span
2024-07-06 15:25:57 -05:00
v-for="(required_lakes, name) in achievements"
:class="{ 'badge': true, 'me-1': true, 'text-bg-secondary': !required_lakes(lakes).every(c => c.guessed), 'text-bg-warning': required_lakes(lakes).every(c => c.guessed) }">
2024-03-01 00:48:54 -06:00
{{ name }}
2024-07-06 15:25:57 -05:00
({{ required_lakes(lakes).filter(c => c.guessed).length }}/{{ required_lakes(lakes).length }})
2024-03-01 00:48:54 -06:00
< / span >
< / div >
< table class = "table" >
< thead >
< tr >
< th > Rank< / th >
< th > Name< / th >
2024-07-06 15:25:57 -05:00
< th > Count< / th >
< th > Total Area< / th >
2024-03-01 00:48:54 -06:00
< / tr >
< / thead >
< tbody >
2024-07-06 15:25:57 -05:00
< template v-for = "(lake, rank) in lakes" >
< tr v-show = "lake.guessed" >
2024-03-01 00:48:54 -06:00
< td > {{ rank + 1 }}< / td >
2024-07-06 15:25:57 -05:00
< td > {{ lake.name }}< / td >
< td > {{ lake.centers.length }}< / td >
< td > {{ lake.area.toFixed(3) }}< / td >
2024-03-01 00:48:54 -06:00
< / tr >
< / template >
< / tbody >
< / table >
< / div >
2024-03-06 22:54:22 -06:00
< div class = "col-8" >
2024-03-07 00:28:54 -06:00
< canvas id = "canvas" class = "mx-3 my-auto" width = "800" height = "600" > < / canvas >
2024-03-05 23:29:06 -06:00
< div >
2024-07-06 15:25:57 -05:00
< input type = "checkbox" id = "show-unguessed-checkbox" v-model = "show_unguessed_lakes" @ change = "draw" >
< label for = "show-unguessed-checkbox" > Show unguessed lakes< / label >
2024-03-05 23:29:06 -06:00
< / div >
2024-03-01 00:48:54 -06:00
< / div >
< / div >
< / div >
< footer class = "footer mt-auto py-3 text-center text-muted" >
2024-07-06 15:25:57 -05:00
Created by < a href = "https://chandlerswift.com" > Chandler Swift< / a > using data from MN DNR |
< a href = "https://git.chandlerswift.com/chandlerswift/name-all-lakes-by-population-quiz" > Source< / a >
(< a href = "https://git.chandlerswift.com/chandlerswift/name-all-lakes-by-population-quiz/src/branch/main/LICENSE" > GPL3< / a > )
2024-03-01 00:48:54 -06:00
< / footer >
< script type = "module" >
2024-03-07 00:28:54 -06:00
import { createApp } from 'https://unpkg.com/petite-vue?module';
2024-07-06 15:25:57 -05:00
const response = await fetch(`lakes.json`);
const lakes = await response.json();
2024-03-07 00:28:54 -06:00
createApp({
2024-07-06 15:25:57 -05:00
lakes: lakes,
bounds: null,
lake_guess: "",
2024-03-07 00:28:54 -06:00
message: "",
2024-07-06 15:25:57 -05:00
show_unguessed_lakes: true,
2024-03-07 00:28:54 -06:00
achievements: {
2024-07-06 15:25:57 -05:00
"Top Five": lakes => lakes.slice(0, 5),
"Top Ten": lakes => lakes.slice(0, 10),
"Top Twenty": lakes => lakes.slice(0, 20),
"Top Fifty": lakes => lakes.slice(0, 50),
"Top Hundred": lakes => lakes.slice(0, 100),
"Every Single One": lakes => lakes,
"All above 1M acres": lakes => lakes.filter(lake => lake.area >= 1000000),
"All above 100k acres": lakes => lakes.filter(lake => lake.area >= 100000),
"All above 50k acres": lakes => lakes.filter(lake => lake.area >= 50000),
"All above 25k acres": lakes => lakes.filter(lake => lake.area >= 25000),
"With >100 lakes": lakes => lakes.filter(lake => lake.centers.length >= 100),
"With >50 lakes": lakes => lakes.filter(lake => lake.centers.length >= 50),
"With >25 lakes": lakes => lakes.filter(lake => lake.centers.length >= 25),
2024-03-07 00:28:54 -06:00
},
2024-03-07 00:49:53 -06:00
async mounted() {
2024-07-06 15:25:57 -05:00
let all_centers = [];
for (let lake of lakes) {
all_centers.push(...lake.centers);
2024-03-07 00:28:54 -06:00
}
2024-03-07 01:26:53 -06:00
2024-07-06 15:25:57 -05:00
this.bounds = find_bounds(all_centers);
console.log(this.lakes[0].name);
this.simplified_lakes = this.lakes.map(lake => simplify(lake.name));
2024-03-07 01:26:53 -06:00
this.draw();
2024-03-07 00:49:53 -06:00
},
guess() {
2024-07-06 15:25:57 -05:00
const rank = this.simplified_lakes.indexOf(simplify(this.lake_guess))
2024-03-07 00:49:53 -06:00
if (rank >= 0) {
2024-07-06 15:25:57 -05:00
const lake = this.lakes[rank];
2024-03-07 00:28:54 -06:00
2024-07-06 15:25:57 -05:00
if (!lake.guessed) {
lake.guessed = true;
this.message = `${lake.name} (total area of ${lake.centers.length} lakes ${lake.area.toFixed(3)} acres) is the ${ordinal_suffix_of(rank + 1)} largest area set of lakes in MN.`;
this.lake_guess = "";
2024-03-07 00:49:53 -06:00
this.draw();
} else {
2024-07-06 15:25:57 -05:00
this.message = `Already guessed ${lake.name} (${lake.area.toFixed(3)} acres on ${lake.centers.length} lakes, rank ${rank + 1}).`;
this.lake_guess = "";
2024-03-07 00:49:53 -06:00
}
2024-03-07 00:28:54 -06:00
}
},
draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
2024-07-06 15:25:57 -05:00
const [minx, maxx, miny, maxy] = this.bounds;
2024-03-07 00:28:54 -06:00
const height_scale = (canvas.height - 8) / (maxy - miny);
const width_scale = (canvas.width - 8) / (maxx - minx);
const scale = Math.min(height_scale, width_scale);
const y_offset = miny - (canvas.height / scale - (maxy - miny)) / 2;
const x_offset = minx - (canvas.width / scale - (maxx - minx)) / 2;
function transform(pt) {
return [scale * (pt[0] - x_offset), canvas.height - (scale * (pt[1] - y_offset))];
}
2024-07-06 15:25:57 -05:00
for (let lake of this.lakes) {
for (let center of lake.centers) {
if (lake.guessed) {
ctx.beginPath();
const c = transform(center)
ctx.fillStyle = "black";
ctx.arc(c[0], c[1], 2, 0, 2*Math.PI, true);
ctx.fill();
} else if (this.show_unguessed_lakes) {
ctx.beginPath();
const c = transform(center)
ctx.fillStyle = "lightgray";
ctx.arc(c[0], c[1], 2, 0, 2*Math.PI, true);
ctx.fill();
}
2024-03-07 00:28:54 -06:00
}
}
2024-03-01 00:48:54 -06:00
}
2024-03-07 00:28:54 -06:00
}).mount();
2024-02-29 17:24:33 -06:00
2024-03-01 00:48:54 -06:00
// https://stackoverflow.com/a/13627586
function ordinal_suffix_of(i) {
let j = i % 10,
k = i % 100;
if (j === 1 & & k !== 11) {
return i + "st";
}
if (j === 2 & & k !== 12) {
return i + "nd";
2024-02-29 17:24:33 -06:00
}
2024-03-01 00:48:54 -06:00
if (j === 3 & & k !== 13) {
return i + "rd";
}
return i + "th";
}
2024-02-29 17:24:33 -06:00
2024-03-01 00:48:54 -06:00
const simplify = s => s.toLowerCase().replace(/[^a-z]/g, '');
2024-02-29 17:24:33 -06:00
2024-03-01 00:48:54 -06:00
// returns minx, maxx, miny, maxy
function find_bounds(coords) {
return [
Math.min(...coords.map(c => c[0])),
Math.max(...coords.map(c => c[0])),
Math.min(...coords.map(c => c[1])),
Math.max(...coords.map(c => c[1]))
];
}
< / script >
2024-02-29 17:24:33 -06:00
< / body >
< / html >