Skip to content

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.

js/init.js
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:

condition ? expressionIfTrue : expressionIfFalse

For example, the following code:

let category;
if (vaccinationStatus == "Yes"){
    category = "vaccinated";
}
else{
    category = "notVaccinated";
}

Can be written as:

let category = vaccinationStatus == "Yes" ? "vaccinated" : "notVaccinated";
The benefit of using a ternary operator is that you can define a variable in a single line, without the need for an if 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!

js/init.js
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.

js/init.js
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:

js/init.js
    new maplibregl.Marker({element:newMarkerElement})

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:

js/init.js
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:

index.html
        <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.

js/init.js
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.

js/init.js
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.

js/init.js
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.

js/init.js
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:

js/init.js
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.

js/init.js
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:

css/styles.css
.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:

css/styles.css
#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().

js/init.js
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:

js/init.js
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';
    });
}
css/styles.css
* {
    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;
}