Adding categories in our addMarker()
function¶
In this lab, we will add categories to our addMarker()
function. This will allow us to filter the markers based on the categories.
Step 1: Add categories to the data¶
In our addMarker function, we will add a category to each feature by first creating a variable called category
. This category will be used to filter the markers later.
function addMarker(data){
let longitude = data['lng']
let latitude = data['lat'];
let vaccinationLocation = data['Where did you get vaccinated?'];
let homeZipcode = data['What zip code do you live in?'];
let vaccinationStatus = data['Have you been vaccinated?']
let category;
if (vaccinationStatus == "Yes"){
category = "vaccinated";
}
else{
category = "notVaccinated";
}
let popup_message;
if (vaccinationStatus == "Yes"){
popup_message = `<h2>Vaccinated</h2> <h3>Location: ${vaccinationLocation}</h3> <p>Zip Code: ${homeZipcode}</p>`
}
else{
popup_message = `<h2>Not Vaccinated</h2><p>Zip Code: ${homeZipcode}</p>`
}
new maplibregl.Marker()
.setLngLat([longitude, latitude])
.setPopup(new maplibregl.Popup()
.setHTML(popup_message))
.addTo(map)
createButtons(latitude,longitude,vaccinationLocation);
}
Teneary Operators¶
In simple English, a ternary operator is a way to write an if
statement in a single line. It is a shorthand way to write an if
statement. The syntax of a ternary operator is:
For example, the following code:
let category;
if (vaccinationStatus == "Yes"){
category = "vaccinated";
}
else{
category = "notVaccinated";
}
Can be written as:
The benefit of using a ternary operator is that you can define a variable in a single line, without the need for anif
statement or curly braces!
The ==
operator is the logical comparison (in this case does the field vaccinationStatus
exactly match Yes
?), the ?
is like the if
and the :
is like the else
.
TLDR-nerd-ary¶
If the concept sounds too confusing for you, it’s okay! Just think of the teneary operator a shortcut like the arrow function (=>
) but for if
statements.
Practice¶
See if you can use a ternary operator in your code! And then check here to see if you got it right!
function addMarker(data){
let longitude = data['lng']
let latitude = data['lat'];
let vaccinationLocation = data['Where did you get vaccinated?'];
let homeZipcode = data['What zip code do you live in?'];
let vaccinationStatus = data['Have you been vaccinated?']
let category = vaccinationStatus == "Yes" ? "vaccinated" : "notVaccinated";
let popup_message;
if (vaccinationStatus == "Yes"){
popup_message = `<h2>Vaccinated</h2> <h3>Location: ${vaccinationLocation}</h3> <p>Zip Code: ${homeZipcode}</p>`
}
else{
popup_message = `<h2>Not Vaccinated</h2><p>Zip Code: ${homeZipcode}</p>`
}
new maplibregl.Marker()
.setLngLat([longitude, latitude])
.setPopup(new maplibregl.Popup()
.setHTML(popup_message))
.addTo(map)
createButtons(latitude,longitude,vaccinationLocation);
if (vaccinationStatus == "Yes"){
category = "vaccinated";
}
else{
category = "notVaccinated";
}
let popup_message;
if (vaccinationStatus == "Yes"){
popup_message = `<h2>Vaccinated</h2> <h3>Location: ${vaccinationLocation}</h3> <p>Zip Code: ${homeZipcode}</p>`
}
else{
popup_message = `<h2>Not Vaccinated</h2><p>Zip Code: ${homeZipcode}</p>`
}
new maplibregl.Marker()
.setLngLat([longitude, latitude])
.setPopup(new maplibregl.Popup()
.setHTML(popup_message))
.addTo(map)
createButtons(latitude,longitude,vaccinationLocation);
}
Step 2: Use the categorization!¶
Now that we have added categories to our data, let’s add another line in our addMarker()
function to add a class to the marker element. This class will be used to filter the markers based on the categories.
function addMarker(data){
let longitude = data['lng']
let latitude = data['lat'];
let vaccinationLocation = data['Where did you get vaccinated?'];
let homeZipcode = data['What zip code do you live in?'];
let vaccinationStatus = data['Have you been vaccinated?']
let category = vaccinationStatus == "Yes" ? "vaccinated" : "notVaccinated";
let popup_message;
if (vaccinationStatus == "Yes"){
popup_message = `<h2>Vaccinated</h2> <h3>Location: ${vaccinationLocation}</h3> <p>Zip Code: ${homeZipcode}</p>`
}
else{
popup_message = `<h2>Not Vaccinated</h2><p>Zip Code: ${homeZipcode}</p>`
}
// add a new div element to hold the marker
const newMarkerElement = document.createElement('div');
// add a class to the marker element based on the category
newMarkerElement.className = `marker marker-${category}`;
// create a new marker using the marker element
new maplibregl.Marker(newMarkerElement)
.setLngLat([longitude, latitude])
.setPopup(new maplibregl.Popup()
.setHTML(popup_message))
.addTo(map)
createButtons(latitude,longitude,vaccinationLocation);
}
We can also use this to style the markers differently based on the category, but to get that to work we have to set the element to newMarkerElement
in the new maplibregl.Marker()
function, like so:
This will allow us to style the markers based on the category in our CSS.
For example, we can use different colors for the markers based on the category!
Your code should look like this:
function addMarker(data){
let longitude = data['lng']
let latitude = data['lat'];
let vaccinationLocation = data['Where did you get vaccinated?'];
let homeZipcode = data['What zip code do you live in?'];
let vaccinationStatus = data['Have you been vaccinated?']
let category = vaccinationStatus == "Yes" ? "vaccinated" : "notVaccinated";
let popup_message;
if (vaccinationStatus == "Yes"){
popup_message = `<h2>Vaccinated</h2> <h3>Location: ${vaccinationLocation}</h3> <p>Zip Code: ${homeZipcode}</p>`
}
else{
popup_message = `<h2>Not Vaccinated</h2><p>Zip Code: ${homeZipcode}</p>`
}
// add a new div element to hold the marker
const newMarkerElement = document.createElement('div');
// add a class to the marker element based on the category
newMarkerElement.className = `marker marker-${category}`;
// create a new marker using the marker element
new maplibregl.Marker({element:newMarkerElement})
.setLngLat([longitude, latitude])
.setPopup(new maplibregl.Popup()
.setHTML(popup_message))
.addTo(map)
createButtons(latitude,longitude,vaccinationLocation);
}
Remember CSS?
The categories are now added to the markers as classes. In the case of the example above, the classes are marker-vaccinated
and marker-notVaccinated
. You can use these classes to style the markers based on the category in your CSS. We’ll C-(ss) this later in the lab!
Step 3: Filter the markers based on the categories¶
Adding a place for the legend¶
Let’s add buttons to the map that will allow users to filter the markers based on the categories. First lets go to our index.html
and add a div element to hold the buttons, we’ll call it legend
:
<main>
<div class="portfolio">
<div id="contents">
<div id="legend"></div> <!-- new line added here -->
</div>
</div>
<div id="map"></div>
</main>
Creating the filter UI¶
Next, we will create a function called createFilterUI()
that will create the buttons for each category that we created in the addMarker()
function. It is important to make sure they match exactly, in this (pascal)Case, the categories are vaccinated
and notVaccinated
.
function createFilterUI() {
// Remember! Make sure that the categories match the categories you used in the addMarker function!!!
const categories = ['vaccinated', 'notVaccinated'];
const filterGroup = document.getElementById('filter-group') || document.createElement('div');
filterGroup.setAttribute('id', 'filter-group');
filterGroup.className = 'filter-group'; // We are setting the class of the div element to 'filter-group' you can style this in CSS later!
let placeToAddLegend = document.getElementById('legend');
placeToAddLegend.appendChild(filterGroup);
}
In this function, we create checkboxes for each category and add an event listener to each checkbox. When a checkbox is checked or unchecked, we filter the markers based on the category.
Step 3b: Creating the checkboxes¶
Now, let’s add the checkboxes to the map. We will add the checkboxes to the filter-group
div that we created in the createFilterUI()
function.
function createCheckboxForCategory(category, filterGroup) {
const input = document.createElement('input');
input.type = 'checkbox';
input.id = category;
input.checked = true;
filterGroup.appendChild(input);
const label = document.createElement('label');
label.setAttribute('for', category);
label.textContent = category;
filterGroup.appendChild(label);
}
We will call this function in the createFilterUI()
function to create the checkboxes for each category.
function createFilterUI() {
const categories = ['vaccinated', 'notVaccinated'];
const filterGroup = document.getElementById('filter-group') || document.createElement('div');
filterGroup.setAttribute('id', 'filter-group');
filterGroup.className = 'filter-group';
document.getElementById("legend").appendChild(filterGroup);
categories.forEach(category => {
createCheckboxForCategory(category, filterGroup);
});
}
Step 3c: Filtering the markers¶
Last but not least, we will create a function called toggleMarkersVisibility()
that will filter the markers based on the category. This function will be called when a checkbox is checked or unchecked.
function toggleMarkersVisibility(category, isVisible) {
const markers = document.querySelectorAll(`.marker-${category}`);
markers.forEach(marker => {
marker.style.display = isVisible ? '' : 'none';
});
}
This function listens for a change in the checkbox and then toggles the visibility of the markers based on the category.
We need to then add this function to the createCheckboxForCategory
function:
function createCheckboxForCategory(category, filterGroup) {
const input = document.createElement('input');
input.type = 'checkbox';
input.id = category;
input.checked = true;
filterGroup.appendChild(input);
const label = document.createElement('label');
label.setAttribute('for', category);
label.textContent = category;
filterGroup.appendChild(label);
input.addEventListener('change', function(event) {
toggleMarkersVisibility(category, event.target.checked);
});
}
Step 3d: Putting it all together¶
Now, let’s put it all together. We will call the createFilterUI()
function in the map.on('load')
event listener.
map.on('load', function() {
createFilterUI();
Papa.parse(dataUrl, {
download: true,
header: true,
complete: function(results) {
processData(results.data);
}
});
});
We just added the createFilterUI()
function to the map.on('load')
event listener. This will create the buttons for each category when the map is loaded.
Step 4: Testing and styling the filter¶
Go ahead and test the filter by clicking the check boxes!
If it works, you can go into styling your markers based on the category in the css like so:
.marker-vaccinated, .marker-notVaccinated {
width: 30px;
height: 30px;
border-radius: 50%;
}
.marker-vaccinated{
background-color: green;
}
.marker-notVaccinated {
background-color: #ff0000;
}
And color the checkboxes based on the category:
#vaccinated:checked + label {
color: green;
}
#notVaccinated:checked + label {
color: red;
}
Now, when you load the map, you should see buttons for each category. Clicking on a button will filter the markers based on the category.
Step 5 (Optional): CSS Grid to circle it all back together in the legend¶
If you want, you can add a circle in the legend to show the color of the markers. You can do this by adding a div element with the class marker
and the class marker-vaccinated
or marker-notVaccinated
to the filter-group
div in the function createCheckboxForCategory()
.
function createCheckboxForCategory(category, filterGroup) {
// Create a container for the checkbox, label, and markerLegend
const container = document.createElement('div');
container.style.display = 'grid';
container.style.gridTemplateColumns = 'auto auto 1fr'; // Define the grid columns
container.style.alignItems = 'center'; // Align items vertically in the center
container.style.gap = '8px'; // Add some space between the items
const input = document.createElement('input');
input.type = 'checkbox';
input.id = category;
input.checked = true;
const label = document.createElement('label');
label.setAttribute('for', category);
label.textContent = category;
const markerLegend = document.createElement('div');
markerLegend.className = `marker marker-${category}`;
// Append the elements to the container instead of directly to the filterGroup
container.appendChild(input);
container.appendChild(label);
container.prepend(markerLegend);
// Append the container to the filterGroup
filterGroup.appendChild(container);
input.addEventListener('change', function(event) {
toggleMarkersVisibility(category, event.target.checked);
});
}
Summary¶
In this lab, we added categories to our data and created buttons to filter the markers based on the categories. This allows users to interact with the map and view only the markers they are interested in. This is a powerful feature that can help users explore the data in a more meaningful way.
Checkpoint¶
Your code should look like this:
let mapOptions = {'centerLngLat': [-118.444,34.0709],'startingZoomLevel':5}
const map = new maplibregl.Map({
container: 'map', // container ID
style: 'https://api.maptiler.com/maps/streets-v2-light/style.json?key=wsyYBQjqRwKnNsZrtci1', // Your style URL
center: mapOptions.centerLngLat, // Starting position [lng, lat]
zoom: mapOptions.startingZoomLevel // Starting zoom level
});
function addMarker(data){
let longitude = data['lng']
let latitude = data['lat'];
let vaccinationLocation = data['Where did you get vaccinated?'];
let homeZipcode = data['What zip code do you live in?'];
let vaccinationStatus = data['Have you been vaccinated?']
let category = vaccinationStatus == "Yes" ? "vaccinated" : "notVaccinated";
let popup_message;
if (vaccinationStatus == "Yes"){
popup_message = `<h2>Vaccinated</h2> <h3>Location: ${vaccinationLocation}</h3> <p>Zip Code: ${homeZipcode}</p>`
}
else{
popup_message = `<h2>Not Vaccinated</h2><p>Zip Code: ${homeZipcode}</p>`
}
// add a new div element to hold the marker
const newMarkerElement = document.createElement('div');
// add a class to the marker element based on the category
newMarkerElement.className = `marker marker-${category}`;
// create a new marker using the marker element
new maplibregl.Marker({element:newMarkerElement})
.setLngLat([longitude, latitude])
.setPopup(new maplibregl.Popup()
.setHTML(popup_message))
.addTo(map)
createButtons(latitude,longitude,vaccinationLocation);
}
function createButtons(lat,lng,title){
if (!title){
return;
}
const newButton = document.createElement("button");
newButton.id = "button"+title;
newButton.innerHTML = title;
newButton.setAttribute("lat",lat);
newButton.setAttribute("lng",lng);
newButton.addEventListener('click', function(){
map.flyTo({
center: [lng,lat],
})
})
document.getElementById("contents").appendChild(newButton);
}
const dataUrl = "https://docs.google.com/spreadsheets/d/e/2PACX-1vSNq8_prhrSwK3CnY2pPptqMyGvc23Ckc5MCuGMMKljW-dDy6yq6j7XAT4m6GG69CISbD6kfBF0-ypS/pub?output=csv"
// When the map is fully loaded, start adding GeoJSON data
map.on('load', function() {
createFilterUI();
Papa.parse(dataUrl, {
download: true,
header: true,
complete: function(results) {
processData(results.data);
}
});
});
function processData(results){
console.log(results) //for debugging: this can help us see if the results are what we want
results.forEach(feature => {
addMarker(feature);
});
};
function createCheckboxForCategory(category, filterGroup) {
// Create a container for the checkbox, label, and markerLegend
const container = document.createElement('div');
container.style.display = 'grid';
container.style.gridTemplateColumns = 'auto auto 1fr'; // Define the grid columns
container.style.alignItems = 'center'; // Align items vertically in the center
container.style.gap = '8px'; // Add some space between the items
const input = document.createElement('input');
input.type = 'checkbox';
input.id = category;
input.checked = true;
const label = document.createElement('label');
label.setAttribute('for', category);
label.textContent = category;
const markerLegend = document.createElement('div');
markerLegend.className = `marker marker-${category}`;
// Append the elements to the container instead of directly to the filterGroup
container.appendChild(input);
container.appendChild(label);
container.prepend(markerLegend);
// Append the container to the filterGroup
filterGroup.appendChild(container);
input.addEventListener('change', function(event) {
toggleMarkersVisibility(category, event.target.checked);
});
}
function createFilterUI() {
const categories = ['vaccinated', 'notVaccinated'];
const filterGroup = document.getElementById('filter-group') || document.createElement('div');
filterGroup.setAttribute('id', 'filter-group');
filterGroup.className = 'filter-group';
document.getElementById("legend").appendChild(filterGroup);
categories.forEach(category => {
createCheckboxForCategory(category, filterGroup);
});
}
function toggleMarkersVisibility(category, isVisible) {
const markers = document.querySelectorAll(`.marker-${category}`);
markers.forEach(marker => {
marker.style.display = isVisible ? '' : 'none';
});
}
* {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
background-color: aqua;
}
html, body {
height: 80vh;
padding: 1rem;
box-sizing: border-box;
}
body {
display: grid;
grid-template-areas:
"header"
"main"
"footer";
grid-template-rows: auto 1fr auto;
}
main {
display: grid;
grid-template-areas:
"portfolio map";
grid-template-columns: 1fr 1fr;
}
header {
grid-area: header;
}
main {
grid-area: main;
}
.portfolio {
grid-area: portfolio;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#map {
grid-area: map;
height: 80vh;
}
#footer {
grid-area: footer;
padding: 5px;
background-color: #4677a0;
color: #fff;
text-align: center;
}
.marker-vaccinated, .marker-notVaccinated {
width: 30px;
height: 30px;
border-radius: 50%;
}
.marker-vaccinated{
background-color: green;
}
.marker-notVaccinated {
background-color: #ff0000;
}
#vaccinated:checked + label {
color: green;
}
#notVaccinated:checked + label {
color: red;
}