Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updating pseudo-code and readme docs #5

Merged
merged 2 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions articles/auto-thefts-2022/auto-thefts.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// establish array of location types
const locations = [
"Streets, Roads, Highways (Bicycle Path, Private Road)",
"Parking Lots (Apt., Commercial Or Non-Commercial)",
Expand Down Expand Up @@ -38,6 +39,7 @@ const locations = [
"Community Group Home",
];

// show a notification when the year is changed (on mobile)
let yearTimer;
function updateDataLabel(year) {
if (["", "sm", "md"].includes(module.currentBreakpoint())) {
Expand Down Expand Up @@ -65,12 +67,14 @@ function updateDataLabel(year) {
}
}

// initialize a geojson object to hold the data
const mappable = {
type: "FeatureCollection",
features: [],
};
const noGood = [];

// collect data for the current selected year
function filterData(year) {
const thefts = mappable.features.filter((f) => f.properties.y == year);
return {
Expand All @@ -79,12 +83,14 @@ function filterData(year) {
};
}

// update the map with the selected year's data
function updateViz(year) {
const src = module.map.getSource("auto-theft");
src.setData(filterData(year));
updateDataLabel(year);
}

// build the year selector element
const radioYears = document.createElement("div");
radioYears.innerHTML = `
<fieldset class="mb-3 -mt-4" id="year-selector">
Expand Down Expand Up @@ -113,6 +119,8 @@ radioYears.innerHTML = `
</div>
</fieldset>
`;

// build and initialize the legend
function addLegend() {
module.setLegendTitle("Toronto Auto Thefts");
module.addToLegend(radioYears);
Expand Down Expand Up @@ -152,6 +160,7 @@ function addLegend() {
selector.addEventListener("change", (e) => updateViz(e.target.value));
}

// show a popup for a theft location
function showPopup(e) {
module.clearPopups();
const { l, p, n } = e.features[0].properties;
Expand Down Expand Up @@ -219,6 +228,7 @@ function showPopup(e) {
});
}

// establish a consistent opacity
const opacity = [
"interpolate",
["linear"],
Expand All @@ -234,6 +244,7 @@ const opacity = [
];

function displayData() {
// adds point clusters at higher zoom levels
module.addVizLayer({
id: "clusters",
filter: ["has", "point_count"],
Expand Down Expand Up @@ -326,6 +337,7 @@ function displayData() {
fetch(url)
.then((r) => r.json())
.then((d) => {
// remove data points outside of Toronto (mislabelled)
d.features.forEach((f) => {
if (
f.geometry.coordinates[0] < -79.9 ||
Expand All @@ -345,6 +357,7 @@ fetch(url)
data: mappable,
type: "geojson",
});
// load and display data for 2022
updateViz(2022);
displayData();
})
Expand Down
12 changes: 12 additions & 0 deletions articles/auto-thefts-2022/neighbourhoods.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// hide selected mapbox layers to reduce visual clutter
module.hideLayer("settlement-subdivision-label");
module.hideLayer("settlement-minor-label");
module.hideLayer("settlement-major-label");

let currentYear = 2022;

// update the visualisation based on the selected year
function updateViz(year) {
module.clearPopups();
currentYear = year;
Expand Down Expand Up @@ -39,6 +41,7 @@ function updateViz(year) {
]);
}

// listen for the initial year selector to be added to the DOM and then add an event listener
const interval = setInterval(() => {
const selector = document.getElementById("year-selector");
if (selector) {
Expand All @@ -47,13 +50,15 @@ const interval = setInterval(() => {
}
}, 1000);

// establish consistent text colours
const textColour = {
red: "text-red-700 dark:text-red-500",
orange: "text-orange-700 dark:text-orange-500",
yellow: "text-yellow-700 dark:text-yellow-500",
blue: "text-blue-700 dark:text-blue-500",
};

// show the popup with the neighbourhood details
function showDetails(e) {
if (module.map.getLayer("clusters")) {
const clusters = module.map.queryRenderedFeatures(e.point, {
Expand Down Expand Up @@ -110,6 +115,7 @@ function showDetails(e) {
.setLngLat(center.geometry.coordinates)
.setHTML(defaultHTML),
);
// zoom to the neighbourhood
module.map.fitBounds(bbox, {
bearing: module.map.getBearing(),
duration: 2500,
Expand All @@ -119,11 +125,13 @@ function showDetails(e) {
});
}

// initialise an object to store upper limits
const limits = {
highestTotal: 0,
highestPerKm: 0,
};

// build the neighbourhood visual and add it to the map
let hoveredStateId = null;
function addNeighbourhoods() {
module.addUnderglowLayer({
Expand Down Expand Up @@ -207,7 +215,9 @@ function addNeighbourhoods() {
],
},
});
// show a popup on click
module.handleCursor("neighbourhoods-trigger", showDetails);
// highlight the neighbourhood boundary on hover
module.map.on("mousemove", "neighbourhoods-trigger", (e) => {
if (e.features.length > 0) {
if (hoveredStateId !== null) {
Expand All @@ -234,6 +244,7 @@ function addNeighbourhoods() {
});
}

// get the upper limits for the data
function getLimits(features) {
features.forEach((f) => {
[2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022].forEach((year) => {
Expand All @@ -247,6 +258,7 @@ function getLimits(features) {
});
}

// fetch the neighbourhood data and begin building the visualisation
fetch(url)
.then((r) => r.json())
.then((d) => {
Expand Down
7 changes: 7 additions & 0 deletions articles/auto-thefts-2022/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# We mapped Toronto auto thefts. Here are the hardest-hit areas.

** Code and markup by Kyle Duncan**

This is a data analysis and visualisation based on auto theft data from the Toronto Police Service. There are three aspects to this visualisation, an elevation-based overview of total thefts by neighbourhood, and a colour-based visual of thefts per square kilometer by neighbourhood at lower zoom levels; and a clustered count of individual thefts locations at higher zoom levels.

Popups with more detail appear on click for both neighbourhoods and individual thefts, and the user can view a given year's data using the selector in the legend. Multiple thefts at a single location are collated and ordered in single popup.
27 changes: 27 additions & 0 deletions articles/greater-toronto-area-quiz/article.html
Original file line number Diff line number Diff line change
@@ -1 +1,28 @@
<!-- Full screen article -->
<figure>
<img
src="https://media.geomodul.us/img/GTA-highway.jpg"
alt="A view of highway signs in the Greater Toronto Area"
style="width: 100%"
/>
<figcaption
class="pl-5 -indent-[14px] pr-2 py-1 text-gray-900 dark:text-gray-400 bg-map-200 dark:bg-gray-800 before:content-chevron text-xs"
>
No hints. (Photo illustration by Torontoverse Staff;
<a
href="https://commons.wikimedia.org/wiki/File:Ontario_Highway_401_(27023911124).jpg"
target="_blank"
>Wikimedia Commons</a
>)
</figcaption>
</figure>

<p>
You’ve come to a site called Torontoverse, so you must have at least
<i>some</i> knowledge of the place. Or you came by mistake. Whatever.
</p>
<p>
The point is: The Greater Toronto and Hamilton Area is made up of 26 distinct
places — a mix of urban, suburban, and rural municipalities. How many of them
can you pick out on a map?
</p>
36 changes: 18 additions & 18 deletions articles/greater-toronto-area-quiz/article.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// hide the article window
function goFullScreen() {
const fullscreen = document.querySelector(".mapctrl-fullscreen");
if (fullscreen) fullscreen.click();
else setTimeout(goFullScreen, 0);
}
goFullScreen();

// show a popup for the intro or instructions
function showPopup(content) {
module.clearPopups();
module.showPopup(
Expand All @@ -19,6 +21,7 @@ function showPopup(content) {
);
}

// initialize data to be used throughout the quiz
const quiz = {
// establish objects for data
answerList: [],
Expand All @@ -42,6 +45,7 @@ const quiz = {
},
};

// initialize comments for responses and end of quiz
quiz.comments = {
result: {
best: "You’re the Greater-est. Drake should write a song about you.", // 21–25 correct
Expand Down Expand Up @@ -73,12 +77,13 @@ quiz.comments = {
],
};

// return a random index of an array
quiz.getRandom = (array) => {
const randomIndex = Math.floor(Math.random() * array.length);
return array[randomIndex];
};

// show an intro popup
// show the intro popup
quiz.showIntro = () => {
const container = document.createElement("div");
const content = document.createElement("div");
Expand Down Expand Up @@ -146,16 +151,14 @@ quiz.showInstructions = () => {
.addEventListener("click", quiz.showPrompt);
};

// create a persistent element at the bottom center of the map that shows:
// - the next municipality to guess
// - the time remaining
// - the number of correct answers
// - a button to skip to the next municipality
// - a button to end the quiz
quiz.showPrompt = () => {
module.clearPopups();
/*
create a persistent element at the bottom center of the map that shows:
- the next municipality to guess
- the time remaining
- the number of correct answers
- a button to skip to the next municipality
- a button to end the quiz
*/
const prompt = document.createElement("div");
prompt.className =
"absolute bg-map-100 dark:bg-map-800 bottom-14 lg:bottom-10 cursor-default default-popoup flex flex-col inset-x-2.5 md:inset-x-1/4 lg:inset-x-1/3 items-center justify-center";
Expand All @@ -180,12 +183,11 @@ quiz.showPrompt = () => {
</div>

<div class="bg-map-200 dark:bg-map-700 flex justify-between mt-1 w-full">
<button class="shadow-emboss py-2 w-1/2" id="skipPrompt" type="button">Skip</button>
<button class="shadow-emboss py-2 w-1/2" id="giveUp" type="button">Give Up</button>
<button class="hover:bg-map-100 dark:hover:bg-map-800 shadow-emboss py-2 w-1/2" id="skipPrompt" type="button">Skip</button>
<button class="hover:bg-map-100 dark:hover:bg-map-800 shadow-emboss py-2 w-1/2" id="giveUp" type="button">Give Up</button>
</div>
`;
prompt.appendChild(content);
// add hover states to buttons
module.map.getContainer().appendChild(prompt);
window.addEventListener("flexWindowReset", () => {
document.getElementById("promptPopup").remove();
Expand All @@ -198,10 +200,9 @@ quiz.newPrompt = () => {
quiz.prompt.innerText = quiz.currentPrompt;
};

/* INITIALIZE QUIZ */
quiz.start = () => {
/* INITIALIZE QUIZ */
// grab visual elements

// text
quiz.siienComment = document.getElementById("commentText");
quiz.prompt = document.getElementById("prompt");
Expand All @@ -215,10 +216,11 @@ quiz.start = () => {
// buttons
quiz.endButton = document.getElementById("giveUp");
quiz.skipButton = document.getElementById("skipPrompt");

// listen for responses and other elements
module.handleCursor("boundary-fills", quiz.checkAnswer);
quiz.skipButton.addEventListener("click", quiz.newPrompt);
quiz.endButton.addEventListener("click", quiz.giveUp);
// load and start the quiz
quiz.buildData();
quiz.newPrompt();
quiz.startTimer();
Expand Down Expand Up @@ -301,7 +303,6 @@ quiz.correctGuess = (answer) => {
if (quiz.correctAnswers.length === quiz.masterTotals.answers) {
setTimeout(() => quiz.end("win"), 1000);
} else quiz.newPrompt();
// else quiz.animateSiien("bounce");
};

quiz.revealAnswer = (answer) => {
Expand Down Expand Up @@ -341,6 +342,7 @@ quiz.timeLeftPercentage = ({ minutes, seconds }) => {
return percentage;
};

// make sure the shorter bar is on top of the other
quiz.checkBarZindex = () => {
const scoreWidth = +quiz.scoreBar.style.width.slice(0, -1);
const timeWidth = +quiz.timeBar.style.width.slice(0, -1);
Expand Down Expand Up @@ -532,8 +534,6 @@ quiz.reset = () => {
module.map.once("idle", quiz.showInstructions);
};

// add intro content to legend/article body?

// add a listener to the map that shows the intro popup when the map is idle
module.map.once("idle", () => {
setTimeout(quiz.showIntro, 500);
Expand Down
5 changes: 5 additions & 0 deletions articles/greater-toronto-area-quiz/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# The great Greater Toronto and Hamilton Area municipality quiz

**Code and markup by Kyle Duncan**

This "article" was an experiment to try different kinds of user interaction as well as a full screen mode, i.e. no article text. At present this is achieved by manually hiding the article window on load. Initially the guesses were typed into a form element, but in adapting to mobile this was converted to a prompt of a municipality name which the user than has to correctly click or tap.
Loading
Loading