diff --git a/src/App.js b/src/App.js
index ebae6bcf..d08caab8 100644
--- a/src/App.js
+++ b/src/App.js
@@ -16,22 +16,23 @@ import HowToHelp from "./Pages/HowToHelp";
const App = () => {
return (
diff --git a/src/Pages/InteractiveMap.js b/src/Pages/InteractiveMap.js
index 9b116f5a..ef60f82d 100644
--- a/src/Pages/InteractiveMap.js
+++ b/src/Pages/InteractiveMap.js
@@ -1,22 +1,34 @@
import React from "react";
import Map from "../components/Map";
+import WardMap from "../components/WardMap";
const InteractiveMap = () => {
return (
-
Interactive Map
-
+
Interactive Maps
+
+
+
Bus Routes
+
+ -
+ Click a route or use the search bar to open route-specific details
+
+ -
+ Use the filter to compare the performance of different bus lines
+ across Chicago
+
+
+
+
+
+
+
Wards
+
+ Click a ward to see the routes that pass through it. This map uses the 2023 ward boundaries.
+
+
+
-
Welcome to the Map Beta!
-
- Use the map above to explore Chicago's bus lines.
-
-
- - Click a
- route or use the search bar to open route-specific details.
- - Use the filter to compare the performance of different bus lines
- across Chicago.
-
See a Bug? Have an Idea?
{" "}
@@ -29,31 +41,42 @@ const InteractiveMap = () => {
For a list of planned upcoming additions to this project, please visit
our GitHub pages.
-
If you want to get involved, you can join our Tuesday Chi Hack Night breakout group.
+
+ If you want to get involved, you can join our Tuesday Chi Hack Night
+ breakout group.
+
Finally, if you have feedback for us on this project, you can always
reach out to us on Twitter!
diff --git a/src/Routes/daily_july2022_cta_ridership_data.json b/src/Routes/daily_july2022_cta_ridership_data.json
index a74374d7..08b18d4f 100644
--- a/src/Routes/daily_july2022_cta_ridership_data.json
+++ b/src/Routes/daily_july2022_cta_ridership_data.json
@@ -16806,4 +16806,4 @@
"day_type": "weekday",
"rides": 4
}
-]
+]
\ No newline at end of file
diff --git a/src/Routes/july_2022_ridership (1).json b/src/Routes/july_2022_ridership (1).json
index 00bbc6a7..d55df5c9 100644
--- a/src/Routes/july_2022_ridership (1).json
+++ b/src/Routes/july_2022_ridership (1).json
@@ -14055,4 +14055,4 @@
"952779": 3,
"952780": 4
}
-}
+}
\ No newline at end of file
diff --git a/src/components/BusRouteDetails.js b/src/components/BusRouteDetails.js
index e0459703..2b626276 100644
--- a/src/components/BusRouteDetails.js
+++ b/src/components/BusRouteDetails.js
@@ -21,7 +21,9 @@ function BusRouteDetails({ selectedRoute, busFraction }) {
// FIX ME : eventually we're going to want these numbers rendered dynamically when the backend/data updates
const percentileKeys = [63, 73, 75, 77, 80, 83, 87, 90, 93, 94];
- const percentileIndex = findPercentileIndex(selectedRoute[0]);
+ const percentileIndex = findPercentileIndex(
+ selectedRoute[0].properties.percentiles * 100
+ );
const barGraphBars = percentileKeys.map((x, index) => {
return (
))}
- {[...Array(busFraction[0])].map((x) => (
-
- ))}
-
+ {[...Array(busFraction[0])].map((x) => (
+
+ ))}
+
) : (
diff --git a/src/components/Filter.js b/src/components/Filter.js
index 261c03d3..1bb9866f 100644
--- a/src/components/Filter.js
+++ b/src/components/Filter.js
@@ -1,10 +1,12 @@
import React from "react";
+import RankingLegend from "./RankingLegend";
const Filter = ({
currentFilters,
setCurrentFilters,
filterOpen,
setFilterOpen,
+ wardFilter,
}) => {
return (
<>
@@ -14,44 +16,65 @@ const Filter = ({
{" "}
<>
{" "}
-
Reliability
-
-
+ {!wardFilter && (
+ <>
+ Reliability
+
+
+ >
+ )}
Map Settings
+ {!wardFilter && (
+
+ )}
- {currentFilters.color && (
-
-
Map Key by Percentile
-
- -
- 0-19%
-
-
- -
- 20-39%
-
-
- -
- 40-59%
-
-
- -
- 60-79%
-
-
- -
- 80-99%
-
-
-
-
- )}
+ {currentFilters.color && }
>
);
};
diff --git a/src/components/Header.js b/src/components/Header.js
index 91e9f76b..e6a82720 100644
--- a/src/components/Header.js
+++ b/src/components/Header.js
@@ -27,8 +27,8 @@ export default function Header() {
);
diff --git a/src/components/Map.js b/src/components/Map.js
index 258fb3ed..452dcd96 100644
--- a/src/components/Map.js
+++ b/src/components/Map.js
@@ -6,7 +6,13 @@ import resultsData from "../Routes/data.json";
import Search from "./Search";
import Modal from "./Modal";
import Filter from "./Filter";
-import findPercentileIndex from "../utils/percentileKeys";
+
+import {
+ highlightFeature,
+ resetHighlight,
+ setColor,
+ findDataForRoute,
+} from "../utils/routeStyles";
export default function Map() {
const [searchTerm, setSearchTerm] = useState("");
@@ -14,27 +20,24 @@ export default function Map() {
const [filterOpen, setFilterOpen] = useState(false);
const [currentFilters, setCurrentFilters] = useState({
+ busLines: true,
color: true,
reliability: {
top10: false,
bottom10: false,
},
});
+ const { reliability } = currentFilters;
// filter functionality
const filterMapRoutes = (route) => {
- if (
- !currentFilters.reliability.top10 &&
- !currentFilters.reliability.bottom10
- ) {
+ if (!reliability.top10 && !reliability.bottom10) {
return true;
}
- const topTen =
- !currentFilters.reliability.top10 || route.properties.ranking <= 10;
- const bottomTen =
- !currentFilters.reliability.bottom10 || route.properties.ranking >= 114;
+ const topTen = !reliability.top10 || route.properties.ranking <= 10;
+ const bottomTen = !reliability.bottom10 || route.properties.ranking >= 114;
return topTen && bottomTen;
};
@@ -76,22 +79,15 @@ export default function Map() {
className="search-result"
onClick={() => onClickBusRoute(result)}
>
- {result.properties.route_id}{result.properties.route_long_name}
+
+ {result.properties.route_id}
+ {result.properties.route_long_name}
+
));
- // modal functionality
-
// clicking a bus route opens the modal
- function findDataForRoute(feature) {
- const results = resultsData.features.filter(
- (data) =>
- String(data.properties.route_id) === String(feature.properties.route_id)
- );
- return results;
- }
-
const onClickBusRoute = (feature) => {
setSelectedRoute(findDataForRoute(feature));
document.body.style.overflow = "hidden";
@@ -113,24 +109,7 @@ export default function Map() {
fillOpacity: 1,
};
- const heatmap = ["#0852C1", "#8E47F3", "#D84091", "#EB4F12", "#FFED39"];
-
- function setColor(route) {
- const percentileIndex = findPercentileIndex(route);
- if (percentileIndex === 0 || percentileIndex === 1) {
- return heatmap[0];
- } else if (percentileIndex === 2 || percentileIndex === 3) {
- return heatmap[1];
- } else if (percentileIndex === 4 || percentileIndex === 5) {
- return heatmap[2];
- } else if (percentileIndex === 6 || percentileIndex === 7) {
- return heatmap[3];
- } else {
- return heatmap[4];
- }
- }
-
- function onEachFeature(feature, layer) {
+ function onEachRouteFeature(feature, layer) {
if (feature.properties) {
const { route_long_name, route_id } = feature.properties;
layer.bindTooltip(`${route_id}, ${route_long_name}`, {
@@ -140,18 +119,19 @@ export default function Map() {
layer.on({
click: () => onClickBusRoute(feature),
mouseover: highlightFeature,
- mouseout: resetHighlight,
+ mouseout: (e) => resetHighlight(e, currentFilters),
});
const routeMatch = findDataForRoute(feature)[0];
+ const routeMatchPercent = routeMatch.properties.percentiles * 100;
routeMatch &&
layer.setStyle(
currentFilters.color
? {
weight: 4,
- fillColor: setColor(routeMatch),
- color: setColor(routeMatch),
+ fillColor: setColor(routeMatchPercent),
+ color: setColor(routeMatchPercent),
fillOpacity: 1,
}
: style
@@ -159,35 +139,8 @@ export default function Map() {
}
}
- function highlightFeature(e) {
- let layer = e.target;
-
- layer.setStyle({
- weight: 4,
- fillColor: "#fff",
- color: "#fff",
- fillOpacity: 1,
- });
- }
-
- function resetHighlight(e) {
- let layer = e.target;
- const routeMatch = findDataForRoute(layer.feature)[0];
- layer.setStyle(
- currentFilters.color
- ? {
- color: setColor(routeMatch),
- fillColor: setColor(routeMatch),
- weight: 3,
- fillOpacity: 1,
- }
- : style
- );
- }
-
return (
-
Map/Data
{selectedRoute && (
)}
@@ -212,12 +165,14 @@ export default function Map() {
attribution='©
Stadia Maps, ©
OpenMapTiles ©
OpenStreetMap contributors'
url="https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png"
/>
-
+ {currentFilters.busLines && (
+
+ )}
);
diff --git a/src/components/Nav.js b/src/components/Nav.js
index 3dbcbca7..5bf6aa1d 100644
--- a/src/components/Nav.js
+++ b/src/components/Nav.js
@@ -8,7 +8,7 @@ const Nav = () => {
const links = [
{ title: "Home", link: "/" },
{ title: "About Us", link: "/about" },
- { title: "Map", link: "/map" },
+ { title: "Interactive Maps", link: "/map" },
{ title: "Methods", link: "/methods" },
{ title: "Further Readings", link: "/further-reading" },
{ title: "How To Help", link: "/how-to-help" },
@@ -32,7 +32,7 @@ const Nav = () => {
{links.map((link) => {
return (
-
+
{link.title}
diff --git a/src/components/RankingLegend.js b/src/components/RankingLegend.js
new file mode 100644
index 00000000..32b425cc
--- /dev/null
+++ b/src/components/RankingLegend.js
@@ -0,0 +1,34 @@
+import React from "react";
+
+const RankingLegend = () => {
+ return (
+
+
Map Key by Percentile
+
+ -
+ 0-19%
+
+
+ -
+ 20-39%
+
+
+ -
+ 40-59%
+
+
+ -
+ 60-79%
+
+
+ -
+ 80-99%
+
+
+
+
+ );
+};
+
+
+export default RankingLegend;
\ No newline at end of file
diff --git a/src/components/WardMap.js b/src/components/WardMap.js
new file mode 100644
index 00000000..dce4c5bb
--- /dev/null
+++ b/src/components/WardMap.js
@@ -0,0 +1,230 @@
+import React, { useState } from "react";
+import { MapContainer, TileLayer, GeoJSON } from "react-leaflet";
+
+import mapRoutes from "../Routes/bus_route_shapes_simplified_linestring.json";
+import resultsData from "../Routes/data.json";
+import wardRankings from "../Routes/ward_data.json";
+import Modal from "./Modal";
+import Filter from "./Filter";
+
+import {
+ highlightFeature,
+ resetHighlight,
+ setColor,
+ findDataForRoute,
+} from "../utils/routeStyles";
+
+export default function WardMap() {
+ const [selectedRoute, setSelectedRoute] = useState();
+
+ const [filterOpen, setFilterOpen] = useState(false);
+ const [currentFilters, setCurrentFilters] = useState({
+ busLines: false,
+ color: true,
+ wards: {
+ selectedWard: null,
+ wardsShowing: true,
+ },
+ });
+ const { wards } = currentFilters;
+
+ const selectedWardFeature = wardRankings.features.find(
+ (feature) => feature.properties.ward === wards.selectedWard
+ );
+
+ // filter functionality
+
+ const filterMapRoutes = (route) => {
+ if (!wards.selectedWard) {
+ return true;
+ }
+
+ const wardMatches =
+ !wards.selectedWard ||
+ selectedWardFeature.properties.routes.includes(
+ ` ${route.properties.route_id} `
+ );
+ return wardMatches;
+ };
+
+ const availableRoutes = resultsData.features
+ .filter(filterMapRoutes)
+ .map((route) => route.properties.route_id)
+ .filter((v, i, a) => a.indexOf(v) === i);
+
+ const mapToDisplay = mapRoutes.features.filter((route) =>
+ availableRoutes.includes(route.properties.route_id)
+ );
+
+ // modal functionality
+
+ // clicking a bus route opens the modal
+
+ const onClickBusRoute = (feature) => {
+ setSelectedRoute(findDataForRoute(feature));
+ document.body.style.overflow = "hidden";
+ };
+
+ const onClickWard = (feature) => {
+ debugger;
+ setCurrentFilters((prevfilters) => {
+ return {
+ ...prevfilters,
+ busLines: true,
+ wards: {
+ ...prevfilters.wards,
+ selectedWard: feature.properties.ward,
+ },
+ };
+ });
+ };
+
+ const closeModal = () => {
+ setSelectedRoute();
+ document.body.style.overflow = "scroll";
+ };
+
+ //leaflet
+
+ //highlight the hovered bus route
+
+ const style = {
+ color: "rgb(51, 136, 255)",
+ fillColor: "rgb(51, 136, 255)",
+ weight: 3,
+ fillOpacity: 1,
+ };
+
+ function onEachRouteFeature(feature, layer) {
+ if (feature.properties) {
+ const { route_long_name, route_id } = feature.properties;
+ layer.bindTooltip(`${route_id}, ${route_long_name}`, {
+ sticky: true,
+ });
+
+ layer.on({
+ click: () => onClickBusRoute(feature),
+ mouseover: highlightFeature,
+ mouseout: (e) => resetHighlight(e, currentFilters),
+ });
+
+ const routeMatch =
+ findDataForRoute(feature)[0].properties.percentiles * 100;
+
+ routeMatch &&
+ layer.setStyle(
+ currentFilters.color
+ ? {
+ weight: 4,
+ fillColor: setColor(routeMatch),
+ color: setColor(routeMatch),
+ fillOpacity: 1,
+ }
+ : style
+ );
+ }
+ }
+
+ const wardStyle = {
+ weight: 1,
+ fillOpacity: 0.2,
+ color: "white",
+ };
+
+ function onEachWardFeature(feature, layer) {
+ if (feature.properties) {
+ const { ward, median_percentiles } = feature.properties;
+ layer.bindTooltip(`Ward ${ward}`, {
+ sticky: true,
+ });
+ layer.setStyle(
+ wards.selectedWard || !currentFilters.color
+ ? wardStyle
+ : {
+ ...wardStyle,
+ color: setColor(median_percentiles * 100),
+ }
+ );
+
+ layer.on({
+ click: () => onClickWard(feature),
+ mouseover: highlightWard,
+ mouseout: resetHighlightWard,
+ });
+ }
+ }
+
+ function highlightWard(e) {
+ let layer = e.target;
+
+ layer.setStyle({
+ fillOpacity: 0.7,
+ });
+ }
+
+ function resetHighlightWard(e) {
+ let layer = e.target;
+
+ layer.setStyle({
+ fillOpacity: 0.2,
+ });
+ }
+
+ return (
+
+ {selectedRoute && (
+
+ )}
+
+
+
+
+
+ {currentFilters.wards.wardsShowing && (
+
+ )}
+ {currentFilters.busLines && (
+
+ )}
+
+
+ );
+}
diff --git a/src/css/main.css b/src/css/main.css
index 3161095e..62cd309b 100644
--- a/src/css/main.css
+++ b/src/css/main.css
@@ -601,6 +601,15 @@ header .h1-container .subtitle-container img {
min-width: 250px;
}
+.route-close-btn {
+ position: absolute;
+ right: 15px;
+ top: 15px;
+ z-index: 150;
+ padding: 10px;
+ border-radius: 10px;
+}
+
.search-container {
position: absolute;
right: 15px;
diff --git a/src/scss/_map.scss b/src/scss/_map.scss
index 9f92ddc7..df21f4cf 100644
--- a/src/scss/_map.scss
+++ b/src/scss/_map.scss
@@ -23,3 +23,12 @@
min-width: 250px;
}
}
+
+.route-close-btn {
+ position: absolute;
+ right: 15px;
+ top: 15px;
+ z-index: 150;
+ padding: 10px;
+ border-radius: 10px;
+}
diff --git a/src/utils/percentileKeys.js b/src/utils/percentileKeys.js
index b06014aa..96274049 100644
--- a/src/utils/percentileKeys.js
+++ b/src/utils/percentileKeys.js
@@ -1,11 +1,10 @@
-
-export default function findPercentileIndex(route) {
+export default function findPercentileIndex(percent) {
const percentileIndex = Number(
- (route.properties.percentiles * 100).toLocaleString("en-US", {
+ percent.toLocaleString("en-US", {
minimumIntegerDigits: 2,
useGrouping: false,
})[0]
);
- return percentileIndex
+ return percentileIndex;
}
diff --git a/src/utils/routeStyles.js b/src/utils/routeStyles.js
new file mode 100644
index 00000000..72d2aa33
--- /dev/null
+++ b/src/utils/routeStyles.js
@@ -0,0 +1,62 @@
+import resultsData from "../Routes/data.json";
+
+import findPercentileIndex from "./percentileKeys";
+
+export function findDataForRoute(feature) {
+ const results = resultsData.features.filter(
+ (data) =>
+ String(data.properties.route_id) === String(feature.properties.route_id)
+ );
+ return results;
+}
+
+const style = {
+ color: "rgb(51, 136, 255)",
+ fillColor: "rgb(51, 136, 255)",
+ weight: 3,
+ fillOpacity: 1,
+};
+
+const heatmap = ["#0852C1", "#8E47F3", "#D84091", "#EB4F12", "#FFED39"];
+
+export function setColor(route) {
+ const percentileIndex = findPercentileIndex(route);
+ if (percentileIndex === 0 || percentileIndex === 1) {
+ return heatmap[0];
+ } else if (percentileIndex === 2 || percentileIndex === 3) {
+ return heatmap[1];
+ } else if (percentileIndex === 4 || percentileIndex === 5) {
+ return heatmap[2];
+ } else if (percentileIndex === 6 || percentileIndex === 7) {
+ return heatmap[3];
+ } else {
+ return heatmap[4];
+ }
+}
+
+export function highlightFeature(e) {
+ let layer = e.target;
+
+ layer.setStyle({
+ weight: 4,
+ fillColor: "#fff",
+ color: "#fff",
+ fillOpacity: 1,
+ });
+}
+
+export function resetHighlight(e, currentFilters) {
+ let layer = e.target;
+ const routeMatch =
+ findDataForRoute(layer.feature)[0].properties.percentiles * 100;
+ layer.setStyle(
+ currentFilters.color
+ ? {
+ color: setColor(routeMatch),
+ fillColor: setColor(routeMatch),
+ weight: 3,
+ fillOpacity: 1,
+ }
+ : style
+ );
+}