diff --git a/map/serializers.py b/map/serializers.py index 03dd912..45e3823 100644 --- a/map/serializers.py +++ b/map/serializers.py @@ -3,32 +3,48 @@ from map.models import CommunityArea, RestaurantPermit +""" +Serialize data, appending total number +of permits per community area + +e.g. The endpoint /map-data/?year=2017 should return something like: + [ + { + "ROGERS PARK": { + area_id: 17, + num_permits: 2 + }, + "BEVERLY": { + area_id: 72, + num_permits: 2 + }, + ... + } + ] + """ class CommunityAreaSerializer(serializers.ModelSerializer): class Meta: model = CommunityArea - fields = ["name", "num_permits"] + fields = ["name", "area_id", "num_permits"] num_permits = serializers.SerializerMethodField() def get_num_permits(self, obj): - """ - TODO: supplement each community area object with the number - of permits issued in the given year. - - e.g. The endpoint /map-data/?year=2017 should return something like: - [ - { - "ROGERS PARK": { - area_id: 17, - num_permits: 2 - }, - "BEVERLY": { - area_id: 72, - num_permits: 2 - }, - ... - } - ] - """ - pass + return len( + RestaurantPermit.objects.filter( + issue_date__year=self.context["year"], + community_area_id=str(obj.area_id) + ) + ) + + # Override to_representation to return data in the specified structure + def to_representation(self, obj): + data = super().to_representation(obj) + return { + data["name"]: { + "area_id": data["area_id"], + "num_permits": data["num_permits"] + } + } + diff --git a/map/static/js/RestaurantPermitMap.js b/map/static/js/RestaurantPermitMap.js index 57f8ea0..862d47b 100644 --- a/map/static/js/RestaurantPermitMap.js +++ b/map/static/js/RestaurantPermitMap.js @@ -45,65 +45,150 @@ export default function RestaurantPermitMap() { const yearlyDataEndpoint = `/map-data/?year=${year}` useEffect(() => { - fetch() - .then((res) => res.json()) + fetch(yearlyDataEndpoint) + .then((res) => { + if(!res.ok) + throw new Error(`Server error: ${res.status}`) + return res.json() + }) .then((data) => { - /** - * TODO: Fetch the data needed to supply to map with data - */ + setCurrentYearData(data) + }) + .catch(error => { + console.error('Error fetching data:', error) }) }, [yearlyDataEndpoint]) + /** + * Add up number of permits per community area, returning citywide total for a given year + */ + const totalSum = currentYearData.reduce((accumulator, currentValue) => { + return accumulator + Object.values(currentValue)[0].num_permits; + }, 0) + /** + * Iterate through number of permits per community area, returning the maximum value found + */ + const maxNumPermits = currentYearData.reduce((accumulator, currentValue) => { + return accumulator > Object.values(currentValue)[0].num_permits ? accumulator : Object.values(currentValue)[0].num_permits; + }, 0) + + /** + * Helper funcion for getColor. Computes percentage of permits + * per ward out of the max number of permits for a given year. + */ + function getPercentageOfPermits(communityPermits) { + if (maxNumPermits === 0){return 0} + return Math.round((communityPermits / maxNumPermits) * 100) + } + + /** + * Splits percentages into 4 'buckets' corresponding + * to each array entry in communityAreaColors + * Bucket 1: | 0% - 24% | #eff3ff + * Bucket 2: | 25% - 49% | #bdd7e7 + * Bucket 3: | 50% - 74% | #6baed6 + * Bucket 4: | 75% - 100% | #2171b5 + */ function getColor(percentageOfPermits) { - /** - * TODO: Use this function in setAreaInteraction to set a community - * area's color using the communityAreaColors constant above - */ + if (percentageOfPermits < 25) { + return communityAreaColors[0] + } else if (percentageOfPermits <50) { + return communityAreaColors[1] + } else if (percentageOfPermits < 75) { + return communityAreaColors[2] + } else { + return communityAreaColors[3] + } } 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("") + + // Get community area object that corresponds to current geojson feature + const currentCommunityObj = currentYearData.find(communityArea => Object.keys(communityArea)[0] === feature.properties.community) + const communityPermits = Object.values(currentCommunityObj)[0].num_permits + + const percentageOfPermits = getPercentageOfPermits(communityPermits) + + layer.setStyle({color: 'black', weight: 1.5, fillColor: getColor(percentageOfPermits), fillOpacity: 1}) + layer.on("click", () => { + layer.bindPopup( + `${feature.properties.community}
+ Year: ${year}
+ Permits issued: ${communityPermits}`) layer.openPopup() }) } return ( - <> - -

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

-

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

- - - {currentYearData.length > 0 ? ( - +
+ +
+ +
+

+ Restaurant permits issued this year: {totalSum} +

+

+ Maximum number of restaurant permits in a single area: + {" "}{maxNumPermits} +

+
+ +
+

Permits (% of max)

+
    +
  • + 0–24% +
  • +
  • + 25–49% +
  • +
  • + 50–74% +
  • +
  • + 75–100% +
  • +
+
+ +
+

+ {currentYearData.length === 0 ? + "Loading map data..." : ""} +

+ + - ) : null} - - + {currentYearData.length > 0 ? ( + + ) : null} + +
+ ) } diff --git a/tests/test_views.py b/tests/test_views.py index 24cc64e..62a3594 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -6,7 +6,9 @@ from map.models import CommunityArea, RestaurantPermit - +# assert that the /map-data/ endpoint +# returns the correct number of permits for Beverly and Lincoln +# Park in 2021 @pytest.mark.django_db def test_map_data_view(): # Create some test community areas @@ -35,7 +37,5 @@ def test_map_data_view(): # Query the map data endpoint client = APIClient() response = client.get(reverse("map_data", query={"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.data[0].get("Beverly").get("num_permits") == 2 + assert response.data[1].get("Lincoln Park").get("num_permits") == 3