diff --git a/Makefile b/Makefile
index c3926e2..6a471cd 100644
--- a/Makefile
+++ b/Makefile
@@ -18,3 +18,14 @@ data/raw/chicago-restaurants.csv:
data/raw/community-areas.geojson:
curl https://data.cityofchicago.org/resource/igwz-8jzy.geojson -o $@
+
+build:
+ docker compose build
+
+up: build
+ docker compose run --rm app python manage.py loaddata map/fixtures/restaurant_permits.json map/fixtures/community_areas.json
+ docker compose up -d
+
+down:
+ docker compose down --rmi 'all'
+
diff --git a/README.md b/README.md
index 4f1a3b7..90fa1a9 100644
--- a/README.md
+++ b/README.md
@@ -21,19 +21,7 @@ Development requires a local installation of [Docker](https://docs.docker.com/ge
Once you have Docker and Docker Compose installed, build the application containers from the project's root directory:
```bash
-docker compose build
-```
-
-Load in the data:
-
-```bash
-docker compose run --rm app python manage.py loaddata map/fixtures/restaurant_permits.json map/fixtures/community_areas.json
-```
-
-And finally, run the app:
-
-```bash
-docker compose up
+make up
```
The app will log to the console, and you should be able to visit it at http://localhost:8000
@@ -89,4 +77,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..efd320c 100644
--- a/map/serializers.py
+++ b/map/serializers.py
@@ -1,4 +1,5 @@
from rest_framework import serializers
+from rest_framework.exceptions import ValidationError
from map.models import CommunityArea, RestaurantPermit
@@ -6,13 +7,11 @@
class CommunityAreaSerializer(serializers.ModelSerializer):
class Meta:
model = CommunityArea
- fields = ["name", "num_permits"]
+ fields = []
- num_permits = serializers.SerializerMethodField()
-
- def get_num_permits(self, obj):
+ def to_representation(self, obj: CommunityArea):
"""
- TODO: supplement each community area object with the number
+ 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:
@@ -30,5 +29,16 @@ def get_num_permits(self, obj):
}
]
"""
+ year = self.context.get("year")
- pass
+ if year:
+ query = RestaurantPermit.objects.filter(
+ community_area_id=obj.area_id
+ )
+
+ return {obj.name : {
+ 'area_id' : obj.area_id,
+ 'num_permits' : query.count()
+ }}
+ else:
+ raise(ValidationError('Year not specified.'))
\ No newline at end of file
diff --git a/map/static/js/RestaurantPermitMap.js b/map/static/js/RestaurantPermitMap.js
index 57f8ea0..ec47872 100644
--- a/map/static/js/RestaurantPermitMap.js
+++ b/map/static/js/RestaurantPermitMap.js
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from "react"
+import React, { useEffect, useState, useMemo } from "react"
import { MapContainer, TileLayer, GeoJSON } from "react-leaflet"
@@ -6,31 +6,26 @@ import "leaflet/dist/leaflet.css"
import RAW_COMMUNITY_AREAS from "../../../data/raw/community-areas.geojson"
-function YearSelect({ setFilterVal }) {
- // Filter by the permit issue year for each restaurant
+function YearSelect({ filterVal, setFilterVal }) {
const startYear = 2026
- const years = [...Array(11).keys()].map((increment) => {
- return startYear - increment
- })
- const options = years.map((year) => {
- return (
-
- )
- })
+ const years = [...Array(11).keys()].map((increment) => startYear - increment)
return (
<>
>
)
@@ -45,65 +40,105 @@ 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(`Request failed: ${res.status}`)
+ }
+ return res.json()
+ })
.then((data) => {
- /**
- * TODO: Fetch the data needed to supply to map with data
- */
+ console.log("API data:", data)
+ setCurrentYearData(Array.isArray(data) ? data : [])
+ })
+ .catch((err) => {
+ console.error("Error fetching map data:", err)
+ setCurrentYearData([])
})
}, [yearlyDataEndpoint])
+ const areaPermitMap = useMemo(() => {
+ const mapped = {}
+
+ if (!Array.isArray(currentYearData)) return mapped
+
+ currentYearData.forEach((item) => {
+ if (!item || typeof item !== "object") return
+
+ const keys = Object.keys(item)
+ if (keys.length === 0) return
+
+ const areaName = keys[0]
+ mapped[areaName] = item[areaName]
+ })
+
+ console.log("areaPermitMap:", mapped)
+ return mapped
+ }, [currentYearData])
+
+ const totalNumPermits = useMemo(() => {
+ return Object.values(areaPermitMap).reduce(
+ (sum, area) => sum + (area?.num_permits ?? 0),
+ 0
+ )
+ }, [areaPermitMap])
+
+ const maxNumPermits = useMemo(() => {
+ const permitCounts = Object.values(areaPermitMap).map(
+ (area) => area?.num_permits ?? 0
+ )
+ return permitCounts.length > 0 ? Math.max(...permitCounts) : 0
+ }, [areaPermitMap])
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.33) return communityAreaColors[1]
+ if (percentageOfPermits <= 0.66) return communityAreaColors[2]
+ 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("")
- layer.openPopup()
+ console.log("GeoJSON feature:", feature)
+
+ const areaName = feature?.properties?.community ?? "Unknown"
+ const areaData = areaPermitMap[areaName]
+ const numPermits = areaData?.num_permits ?? 0
+
+ const percentageOfPermits =
+ maxNumPermits > 0 ? numPermits / maxNumPermits : 0
+
+ layer.setStyle({
+ fillColor: getColor(percentageOfPermits),
+ fillOpacity: 0.7,
+ color: "#333",
+ weight: 1,
})
- }
+ layer.bindPopup(`${areaName}
Year: ${year}
Restaurant permits: ${numPermits}`)
+
+ layer.on("mouseover", () => layer.openPopup())
+ layer.on("mouseout", () => layer.closePopup())
+ }
+
return (
<>
Restaurant permits issued this year: {totalNumPermits}
- Restaurant permits issued this year: {/* TODO: display this value */} + Maximum number of restaurant permits in a single area: {maxNumPermits}
-- Maximum number of restaurant permits in a single area: - {/* TODO: display this value */} -
-