diff --git a/src/app/map/[id]/components/AreaInfo.tsx b/src/app/map/[id]/components/AreaInfo.tsx deleted file mode 100644 index 71553719..00000000 --- a/src/app/map/[id]/components/AreaInfo.tsx +++ /dev/null @@ -1,378 +0,0 @@ -import { AnimatePresence, motion } from "framer-motion"; -import { XIcon } from "lucide-react"; -import { expression } from "mapbox-gl/dist/style-spec/index.cjs"; -import { useMemo, useState } from "react"; - -import { ColumnType } from "@/server/models/DataSource"; -import { CalculationType } from "@/server/models/MapView"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/shadcn/ui/table"; -import { formatNumber } from "@/utils/text"; - -import { useFillColor } from "../colors"; -import { useAreaStats } from "../data"; -import { useChoroplethDataSource } from "../hooks/useDataSources"; -import { useHoverArea } from "../hooks/useMapHover"; -import { useMapViews } from "../hooks/useMapViews"; -import { useSelectedAreas } from "../hooks/useSelectedAreas"; - -const getDisplayValue = ( - calculationType: CalculationType | null | undefined, - areaStats: - | { - columnType: ColumnType; - minValue: number; - maxValue: number; - } - | undefined - | null, - areaStatValue: unknown, -): string => { - if ( - areaStatValue === undefined || - areaStatValue === null || - areaStatValue === "" - ) { - return calculationType === CalculationType.Count ? "0" : "-"; - } - if (areaStats?.columnType !== ColumnType.Number) { - return String(areaStatValue); - } - const value = Number(areaStatValue); - if (isNaN(value)) { - return "-"; - } - if (areaStats?.minValue >= 0 && areaStats?.maxValue <= 1) { - return `${Math.round(value * 1000) / 10}%`; - } - return formatNumber(value); -}; - -const toRGBA = (expressionResult: unknown) => { - if ( - !expressionResult || - !Array.isArray(expressionResult) || - expressionResult.length < 3 - ) { - return `rgba(0, 0, 0, 0)`; - } - const [r, g, b, ...rest] = expressionResult; - const a = rest.length ? rest[0] : 1; - return `rgba(${r}, ${g}, ${b}, ${a})`; -}; - -export default function AreaInfo() { - const [hoverArea] = useHoverArea(); - const [hoveredRowArea, setHoveredRowArea] = useState<{ - code: string; - areaSetCode: string; - name: string; - coordinates: [number, number]; - } | null>(null); - const [selectedAreas, setSelectedAreas] = useSelectedAreas(); - const areaStatsQuery = useAreaStats(); - const areaStats = areaStatsQuery.data; - const choroplethDataSource = useChoroplethDataSource(); - const { viewConfig } = useMapViews(); - - const fillColor = useFillColor({ - areaStats, - viewConfig, - selectedBivariateBucket: null, - }); - - // Combine selected areas and hover area, avoiding duplicates - // Memoized to prevent downstream recalculations (especially color expressions) - const areasToDisplay = useMemo(() => { - const areas = []; - - // Add all selected areas - for (const selectedArea of selectedAreas) { - areas.push({ - code: selectedArea.code, - name: selectedArea.name, - areaSetCode: selectedArea.areaSetCode, - coordinates: selectedArea.coordinates, - isSelected: true, - }); - } - - // Add hover area only if it's not already in selected areas - if (hoverArea) { - const isHoverAreaSelected = selectedAreas.some( - (a) => - a.code === hoverArea.code && a.areaSetCode === hoverArea.areaSetCode, - ); - if (!isHoverAreaSelected) { - areas.push({ - code: hoverArea.code, - name: hoverArea.name, - areaSetCode: hoverArea.areaSetCode, - coordinates: hoverArea.coordinates, - isSelected: false, - }); - } - } - - // Add hovered row area even if it's no longer in hoverArea - if (hoveredRowArea) { - const isAreaAlreadyDisplayed = areas.some( - (a) => - a.code === hoveredRowArea.code && - a.areaSetCode === hoveredRowArea.areaSetCode, - ); - if (!isAreaAlreadyDisplayed) { - areas.push({ - code: hoveredRowArea.code, - name: hoveredRowArea.name, - areaSetCode: hoveredRowArea.areaSetCode, - coordinates: hoveredRowArea.coordinates, - isSelected: false, - }); - } - } - - return areas; - }, [selectedAreas, hoverArea, hoveredRowArea]); - - const multipleAreas = selectedAreas.length > 1; - const hasSecondaryData = Boolean(viewConfig.areaDataSecondaryColumn); - - const statLabel = areaStats - ? areaStats.calculationType === CalculationType.Count - ? `${choroplethDataSource?.name || "Unknown"} count` - : viewConfig.areaDataColumn - : ""; - - const { result, value: fillColorExpression } = expression.createExpression([ - "to-rgba", - fillColor, - ]); - - if (result !== "success") { - console.error( - "Attempted to parse invalid MapboxGL expression", - JSON.stringify(fillColor), - fillColorExpression, - ); - } - - // Memoize color calculations for all areas to improve performance - const areaColors = useMemo(() => { - const colors = new Map(); - - if (result !== "success" || !areaStats) { - return colors; - } - - for (const area of areasToDisplay) { - const areaStat = - areaStats.areaSetCode === area.areaSetCode - ? areaStats.stats.find((s) => s.areaCode === area.code) - : null; - - if (!areaStat) { - colors.set( - `${area.areaSetCode}-${area.code}`, - "rgba(200, 200, 200, 1)", - ); - continue; - } - - // For bivariate color schemes, evaluate with both primary and secondary values - const colorResult = fillColorExpression.evaluate( - { zoom: 0 }, - { type: "Polygon", properties: {} }, - { - value: areaStat.primary || 0, - secondaryValue: areaStat.secondary || 0, - }, - ); - - colors.set(`${area.areaSetCode}-${area.code}`, toRGBA(colorResult)); - } - - return colors; - }, [areasToDisplay, areaStats, fillColorExpression, result]); - - // Helper to get color for an area based on memoized calculations - const getAreaColor = (area: { - code: string; - areaSetCode: string; - }): string => { - return ( - areaColors.get(`${area.areaSetCode}-${area.code}`) || - "rgba(200, 200, 200, 1)" - ); - }; - - return ( - - {areasToDisplay.length > 0 && ( - - {selectedAreas.length > 0 && ( - - )} - - {multipleAreas && ( - - - - - {statLabel} - - {hasSecondaryData && ( - - {viewConfig.areaDataSecondaryColumn} - - )} - - - )} - - {areasToDisplay.map((area) => { - const areaStat = - areaStats?.areaSetCode === area.areaSetCode - ? areaStats.stats.find((s) => s.areaCode === area.code) - : null; - - const primaryValue = - areaStats && areaStat - ? getDisplayValue( - areaStats.calculationType, - areaStats.primary, - areaStat.primary, - ) - : null; - const secondaryValue = - areaStats && areaStat - ? getDisplayValue( - areaStats.calculationType, - areaStats.secondary, - areaStat.secondary, - ) - : null; - - return ( - { - if (!area.isSelected) { - setHoveredRowArea(area); - } - }} - onMouseLeave={() => { - setHoveredRowArea(null); - }} - onClick={() => { - if (area.isSelected) { - // Remove from selected areas - setSelectedAreas( - selectedAreas.filter( - (a) => - !( - a.code === area.code && - a.areaSetCode === area.areaSetCode - ), - ), - ); - } else { - // Add to selected areas - setSelectedAreas([ - ...selectedAreas, - { - code: area.code, - name: area.name, - areaSetCode: area.areaSetCode, - coordinates: area.coordinates, - }, - ]); - } - }} - > - -
-
- {area.name} -
- - {primaryValue && !multipleAreas && ( - -
- - )} - {areaStats && ( - - {!multipleAreas ? ( -
- - {statLabel}: - - {primaryValue} -
- ) : ( - primaryValue || "-" - )} -
- )} - {hasSecondaryData && ( - - {!multipleAreas ? ( -
- - {viewConfig.areaDataSecondaryColumn}: - - {secondaryValue} -
- ) : ( - secondaryValue || "-" - )} -
- )} - - ); - })} - -
-
- )} -
- ); -} diff --git a/src/app/map/[id]/components/AreaPopup.tsx b/src/app/map/[id]/components/AreaPopup.tsx deleted file mode 100644 index e51cf204..00000000 --- a/src/app/map/[id]/components/AreaPopup.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import { expression } from "mapbox-gl/dist/style-spec/index.cjs"; -import { Popup } from "react-map-gl/mapbox"; -import { ColumnType } from "@/server/models/DataSource"; -import { CalculationType } from "@/server/models/MapView"; -import { formatNumber } from "@/utils/text"; -import { useFillColor } from "../colors"; -import { useAreaStats } from "../data"; -import { useChoroplethDataSource } from "../hooks/useDataSources"; -import { useHoverArea } from "../hooks/useMapHover"; -import { useMapViews } from "../hooks/useMapViews"; -import type { AreaSetCode } from "@/server/models/AreaSet"; - -export default function AreaPopup() { - const [hoverArea] = useHoverArea(); - - if (!hoverArea) { - return null; - } - - return ; -} - -function WrappedAreaPopup({ - areaSetCode, - code, - name, - coordinates, -}: { - areaSetCode: AreaSetCode; - code: string; - name: string; - coordinates: [number, number]; -}) { - const { viewConfig } = useMapViews(); - const choroplethDataSource = useChoroplethDataSource(); - const [, setHoverArea] = useHoverArea(); - - const areaStatsQuery = useAreaStats(); - const areaStats = areaStatsQuery.data; - - const areaStat = - areaStats?.areaSetCode === areaSetCode - ? areaStats?.stats.find((s) => s.areaCode === code) - : null; - - const primaryDisplayValue = getDisplayValue( - areaStats?.calculationType, - areaStats?.primary, - areaStat?.primary, - ); - const secondaryDisplayValue = getDisplayValue( - areaStats?.calculationType, - areaStats?.secondary, - areaStat?.secondary, - ); - - const fillColor = useFillColor({ - areaStats, - viewConfig, - selectedBivariateBucket: null, - }); - - const { result, value: fillColorExpression } = expression.createExpression([ - "to-rgba", - fillColor, - ]); - - if (result !== "success") { - console.error( - "Attempted to parse invalid MapboxGL expression", - JSON.stringify(fillColor), - fillColorExpression, - ); - return null; - } - - // If using a bivariate color scheme, separate out the colors - // here so the user can easily see the value along each dimension - const primaryColor = fillColorExpression.evaluate( - { zoom: 0 }, - { type: "Polygon", properties: {} }, - { - value: areaStat?.primary || 0, - secondaryValue: 0, // Only look at primary stat - }, - ); - - const secondaryColor = fillColorExpression.evaluate( - { zoom: 0 }, - { type: "Polygon", properties: {} }, - { - value: 0, // Only look at secondary stat - secondaryValue: areaStat?.secondary, - }, - ); - - const statLabel = - areaStats?.calculationType === CalculationType.Count - ? `${choroplethDataSource?.name || "Unknown"} count` - : viewConfig.areaDataColumn; - - return ( - setHoverArea(null)} - > -
-

{name}

- {areaStats?.primary && ( -
-
-

- {statLabel}: {primaryDisplayValue} -

-
- )} - {areaStats?.secondary && ( -
-
-

- {viewConfig.areaDataSecondaryColumn}: {secondaryDisplayValue} -

-
- )} -
-
- ); -} - -const toRGBA = (expressionResult: unknown) => { - if ( - !expressionResult || - !Array.isArray(expressionResult) || - expressionResult.length < 3 - ) { - return `rgba(0, 0, 0, 0)`; - } - const [r, g, b, ...rest] = expressionResult; - const a = rest.length ? rest[0] : 1; - return `rgba(${r}, ${g}, ${b}, ${a})`; -}; - -const getDisplayValue = ( - calculationType: CalculationType | null | undefined, - areaStats: - | { - columnType: ColumnType; - minValue: number; - maxValue: number; - } - | undefined - | null, - areaStatValue: unknown, -): string => { - if ( - areaStatValue === undefined || - areaStatValue === null || - areaStatValue === "" - ) { - return calculationType === CalculationType.Count ? "0" : "-"; - } - if (areaStats?.columnType !== ColumnType.Number) { - return String(areaStatValue); - } - const value = Number(areaStatValue); - if (isNaN(value)) { - return "-"; - } - if (areaStats.minValue >= 0 && areaStats.maxValue <= 1) { - return `${Math.round(value * 1000) / 10}%`; - } - return formatNumber(value); -}; diff --git a/src/app/map/[id]/components/BoundaryHoverInfo/AreasList.tsx b/src/app/map/[id]/components/BoundaryHoverInfo/AreasList.tsx new file mode 100644 index 00000000..87f68e9f --- /dev/null +++ b/src/app/map/[id]/components/BoundaryHoverInfo/AreasList.tsx @@ -0,0 +1,227 @@ +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/shadcn/ui/table"; + +import { getDisplayValue } from "./utils"; +import type { ColumnType } from "@/server/models/DataSource"; +import type { CalculationType } from "@/server/models/MapView"; + +interface AreaStat { + areaCode: string; + primary?: unknown; + secondary?: unknown; +} + +interface AreaStats { + areaSetCode: string; + calculationType: CalculationType; + primary: { + column: string; + columnType: ColumnType; + minValue: number; + maxValue: number; + } | null; + secondary: { + column: string; + columnType: ColumnType; + minValue: number; + maxValue: number; + } | null; + stats: AreaStat[]; +} + +interface AreasListProps { + areas: { + code: string; + areaSetCode: string; + name: string; + coordinates: [number, number]; + isSelected: boolean; + }[]; + statLabel: string; + hasSecondaryData: boolean; + secondaryColumnName?: string; + areaStats: AreaStats | null; + getAreaColor: (area: { code: string; areaSetCode: string }) => string; + selectedAreas: { + code: string; + areaSetCode: string; + name: string; + coordinates: [number, number]; + }[]; + setSelectedAreas: ( + areas: { + code: string; + areaSetCode: string; + name: string; + coordinates: [number, number]; + }[], + ) => void; + onHoveredRowAreaChange: ( + area: { + code: string; + areaSetCode: string; + name: string; + coordinates: [number, number]; + } | null, + ) => void; +} + +export function AreasList({ + areas, + statLabel, + hasSecondaryData, + secondaryColumnName, + areaStats, + getAreaColor, + selectedAreas, + setSelectedAreas, + onHoveredRowAreaChange, +}: AreasListProps) { + const multipleAreas = selectedAreas.length > 1; + + return ( + + {multipleAreas && ( + + + + + {statLabel} + + {hasSecondaryData && ( + + {secondaryColumnName} + + )} + + + )} + + {areas.map((area) => { + const areaStat = + areaStats?.areaSetCode === area.areaSetCode + ? areaStats.stats.find((s) => s.areaCode === area.code) + : null; + + const primaryValue = + areaStats && areaStat + ? getDisplayValue( + areaStats.calculationType, + areaStats.primary, + areaStat.primary, + ) + : null; + const secondaryValue = + areaStats && areaStat + ? getDisplayValue( + areaStats.calculationType, + areaStats.secondary, + areaStat.secondary, + ) + : null; + + return ( + { + if (!area.isSelected) { + onHoveredRowAreaChange(area); + } + }} + onMouseLeave={() => { + onHoveredRowAreaChange(null); + }} + onClick={() => { + if (area.isSelected) { + // Remove from selected areas + setSelectedAreas( + selectedAreas.filter( + (a) => + !( + a.code === area.code && + a.areaSetCode === area.areaSetCode + ), + ), + ); + } else { + // Add to selected areas + setSelectedAreas([ + ...selectedAreas, + { + code: area.code, + name: area.name, + areaSetCode: area.areaSetCode, + coordinates: area.coordinates, + }, + ]); + } + }} + > + +
+
+ {area.name} +
+ + {primaryValue && !multipleAreas && ( + +
+ + )} + {areaStats && ( + + {multipleAreas ? ( + primaryValue || "-" + ) : ( +
+ + {statLabel}: + + {primaryValue} +
+ )} +
+ )} + {hasSecondaryData && ( + + {multipleAreas ? ( + secondaryValue || "-" + ) : ( +
+ + {secondaryColumnName}: + + {secondaryValue} +
+ )} +
+ )} + + ); + })} + +
+ ); +} diff --git a/src/app/map/[id]/components/BoundaryHoverInfo/index.tsx b/src/app/map/[id]/components/BoundaryHoverInfo/index.tsx new file mode 100644 index 00000000..d0fd98f8 --- /dev/null +++ b/src/app/map/[id]/components/BoundaryHoverInfo/index.tsx @@ -0,0 +1,202 @@ +import { AnimatePresence, motion } from "framer-motion"; +import { XIcon } from "lucide-react"; +import { expression } from "mapbox-gl/dist/style-spec/index.cjs"; +import { useMemo, useState } from "react"; + +import { CalculationType } from "@/server/models/MapView"; + +import { useFillColor } from "../../colors"; +import { useAreaStats } from "../../data"; +import { useChoroplethDataSource } from "../../hooks/useDataSources"; +import { useHoverArea } from "../../hooks/useMapHover"; +import { useMapViews } from "../../hooks/useMapViews"; +import { useSelectedAreas } from "../../hooks/useSelectedAreas"; +import { AreasList } from "./AreasList"; +import { toRGBA } from "./utils"; + +export default function BoundaryHoverInfo() { + const [hoverArea] = useHoverArea(); + const [hoveredRowArea, setHoveredRowArea] = useState<{ + code: string; + areaSetCode: string; + name: string; + coordinates: [number, number]; + } | null>(null); + const [selectedAreas, setSelectedAreas] = useSelectedAreas(); + const areaStatsQuery = useAreaStats(); + const areaStats = areaStatsQuery.data; + const choroplethDataSource = useChoroplethDataSource(); + const { viewConfig } = useMapViews(); + + const fillColor = useFillColor({ + areaStats, + viewConfig, + selectedBivariateBucket: null, + }); + + // Combine selected areas and hover area, avoiding duplicates + // Memoized to prevent downstream recalculations (especially color expressions) + const areasToDisplay = useMemo(() => { + const areas = []; + + // Add all selected areas + for (const selectedArea of selectedAreas) { + areas.push({ + code: selectedArea.code, + name: selectedArea.name, + areaSetCode: selectedArea.areaSetCode, + coordinates: selectedArea.coordinates, + isSelected: true, + }); + } + + // Add hover area only if it's not already in selected areas + if (hoverArea) { + const isHoverAreaSelected = selectedAreas.some( + (a: { code: string; areaSetCode: string }) => + a.code === hoverArea.code && a.areaSetCode === hoverArea.areaSetCode, + ); + if (!isHoverAreaSelected) { + areas.push({ + code: hoverArea.code, + name: hoverArea.name, + areaSetCode: hoverArea.areaSetCode, + coordinates: hoverArea.coordinates, + isSelected: false, + }); + } + } + + // Add hovered row area even if it's no longer in hoverArea + if (hoveredRowArea) { + const isAreaAlreadyDisplayed = areas.some( + (a) => + a.code === hoveredRowArea.code && + a.areaSetCode === hoveredRowArea.areaSetCode, + ); + if (!isAreaAlreadyDisplayed) { + areas.push({ + code: hoveredRowArea.code, + name: hoveredRowArea.name, + areaSetCode: hoveredRowArea.areaSetCode, + coordinates: hoveredRowArea.coordinates, + isSelected: false, + }); + } + } + + return areas; + }, [selectedAreas, hoverArea, hoveredRowArea]); + + const hasSecondaryData = Boolean(viewConfig.areaDataSecondaryColumn); + + const statLabel = areaStats + ? areaStats.calculationType === CalculationType.Count + ? `${choroplethDataSource?.name || "Unknown"} count` + : viewConfig.areaDataColumn + : ""; + + const { result, value: fillColorExpression } = expression.createExpression([ + "to-rgba", + fillColor, + ]); + + if (result !== "success") { + console.error( + "Attempted to parse invalid MapboxGL expression", + JSON.stringify(fillColor), + fillColorExpression, + ); + } + + // Memoize color calculations for all areas to improve performance + const areaColors = useMemo(() => { + const colors = new Map(); + + if (result !== "success" || !areaStats) { + return colors; + } + + for (const area of areasToDisplay) { + const areaStat = + areaStats.areaSetCode === area.areaSetCode + ? areaStats.stats.find( + (s: { areaCode: string }) => s.areaCode === area.code, + ) + : null; + + if (!areaStat) { + colors.set( + `${area.areaSetCode}-${area.code}`, + "rgba(200, 200, 200, 1)", + ); + continue; + } + + // For bivariate color schemes, evaluate with both primary and secondary values + const colorResult = fillColorExpression.evaluate( + { zoom: 0 }, + { type: "Polygon", properties: {} }, + { + value: areaStat.primary || 0, + secondaryValue: areaStat.secondary || 0, + }, + ); + + colors.set(`${area.areaSetCode}-${area.code}`, toRGBA(colorResult)); + } + + return colors; + }, [areasToDisplay, areaStats, fillColorExpression, result]); + + // Helper to get color for an area based on memoized calculations + const getAreaColor = (area: { + code: string; + areaSetCode: string; + }): string => { + return ( + areaColors.get(`${area.areaSetCode}-${area.code}`) || + "rgba(200, 200, 200, 1)" + ); + }; + + return ( + + {hoverArea && areasToDisplay.length > 0 && ( + + {selectedAreas.length > 0 && ( + + )} +
+ +
+
+ )} +
+ ); +} diff --git a/src/app/map/[id]/components/BoundaryHoverInfo/utils.ts b/src/app/map/[id]/components/BoundaryHoverInfo/utils.ts new file mode 100644 index 00000000..91cba351 --- /dev/null +++ b/src/app/map/[id]/components/BoundaryHoverInfo/utils.ts @@ -0,0 +1,48 @@ +import { ColumnType } from "@/server/models/DataSource"; +import { CalculationType } from "@/server/models/MapView"; +import { formatNumber } from "@/utils/text"; + +export const getDisplayValue = ( + calculationType: CalculationType | null | undefined, + areaStats: + | { + columnType: ColumnType; + minValue: number; + maxValue: number; + } + | undefined + | null, + areaStatValue: unknown, +): string => { + if ( + areaStatValue === undefined || + areaStatValue === null || + areaStatValue === "" + ) { + return calculationType === CalculationType.Count ? "0" : "-"; + } + if (areaStats?.columnType !== ColumnType.Number) { + return String(areaStatValue); + } + const value = Number(areaStatValue); + if (isNaN(value)) { + return "-"; + } + if (areaStats?.minValue >= 0 && areaStats?.maxValue <= 1) { + return `${Math.round(value * 1000) / 10}%`; + } + return formatNumber(value); +}; + +export const toRGBA = (expressionResult: unknown) => { + if ( + !expressionResult || + !Array.isArray(expressionResult) || + expressionResult.length < 3 + ) { + return `rgba(0, 0, 0, 0)`; + } + const [r, g, b, ...rest] = expressionResult; + const a = rest.length ? rest[0] : 1; + return `rgba(${r}, ${g}, ${b}, ${a})`; +}; diff --git a/src/app/map/[id]/components/MapWrapper.tsx b/src/app/map/[id]/components/MapWrapper.tsx index fc9e07fc..bbd09079 100644 --- a/src/app/map/[id]/components/MapWrapper.tsx +++ b/src/app/map/[id]/components/MapWrapper.tsx @@ -1,6 +1,7 @@ import { XIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { MapType } from "@/server/models/MapView"; +import { useChoropleth } from "../hooks/useChoropleth"; import { useInspector } from "../hooks/useInspector"; import { useCompareGeographiesMode, @@ -9,7 +10,7 @@ import { } from "../hooks/useMapControls"; import { useMapViews } from "../hooks/useMapViews"; import { CONTROL_PANEL_WIDTH, mapColors } from "../styles"; -import AreaInfo from "./AreaInfo"; +import BoundaryHoverInfo from "./BoundaryHoverInfo"; import InspectorPanel from "./inspector/InspectorPanel"; import MapMarkerAndAreaControls from "./MapMarkerAndAreaControls"; import MapStyleSelector from "./MapStyleSelector"; @@ -29,6 +30,7 @@ export default function MapWrapper({ const { viewConfig } = useMapViews(); const { inspectorContent } = useInspector(); const inspectorVisible = Boolean(inspectorContent); + const { boundariesPanelOpen } = useChoropleth(); const compareGeographiesMode = useCompareGeographiesMode(); const { pinDropMode, @@ -88,16 +90,19 @@ export default function MapWrapper({ {children}
- + {viewConfig.mapType !== MapType.Hex && (