diff --git a/spotlight-client/src/charts/TopologicalMap.tsx b/spotlight-client/src/charts/TopologicalMap.tsx deleted file mode 100644 index 1c3dbc91..00000000 --- a/spotlight-client/src/charts/TopologicalMap.tsx +++ /dev/null @@ -1,218 +0,0 @@ -// Recidiviz - a data platform for criminal justice reform -// Copyright (C) 2021 Recidiviz, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// ============================================================================= - -import { geoAlbers, geoCentroid } from "d3-geo"; -import { rem } from "polished"; -import React, { useState } from "react"; -import { - ComposableMap, - Geographies, - Geography, - Marker, - GeographyProps, -} from "react-simple-maps"; -import { Spring } from "react-spring/renderprops.cjs"; -import styled from "styled-components/macro"; -import { mesh } from "topojson"; -import type { Topology } from "topojson-specification"; -import { ValuesType } from "utility-types"; -import { LocalityDataMapping } from "../contentModels/types"; -import MeasureWidth from "../MeasureWidth"; -import { colors } from "../UiLibrary"; - -const RatioContainerOuter = styled.div` - position: relative; - height: 0; -`; - -const RatioContainerInner = styled.div({ - position: "absolute", - top: 0, - left: 0, - right: 0, - bottom: 0, -}); - -/** - * Implements a version of the Aspect Ratio Box technique described here: - * https://github.com/zcreativelabs/react-simple-maps/issues/37#issuecomment-349435145 - * but with explicit width (our flex layout prefers it or elements may collapse). - * This is needed to size the map SVG properly in IE 11 and some mobile devices. - */ -const RatioContainer: React.FC<{ width: number; aspectRatio: number }> = ({ - aspectRatio, - children, - width, -}) => { - return ( - - {children} - - ); -}; - -const Wrapper = styled.div``; - -type MapProps = { - aspectRatio: number; - localityData: LocalityDataMapping; - topology: Topology; -}; - -/** - * Given a topojson topology and a mapping of topological object IDs to values, - * draws a map and labels the topological objects accordingly. - */ -export default function TopologicalMap({ - aspectRatio, - localityData, - topology, -}: MapProps): React.ReactElement { - return ( - - {({ measureRef, width }) => { - const stateProjection = geoAlbers().fitExtent( - [ - [0, 0], - [width, width / aspectRatio], - ], - mesh(topology) - ); - - return ( - - - - - {({ geographies }) => { - return geographies.map((geography) => { - return ( - - ); - }); - }} - - - - - ); - }} - - ); -} - -const RegionGroup = styled.g` - &:focus { - outline: none; - } -`; - -const RegionGeography = styled(Geography)` - &:focus { - outline: none; - } -`; - -const RegionMarker = styled(Marker)``; - -const RegionLabel = styled.text` - font-size: ${rem(18)}; - font-weight: 600; - letter-spacing: -0.015em; - text-anchor: middle; -`; - -const Region = ({ - data, - geography, -}: { - data: ValuesType; - geography: GeographyProps["geography"]; -}) => { - const centroid = geoCentroid(geography); - const { label, value } = data; - const [hoverRegion, setHoverRegion] = useState(false); - - const setHover = () => { - setHoverRegion(true); - }; - - const clearHover = () => { - setHoverRegion(false); - }; - - return ( - - {/* - using spring renderprops instead of hook because react-simple-maps - components are not compatible with the `animated` wrapper - */} - - {(props) => ( - <> - - - {value} - - - )} - - - ); -}; diff --git a/spotlight-client/src/charts/TopologicalMap/RatioContainer.tsx b/spotlight-client/src/charts/TopologicalMap/RatioContainer.tsx new file mode 100644 index 00000000..bd006358 --- /dev/null +++ b/spotlight-client/src/charts/TopologicalMap/RatioContainer.tsx @@ -0,0 +1,55 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2021 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import React from "react"; +import styled from "styled-components/macro"; + +const RatioContainerOuter = styled.div` + position: relative; + height: 0; +`; + +const RatioContainerInner = styled.div({ + position: "absolute", + top: 0, + left: 0, + right: 0, + bottom: 0, +}); + +/** + * Implements a version of the Aspect Ratio Box technique described here: + * https://github.com/zcreativelabs/react-simple-maps/issues/37#issuecomment-349435145 + * but with explicit width (our flex layout prefers it or elements may collapse). + * This is needed to size the map SVG properly in IE 11 and some mobile devices. + */ +const RatioContainer: React.FC<{ width: number; aspectRatio: number }> = ({ + aspectRatio, + children, + width, +}) => { + return ( + + {children} + + ); +}; + +export default RatioContainer; diff --git a/spotlight-client/src/charts/TopologicalMap/Region.tsx b/spotlight-client/src/charts/TopologicalMap/Region.tsx new file mode 100644 index 00000000..2aaf7b3c --- /dev/null +++ b/spotlight-client/src/charts/TopologicalMap/Region.tsx @@ -0,0 +1,112 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2021 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { geoCentroid } from "d3-geo"; +import { rem } from "polished"; +import React, { useState } from "react"; +import { Geography, Marker, GeographyProps } from "react-simple-maps"; +import { Spring } from "react-spring/renderprops.cjs"; +import styled from "styled-components/macro"; +import { ValuesType } from "utility-types"; +import { LocalityDataMapping } from "../../contentModels/types"; +import { colors } from "../../UiLibrary"; + +const RegionGroup = styled.g` + &:focus { + outline: none; + } +`; + +const RegionGeography = styled(Geography)` + &:focus { + outline: none; + } +`; + +const RegionMarker = styled(Marker)``; + +const RegionLabel = styled.text` + font-size: ${rem(18)}; + font-weight: 600; + letter-spacing: -0.015em; + text-anchor: middle; +`; + +const Region = ({ + data, + geography, +}: { + data: ValuesType; + geography: GeographyProps["geography"]; +}): React.ReactElement => { + const centroid = geoCentroid(geography); + const { label, value } = data; + const [hoverRegion, setHoverRegion] = useState(false); + + const setHover = () => { + setHoverRegion(true); + }; + + const clearHover = () => { + setHoverRegion(false); + }; + + return ( + + {/* + using spring renderprops instead of hook because react-simple-maps + components are not compatible with the `animated` wrapper + */} + + {(props) => ( + <> + + + {value} + + + )} + + + ); +}; + +export default Region; diff --git a/spotlight-client/src/charts/TopologicalMap/TopologicalMap.tsx b/spotlight-client/src/charts/TopologicalMap/TopologicalMap.tsx new file mode 100644 index 00000000..9f9bbf6a --- /dev/null +++ b/spotlight-client/src/charts/TopologicalMap/TopologicalMap.tsx @@ -0,0 +1,92 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2021 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +import { geoAlbers } from "d3-geo"; +import React from "react"; +import { ComposableMap, Geographies } from "react-simple-maps"; +import styled from "styled-components/macro"; +import { mesh } from "topojson"; +import type { Topology } from "topojson-specification"; +import { LocalityDataMapping } from "../../contentModels/types"; +import MeasureWidth from "../../MeasureWidth"; +import RatioContainer from "./RatioContainer"; +import Region from "./Region"; + +const Wrapper = styled.div``; + +type MapProps = { + aspectRatio: number; + localityData: LocalityDataMapping; + topology: Topology; +}; + +/** + * Given a topojson topology and a mapping of topological object IDs to values, + * draws a map and labels the topological objects accordingly. + */ +export default function TopologicalMap({ + aspectRatio, + localityData, + topology, +}: MapProps): React.ReactElement { + return ( + + {({ measureRef, width }) => { + const stateProjection = geoAlbers().fitExtent( + [ + [0, 0], + [width, width / aspectRatio], + ], + mesh(topology) + ); + + return ( + + + + + {({ geographies }) => { + return geographies.map((geography) => { + return ( + + ); + }); + }} + + + + + ); + }} + + ); +} diff --git a/spotlight-client/src/charts/TopologicalMap/index.ts b/spotlight-client/src/charts/TopologicalMap/index.ts new file mode 100644 index 00000000..87520889 --- /dev/null +++ b/spotlight-client/src/charts/TopologicalMap/index.ts @@ -0,0 +1,18 @@ +// Recidiviz - a data platform for criminal justice reform +// Copyright (C) 2021 Recidiviz, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ============================================================================= + +export { default } from "./TopologicalMap";