Created
July 3, 2024 23:10
-
-
Save jserrao/423779c263c8f1ab1e6295b0c25be097 to your computer and use it in GitHub Desktop.
City Ratings Historic Line Graph Example (with some extra comments)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useContext, useState, useEffect } from "react"; | |
import styled, { ThemeContext } from "styled-components"; | |
import dynamic from "next/dynamic"; | |
import historicCRResults from "../../data/processed/2024/city-ratings-all-historical-results-v24.2-short-mod-with-slugs-nivo-compatible.json"; | |
import { | |
historicCanadianCityList, | |
historicSmallCityList, | |
historicMediumCityList, | |
historicLargeCityList, | |
} from "../../data/static/historic-ratings-city-reference.js"; | |
// to avoid D3 ES/CJS module conflict, avoid SSR - not ideal but workable | |
// more here: https://github.com/plouc/nivo/issues/2310 | |
const ResponsiveLine = dynamic( | |
() => import("@nivo/line").then((m) => m.ResponsiveLine), | |
{ ssr: false } | |
); | |
export default function HistoricRatingsGraph({ | |
featureCitySize, | |
featureCityCountry, | |
featureCity, | |
}) { | |
const themeProps = useContext(ThemeContext); | |
const [comparisonCities, setComparisonCities] = useState([]); | |
const customTheme = { | |
fontFamily: themeProps.montserrat, | |
axis: { | |
ticks: { | |
text: { | |
fill: "#333333", | |
fontFamily: themeProps.montserrat, | |
}, | |
}, | |
}, | |
tooltip: { | |
container: { | |
fontFamily: themeProps.montserrat, | |
fontSize: "16px", | |
border: "1px solid #222", | |
padding: "5px", | |
}, | |
}, | |
}; | |
useEffect(() => { | |
setComparisonCities([]); // reset state each time | |
if (featureCityCountry === "Canada") { | |
setComparisonCities(historicCanadianCityList); | |
} else if (featureCitySize === "large") { | |
setComparisonCities(historicLargeCityList); | |
} else if (featureCitySize === "medium") { | |
setComparisonCities(historicMediumCityList); | |
} else if (featureCitySize === "small") { | |
setComparisonCities(historicSmallCityList); | |
} | |
setComparisonCities((prevCities) => [ | |
historicCRResults.find((city) => city.citySlug === featureCity), | |
...prevCities, | |
]); | |
}, [featureCitySize, featureCityCountry, featureCity]); | |
if (comparisonCities.length === 0) { | |
return null; | |
} | |
return ( | |
comparisonCities && ( | |
<> | |
<hr style={{ marginBottom: "6vh" }} /> | |
<h2 style={{ marginBottom: "5px" }}>{comparisonCities[0].id}</h2> | |
<p | |
style={{ | |
fontWeight: "700", | |
textTransform: "uppercase", | |
marginBottom: "5px", | |
}} | |
> | |
City Rating Through Time | |
</p> | |
<p> | |
Score changes over time reflect investments in bike infrastructure, | |
adjustments to speed limits, and OpenStreetMap data quality | |
improvements. | |
</p> | |
<CustomLegend cities={comparisonCities} /> | |
<ResponsiveLine | |
data={comparisonCities} | |
margin={{ top: 50, right: 30, bottom: 50, left: 40 }} | |
xScale={{ type: "point" }} | |
yScale={{ | |
type: "linear", | |
min: 0, | |
max: 100, | |
}} | |
axisTop={null} | |
axisRight={null} | |
axisBottom={{ | |
tickSize: 5, | |
tickPadding: 5, | |
tickRotation: 0, | |
truncateTickAt: 0, | |
}} | |
axisLeft={{ | |
tickSize: 5, | |
tickPadding: 5, | |
tickRotation: 0, | |
truncateTickAt: 0, | |
}} | |
pointSize={8} | |
colors={(data) => data.color} | |
pointColor={{ from: "color" }} | |
pointLabel="data.yFormatted" | |
enableTouchCrosshair={true} | |
useMesh={true} | |
legends={[]} | |
gridXValues={[]} | |
theme={customTheme} | |
tooltip={CustomTooltip} | |
/> | |
</> | |
) | |
); | |
} | |
// Tooltip | |
const CustomTooltip = ({ point }) => ( | |
<div | |
style={{ | |
background: "#fff", | |
padding: "5px 10px", | |
border: "1px solid #ccc", | |
}} | |
> | |
<strong>{point.serieId}</strong> | |
<br /> | |
Year: {point.data.x} | |
<br /> | |
Score: {point.data.y} | |
</div> | |
); | |
// Legend | |
const LegendContainer = styled.div` | |
display: flex; | |
flex-direction: column; | |
@media (min-width: ${(props) => props.theme.xs}) { | |
flex-direction: row; | |
gap: 10px; | |
margin: 10px 0; | |
} | |
`; | |
const Legend = styled.div` | |
background: ${(props) => props.color}; | |
color: white; | |
font-size: 16px; | |
font-weight: 700; | |
line-height: 20px; | |
padding: 5px 10px; | |
`; | |
const CustomLegend = ({ cities }) => ( | |
<LegendContainer> | |
{cities.map((city, index) => ( | |
<Legend key={index} color={city.color}> | |
{city.id} | |
</Legend> | |
))} | |
</LegendContainer> | |
); | |
// example of what the data shape looks like in historicCRResults json file | |
[ | |
// { | |
// "id": "Crested Butte, CO", | |
// "citySlug": "crested-butte-co", | |
// "color": "rgb(79,148,193)", | |
// "data": [ | |
// { "x": "2018", "y": 87.51 }, | |
// { "x": "2019", "y": 28.79 }, | |
// { "x": "2020", "y": 88.05 }, | |
// { "x": "2021", "y": 89.04 }, | |
// { "x": "2022", "y": 87.18 }, | |
// { "x": "2023", "y": 87.2 }, | |
// { "x": "2024", "y": 87.21 } | |
// ] | |
// }, | |
// { | |
// "id": "Provincetown, MA", | |
// "citySlug": "provincetown-ma", | |
// "color": "rgb(79,148,193)", | |
// "data": [ | |
// { "x": "2018", "y": 85.15 }, | |
// { "x": "2019", "y": 86.81 }, | |
// { "x": "2020", "y": 87.32 }, | |
// { "x": "2021", "y": 87.25 }, | |
// { "x": "2022", "y": 87.8 }, | |
// { "x": "2023", "y": 87.76 }, | |
// { "x": "2024", "y": 96.03 } | |
// ] | |
// }, | |
// ... | |
// ] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment