Skip to content

Instantly share code, notes, and snippets.

@jserrao
Created July 3, 2024 23:10
Show Gist options
  • Save jserrao/423779c263c8f1ab1e6295b0c25be097 to your computer and use it in GitHub Desktop.
Save jserrao/423779c263c8f1ab1e6295b0c25be097 to your computer and use it in GitHub Desktop.
City Ratings Historic Line Graph Example (with some extra comments)
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