Skip to content

Commit 596f5cc

Browse files
committed
Render plant nodes with star icon and update tooltips
Plant nodes are now displayed using a bright yellow star icon via an IconLayer, rendered above other node types for better visibility. Node layers are split by type (PLANT, CONSUMER, NONE) for improved rendering order, and tooltips now show a more descriptive title based on node type.
1 parent e6c2d13 commit 596f5cc

File tree

3 files changed

+127
-18
lines changed

3 files changed

+127
-18
lines changed

src/assets/icons/star-fill.svg

Lines changed: 3 additions & 0 deletions
Loading

src/features/map/components/Map/Map.jsx

Lines changed: 110 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useRef, useEffect, useState, useMemo, useCallback } from 'react';
33
import { DeckGL } from '@deck.gl/react';
44
import {
55
GeoJsonLayer,
6+
IconLayer,
67
PointCloudLayer,
78
PolygonLayer,
89
TextLayer,
@@ -11,6 +12,8 @@ import { DataFilterExtension, PathStyleExtension } from '@deck.gl/extensions';
1112

1213
import positron from 'constants/mapStyles/positron.json';
1314
import no_label from 'constants/mapStyles/positron_nolabel.json';
15+
// eslint-disable-next-line import/no-unresolved
16+
import starFillIcon from 'assets/icons/star-fill.svg?url';
1417

1518
import * as turf from '@turf/turf';
1619
import './Map.css';
@@ -244,23 +247,114 @@ const useMapLayers = (onHover = () => {}) => {
244247
}),
245248
);
246249

247-
_layers.push(
248-
new GeoJsonLayer({
249-
id: `${name}-nodes`,
250-
data: mapLayers[name]?.nodes,
251-
getFillColor: (f) => nodeFillColor(f.properties['type']),
252-
getPointRadius: (f) => nodeRadius(f.properties['type']),
253-
getLineColor: (f) => nodeLineColor(f.properties['type']),
254-
getLineWidth: 1,
255-
updateTriggers: {
256-
getPointRadius: [scale],
257-
},
258-
onHover: onHover,
259-
pickable: true,
260-
261-
parameters: { depthTest: false },
262-
}),
250+
// Partition nodes by type
251+
const nodesData = mapLayers[name]?.nodes;
252+
const { plantNodes, consumerNodes, noneNodes } = (
253+
nodesData?.features ?? []
254+
).reduce(
255+
(acc, feature) => {
256+
const type = feature.properties['type'];
257+
if (type === 'PLANT') {
258+
acc.plantNodes.push(feature);
259+
} else if (type === 'CONSUMER') {
260+
acc.consumerNodes.push(feature);
261+
} else {
262+
acc.noneNodes.push(feature);
263+
}
264+
return acc;
265+
},
266+
{ plantNodes: [], consumerNodes: [], noneNodes: [] },
263267
);
268+
269+
// Add GeoJsonLayer for NONE nodes - rendered first (bottom layer)
270+
if (noneNodes.length > 0) {
271+
_layers.push(
272+
new GeoJsonLayer({
273+
id: `${name}-none-nodes`,
274+
data: {
275+
type: 'FeatureCollection',
276+
features: noneNodes,
277+
},
278+
getFillColor: (f) => nodeFillColor(f.properties['type']),
279+
getPointRadius: (f) => nodeRadius(f.properties['type']),
280+
getLineColor: (f) => nodeLineColor(f.properties['type']),
281+
getLineWidth: 1,
282+
updateTriggers: {
283+
getPointRadius: [scale],
284+
},
285+
onHover: onHover,
286+
pickable: true,
287+
parameters: { depthTest: false },
288+
}),
289+
);
290+
}
291+
292+
// Add GeoJsonLayer for CONSUMER nodes - rendered second (above NONE nodes)
293+
if (consumerNodes.length > 0) {
294+
_layers.push(
295+
new GeoJsonLayer({
296+
id: `${name}-consumer-nodes`,
297+
data: {
298+
type: 'FeatureCollection',
299+
features: consumerNodes,
300+
},
301+
getFillColor: (f) => nodeFillColor(f.properties['type']),
302+
getPointRadius: (f) => nodeRadius(f.properties['type']),
303+
getLineColor: (f) => nodeLineColor(f.properties['type']),
304+
getLineWidth: 1,
305+
updateTriggers: {
306+
getPointRadius: [scale],
307+
},
308+
onHover: onHover,
309+
pickable: true,
310+
parameters: { depthTest: false },
311+
}),
312+
);
313+
}
314+
315+
// Add IconLayer for plant nodes with star icon
316+
// Rendered after other nodes to appear on top
317+
if (plantNodes.length > 0) {
318+
// Use bright yellow for high visibility and to complement blue/red edges
319+
const plantColor = [255, 209, 29, 255]; // Bright yellow
320+
321+
_layers.push(
322+
new IconLayer({
323+
id: `${name}-plant-nodes`,
324+
data: plantNodes,
325+
getIcon: () => ({
326+
url: starFillIcon,
327+
width: 64,
328+
height: 64,
329+
anchorY: 32,
330+
mask: true,
331+
}),
332+
getPosition: (f) => {
333+
const coords = f.geometry.coordinates;
334+
// Add z-elevation of 3 meters to lift icon above the map
335+
return [coords[0], coords[1], 3];
336+
},
337+
getSize: 10 * scale,
338+
getColor: plantColor,
339+
sizeUnits: 'meters',
340+
sizeMinPixels: 20,
341+
billboard: true,
342+
loadOptions: {
343+
imagebitmap: {
344+
resizeWidth: 64,
345+
resizeHeight: 64,
346+
resizeQuality: 'high',
347+
},
348+
},
349+
onHover: onHover,
350+
pickable: true,
351+
updateTriggers: {
352+
getSize: [scale],
353+
},
354+
parameters: { depthTest: false },
355+
}),
356+
);
357+
}
264358
}
265359

266360
if (name == DEMAND && mapLayers?.[name]) {

src/features/map/components/Map/MapTooltip.jsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,12 +213,24 @@ const MapTooltip = ({ info }) => {
213213
</div>
214214
</div>
215215
);
216-
} else if (layer.id === `${THERMAL_NETWORK}-nodes`) {
216+
} else if (
217+
layer.id === `${THERMAL_NETWORK}-none-nodes` ||
218+
layer.id === `${THERMAL_NETWORK}-consumer-nodes` ||
219+
layer.id === `${THERMAL_NETWORK}-plant-nodes`
220+
) {
217221
if (properties?.type === 'NONE') return null;
218222

223+
// Determine title based on node type
224+
const nodeTitle =
225+
properties?.type === 'PLANT'
226+
? 'Plant Node'
227+
: properties?.type === 'CONSUMER'
228+
? 'Building Node'
229+
: 'Network Node';
230+
219231
return (
220232
<div className="tooltip-content">
221-
<b style={{ fontSize: '1.2em', marginBottom: '4px' }}>Network Node</b>
233+
<b style={{ fontSize: '1.2em', marginBottom: '4px' }}>{nodeTitle}</b>
222234
<div className="tooltip-grid">
223235
<div>ID</div>
224236
<b style={{ marginLeft: 'auto' }}>{object?.id}</b>

0 commit comments

Comments
 (0)