diff --git a/README.md b/README.md index 4f1a3b7..48c8724 100644 --- a/README.md +++ b/README.md @@ -89,4 +89,4 @@ _Note: If you would prefer to keep your code challenge private, please share acc | Xavier | https://github.com/xmedr | | Hayley | https://github.com/haowens | -Keep in mind that you cannot create a private fork of a public repository on GitHub, so you’ll need to [follow these instructions](https://gist.github.com/0xjac/85097472043b697ab57ba1b1c7530274) to create a private copy of the repo. \ No newline at end of file +Keep in mind that you cannot create a private fork of a public repository on GitHub, so you’ll need to [follow these instructions](https://gist.github.com/0xjac/85097472043b697ab57ba1b1c7530274) to create a private copy of the repo. diff --git a/map/serializers.py b/map/serializers.py index 03dd912..f3cd265 100644 --- a/map/serializers.py +++ b/map/serializers.py @@ -6,7 +6,7 @@ class CommunityAreaSerializer(serializers.ModelSerializer): class Meta: model = CommunityArea - fields = ["name", "num_permits"] + fields = ["name", "area_id", "num_permits"] num_permits = serializers.SerializerMethodField() @@ -30,5 +30,13 @@ def get_num_permits(self, obj): } ] """ + year = self.context.get("year") + try: + year = int(year) + except (TypeError, ValueError): + return 0 - pass + return RestaurantPermit.objects.filter( + community_area_id=str(obj.area_id), + issue_date__year=year, + ).count() \ No newline at end of file diff --git a/map/static/js/RestaurantPermitMap.js b/map/static/js/RestaurantPermitMap.js index 57f8ea0..984d65c 100644 --- a/map/static/js/RestaurantPermitMap.js +++ b/map/static/js/RestaurantPermitMap.js @@ -1,24 +1,24 @@ -import React, { useEffect, useState } from "react" +import React, { useEffect, useState } from "react"; -import { MapContainer, TileLayer, GeoJSON } from "react-leaflet" +import { MapContainer, TileLayer, GeoJSON } from "react-leaflet"; -import "leaflet/dist/leaflet.css" +import "leaflet/dist/leaflet.css"; -import RAW_COMMUNITY_AREAS from "../../../data/raw/community-areas.geojson" +import RAW_COMMUNITY_AREAS from "../../../data/raw/community-areas.geojson"; -function YearSelect({ setFilterVal }) { +function YearSelect({ filterVal, setFilterVal }) { // Filter by the permit issue year for each restaurant - const startYear = 2026 + const startYear = 2026; const years = [...Array(11).keys()].map((increment) => { - return startYear - increment - }) + return startYear - increment; + }); const options = years.map((year) => { return ( - ) - }) + ); + }); return ( <> @@ -28,70 +28,106 @@ function YearSelect({ setFilterVal }) { - ) + ); } export default function RestaurantPermitMap() { - const communityAreaColors = ["#eff3ff", "#bdd7e7", "#6baed6", "#2171b5"] + const communityAreaColors = ["#eff3ff", "#bdd7e7", "#6baed6", "#2171b5"]; - const [currentYearData, setCurrentYearData] = useState([]) - const [year, setYear] = useState(2026) + const [currentYearData, setCurrentYearData] = useState([]); + const [year, setYear] = useState(2026); - const yearlyDataEndpoint = `/map-data/?year=${year}` + const yearlyDataEndpoint = `/map-data/?year=${year}`; useEffect(() => { - fetch() + fetch(yearlyDataEndpoint) .then((res) => res.json()) .then((data) => { - /** - * TODO: Fetch the data needed to supply to map with data - */ + setCurrentYearData(data); }) - }, [yearlyDataEndpoint]) + .catch(() => { + setCurrentYearData([]); + }); + }, [yearlyDataEndpoint]); + const totalPermits = currentYearData.reduce( + (sum, area) => sum + area.num_permits, + 0, + ); + + const maxNumPermits = currentYearData.reduce( + (maxPermits, area) => Math.max(maxPermits, area.num_permits), + 0, + ); + + const permitsByAreaId = currentYearData.reduce((lookup, area) => { + lookup[String(area.area_id)] = area.num_permits; + return lookup; + }, {}); function getColor(percentageOfPermits) { - /** - * TODO: Use this function in setAreaInteraction to set a community - * area's color using the communityAreaColors constant above - */ + if (percentageOfPermits === 0) { + return communityAreaColors[0]; + } + if (percentageOfPermits <= 0.05) { + return communityAreaColors[1]; + } + if (percentageOfPermits <= 0.12) { + return communityAreaColors[2]; + } + + return communityAreaColors[3]; + } + + function toTitleCase(str) { + return str + .toLowerCase() + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); } function setAreaInteraction(feature, layer) { - /** - * TODO: Use the methods below to: - * 1) Shade each community area according to what percentage of - * permits were issued there in the selected year - * 2) On hover, display a popup with the community area's raw - * permit count for the year - */ - layer.setStyle() - layer.on("", () => { - layer.bindPopup("") - layer.openPopup() - }) + const areaName = toTitleCase(feature.properties.community); + const areaId = feature.properties.area_numbe; + const permitCount = permitsByAreaId[String(areaId)] || 0; + const permitShare = totalPermits > 0 ? permitCount / totalPermits : 0; + + layer.setStyle({ + fillColor: getColor(permitShare), + fillOpacity: 0.7, + weight: 1, + opacity: 1, + }); + + layer.on("mouseover", () => { + layer.bindPopup( + `${areaName}
Restaurant permits issued in ${year}: ${permitCount}
Area share of citywide permits: ${(permitShare * 100).toFixed(1)}%`, + ); + layer.openPopup(); + }); + + layer.on("mouseout", () => { + layer.closePopup(); + }); } return ( <>

- Restaurant permits issued this year: {/* TODO: display this value */} + Restaurant permits issued this year: {totalPermits}

- Maximum number of restaurant permits in a single area: - {/* TODO: display this value */} + Maximum number of restaurant permits in a single area: {maxNumPermits}

- + ) : null} - ) + ); } diff --git a/tests/test_views.py b/tests/test_views.py index 24cc64e..80848c5 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -34,8 +34,14 @@ def test_map_data_view(): # Query the map data endpoint client = APIClient() - response = client.get(reverse("map_data", query={"year": 2021})) + response = client.get(reverse("map_data"), {"year": 2021}) # TODO: Complete the test by asserting that the /map-data/ endpoint # returns the correct number of permits for Beverly and Lincoln # Park in 2021 + assert response.status_code == 200 + + response_data = {item["name"]: item["num_permits"] for item in response.json()} + + assert response_data["Beverly"] == 2 + assert response_data["Lincoln Park"] == 3