Format the map
This commit is contained in:
parent
4c258018c3
commit
0fde087c3d
3 changed files with 64 additions and 29 deletions
|
|
@ -66,7 +66,9 @@ export default function App() {
|
||||||
const poiAbortControllerRef = useRef<AbortController | null>(null);
|
const poiAbortControllerRef = useRef<AbortController | null>(null);
|
||||||
|
|
||||||
// Hexagon properties state
|
// Hexagon properties state
|
||||||
const [selectedHexagon, setSelectedHexagon] = useState<{ h3: string; resolution: number } | null>(null);
|
const [selectedHexagon, setSelectedHexagon] = useState<{ h3: string; resolution: number } | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
const [properties, setProperties] = useState<Property[]>([]);
|
const [properties, setProperties] = useState<Property[]>([]);
|
||||||
const [propertiesTotal, setPropertiesTotal] = useState(0);
|
const [propertiesTotal, setPropertiesTotal] = useState(0);
|
||||||
const [propertiesOffset, setPropertiesOffset] = useState(0);
|
const [propertiesOffset, setPropertiesOffset] = useState(0);
|
||||||
|
|
@ -347,9 +349,7 @@ export default function App() {
|
||||||
<div className="flex border-b border-gray-200">
|
<div className="flex border-b border-gray-200">
|
||||||
<button
|
<button
|
||||||
className={`flex-1 p-3 ${
|
className={`flex-1 p-3 ${
|
||||||
rightPaneTab === 'pois'
|
rightPaneTab === 'pois' ? 'border-b-2 border-blue-500 font-semibold' : 'text-gray-600'
|
||||||
? 'border-b-2 border-blue-500 font-semibold'
|
|
||||||
: 'text-gray-600'
|
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setRightPaneTab('pois')}
|
onClick={() => setRightPaneTab('pois')}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ function emojiToTwemojiUrl(emoji: string): string {
|
||||||
return `${TWEMOJI_BASE}${hex}.png`;
|
return `${TWEMOJI_BASE}${hex}.png`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const INITIAL_VIEW: ViewState = {
|
const INITIAL_VIEW: ViewState = {
|
||||||
longitude: -1.5,
|
longitude: -1.5,
|
||||||
latitude: 53.5,
|
latitude: 53.5,
|
||||||
|
|
@ -144,7 +143,16 @@ function countToColor(t: number): [number, number, number] {
|
||||||
return [r, g, b];
|
return [r, g, b];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Map({ data, pois, onViewChange, activeFeature, dragValue, features, selectedHexagonId, onHexagonClick }: MapProps) {
|
export default function Map({
|
||||||
|
data,
|
||||||
|
pois,
|
||||||
|
onViewChange,
|
||||||
|
activeFeature,
|
||||||
|
dragValue,
|
||||||
|
features,
|
||||||
|
selectedHexagonId,
|
||||||
|
onHexagonClick,
|
||||||
|
}: MapProps) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [viewState, setViewState] = useState<ViewState>(INITIAL_VIEW);
|
const [viewState, setViewState] = useState<ViewState>(INITIAL_VIEW);
|
||||||
const [dimensions, setDimensions] = useState<Dimensions>({ width: 0, height: 0 });
|
const [dimensions, setDimensions] = useState<Dimensions>({ width: 0, height: 0 });
|
||||||
|
|
@ -180,7 +188,8 @@ export default function Map({ data, pois, onViewChange, activeFeature, dragValue
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Make place labels more legible over the colored hexagons
|
// Make place labels more legible over the colored hexagons
|
||||||
const handleMapLoad = useCallback((evt: { target: MapRef['getMap'] extends () => infer M ? M : never }) => {
|
const handleMapLoad = useCallback(
|
||||||
|
(evt: { target: MapRef['getMap'] extends () => infer M ? M : never }) => {
|
||||||
const map = evt.target;
|
const map = evt.target;
|
||||||
for (const layer of map.getStyle().layers || []) {
|
for (const layer of map.getStyle().layers || []) {
|
||||||
if (layer.type !== 'symbol') continue;
|
if (layer.type !== 'symbol') continue;
|
||||||
|
|
@ -188,7 +197,9 @@ export default function Map({ data, pois, onViewChange, activeFeature, dragValue
|
||||||
map.setPaintProperty(layer.id, 'text-halo-width', 2);
|
map.setPaintProperty(layer.id, 'text-halo-width', 2);
|
||||||
map.setPaintProperty(layer.id, 'text-color', '#222');
|
map.setPaintProperty(layer.id, 'text-color', '#222');
|
||||||
}
|
}
|
||||||
}, []);
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
// Popup state for POI hover
|
// Popup state for POI hover
|
||||||
const [popupInfo, setPopupInfo] = useState<{
|
const [popupInfo, setPopupInfo] = useState<{
|
||||||
|
|
@ -226,7 +237,9 @@ export default function Map({ data, pois, onViewChange, activeFeature, dragValue
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
// Determine color mode
|
// Determine color mode
|
||||||
const colorFeatureMeta = activeFeature ? features.find((f) => f.name === activeFeature) || null : null;
|
const colorFeatureMeta = activeFeature
|
||||||
|
? features.find((f) => f.name === activeFeature) || null
|
||||||
|
: null;
|
||||||
|
|
||||||
const handleHexagonClick = useCallback(
|
const handleHexagonClick = useCallback(
|
||||||
(info: PickingInfo<HexagonData>) => {
|
(info: PickingInfo<HexagonData>) => {
|
||||||
|
|
@ -258,7 +271,13 @@ export default function Map({ data, pois, onViewChange, activeFeature, dragValue
|
||||||
const t = (c - countRange.min) / (countRange.max - countRange.min);
|
const t = (c - countRange.min) / (countRange.max - countRange.min);
|
||||||
return countToColor(Math.max(0, Math.min(1, t)));
|
return countToColor(Math.max(0, Math.min(1, t)));
|
||||||
},
|
},
|
||||||
getLineColor: (d) => (d.h3 === selectedHexagonId ? [255, 255, 255, 255] : [0, 0, 0, 0]) as [number, number, number, number],
|
getLineColor: (d) =>
|
||||||
|
(d.h3 === selectedHexagonId ? [255, 255, 255, 255] : [0, 0, 0, 0]) as [
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
],
|
||||||
getLineWidth: (d) => (d.h3 === selectedHexagonId ? 2 : 0),
|
getLineWidth: (d) => (d.h3 === selectedHexagonId ? 2 : 0),
|
||||||
lineWidthUnits: 'pixels',
|
lineWidthUnits: 'pixels',
|
||||||
updateTriggers: {
|
updateTriggers: {
|
||||||
|
|
@ -290,7 +309,17 @@ export default function Map({ data, pois, onViewChange, activeFeature, dragValue
|
||||||
onHover: handlePoiHover,
|
onHover: handlePoiHover,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
[data, pois, handlePoiHover, handleHexagonClick, activeFeature, dragValue, countRange, colorFeatureMeta, selectedHexagonId]
|
[
|
||||||
|
data,
|
||||||
|
pois,
|
||||||
|
handlePoiHover,
|
||||||
|
handleHexagonClick,
|
||||||
|
activeFeature,
|
||||||
|
dragValue,
|
||||||
|
countRange,
|
||||||
|
colorFeatureMeta,
|
||||||
|
selectedHexagonId,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getTooltip = useCallback(
|
const getTooltip = useCallback(
|
||||||
|
|
@ -305,8 +334,14 @@ export default function Map({ data, pois, onViewChange, activeFeature, dragValue
|
||||||
const minVal = hex[`min_${f.name}`];
|
const minVal = hex[`min_${f.name}`];
|
||||||
const maxVal = hex[`max_${f.name}`];
|
const maxVal = hex[`max_${f.name}`];
|
||||||
if (minVal != null && maxVal != null) {
|
if (minVal != null && maxVal != null) {
|
||||||
const minStr = typeof minVal === 'number' ? minVal.toLocaleString(undefined, { maximumFractionDigits: 1 }) : String(minVal);
|
const minStr =
|
||||||
const maxStr = typeof maxVal === 'number' ? maxVal.toLocaleString(undefined, { maximumFractionDigits: 1 }) : String(maxVal);
|
typeof minVal === 'number'
|
||||||
|
? minVal.toLocaleString(undefined, { maximumFractionDigits: 1 })
|
||||||
|
: String(minVal);
|
||||||
|
const maxStr =
|
||||||
|
typeof maxVal === 'number'
|
||||||
|
? maxVal.toLocaleString(undefined, { maximumFractionDigits: 1 })
|
||||||
|
: String(maxVal);
|
||||||
const highlight = f.name === activeFeature ? 'font-weight: bold;' : '';
|
const highlight = f.name === activeFeature ? 'font-weight: bold;' : '';
|
||||||
lines.push(`<div style="${highlight}">${f.label}: ${minStr} - ${maxStr}</div>`);
|
lines.push(`<div style="${highlight}">${f.label}: ${minStr} - ${maxStr}</div>`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,7 @@ export function PropertiesPane({
|
||||||
case 'size':
|
case 'size':
|
||||||
return ((b.total_floor_area as number) || 0) - ((a.total_floor_area as number) || 0);
|
return ((b.total_floor_area as number) || 0) - ((a.total_floor_area as number) || 0);
|
||||||
case 'energy':
|
case 'energy':
|
||||||
return (a.current_energy_rating || 'Z').localeCompare(
|
return (a.current_energy_rating || 'Z').localeCompare(b.current_energy_rating || 'Z');
|
||||||
b.current_energy_rating || 'Z'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [properties, sortBy]);
|
}, [properties, sortBy]);
|
||||||
|
|
@ -142,7 +140,8 @@ function PropertyCard({ property }: { property: Property }) {
|
||||||
)}
|
)}
|
||||||
{property.total_floor_area && (
|
{property.total_floor_area && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-600">Area:</span> {formatNumber(property.total_floor_area as number)}m²
|
<span className="text-gray-600">Area:</span>{' '}
|
||||||
|
{formatNumber(property.total_floor_area as number)}m²
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{property.number_habitable_rooms && (
|
{property.number_habitable_rooms && (
|
||||||
|
|
@ -163,7 +162,8 @@ function PropertyCard({ property }: { property: Property }) {
|
||||||
)}
|
)}
|
||||||
{property.construction_age_band !== undefined && (
|
{property.construction_age_band !== undefined && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-600">Built (age):</span> {formatNumber(property.construction_age_band as number)}
|
<span className="text-gray-600">Built (age):</span>{' '}
|
||||||
|
{formatNumber(property.construction_age_band as number)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue