From 57b1951d0f1fee2ee71f95e36382b0402116820a Mon Sep 17 00:00:00 2001
From: "Vink, S.A. (Sjoerd)" <s.a.vink@uu.nl>
Date: Wed, 28 Aug 2024 12:09:10 -0400
Subject: [PATCH 01/12] feat(mapAttributeColoring): coloring based on data
 attributes

---
 .../lib/vis/visualizations/mapvis/config.ts   |  5 ++
 .../mapvis/layers/heatmap-layer/HeatLayer.tsx | 35 +----------
 .../visualizations/mapvis/layers/index.tsx    |  7 +--
 .../layers/nodelink-layer/NodeLinkLayer.tsx   | 61 +++++++++++++++++-
 .../layers/nodelink-layer/NodeLinkOptions.tsx | 63 ++++++++++++++++++-
 .../vis/visualizations/mapvis/mapvis.types.ts |  1 +
 .../lib/vis/visualizations/mapvis/utils.ts    |  2 +-
 .../lib/vis/visualizations/mapvis/utlis.tsx   | 10 ---
 8 files changed, 129 insertions(+), 55 deletions(-)
 create mode 100644 libs/shared/lib/vis/visualizations/mapvis/config.ts
 delete mode 100644 libs/shared/lib/vis/visualizations/mapvis/utlis.tsx

diff --git a/libs/shared/lib/vis/visualizations/mapvis/config.ts b/libs/shared/lib/vis/visualizations/mapvis/config.ts
new file mode 100644
index 000000000..db5f810c6
--- /dev/null
+++ b/libs/shared/lib/vis/visualizations/mapvis/config.ts
@@ -0,0 +1,5 @@
+export const MAP_PROVIDER = [
+  'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png',
+  'https://b.tile.openstreetmap.org/{z}/{x}/{y}.png',
+  'https://c.tile.openstreetmap.org/{z}/{x}/{y}.png',
+];
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/heatmap-layer/HeatLayer.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/heatmap-layer/HeatLayer.tsx
index 52395513b..5641f0182 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/heatmap-layer/HeatLayer.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/heatmap-layer/HeatLayer.tsx
@@ -1,8 +1,6 @@
 import React from 'react';
 import { CompositeLayer, HeatmapLayer, Layer } from 'deck.gl';
-import * as d3 from 'd3';
-import { getDistance } from '../../utlis';
-import { CompositeLayerType, Edge, LayerProps } from '../../mapvis.types';
+import { CompositeLayerType, LayerProps } from '../../mapvis.types';
 import { Node } from '@graphpolaris/shared/lib/data-access';
 
 export class HeatLayer extends CompositeLayer<CompositeLayerType> {
@@ -17,37 +15,6 @@ export class HeatLayer extends CompositeLayer<CompositeLayerType> {
     return changeFlags.propsOrDataChanged || changeFlags.somethingChanged;
   }
 
-  createSegments(edges: Edge[]) {
-    // Generates a path between source and target nodes
-    return edges.map((edge: Edge, index) => {
-      const length = getDistance(edge.path[0], edge.path[1]);
-      const nSegments = length * this.props.settings.nSegments;
-
-      let xscale = d3
-        .scaleLinear()
-        .domain([0, nSegments + 1])
-        .range([edge.path[0][0], edge.path[1][0]]);
-
-      let yscale = d3
-        .scaleLinear()
-        .domain([0, nSegments + 1])
-        .range([edge.path[0][1], edge.path[1][1]]);
-
-      let source = edge.path[0];
-      let target = null;
-      let local = [source];
-
-      for (let j = 1; j <= nSegments; j++) {
-        target = [xscale(j), yscale(j)];
-        local.push(target);
-        source = target;
-      }
-
-      local.push(edge.path.slice(-1)[0]);
-      return { ...edge, path: local };
-    });
-  }
-
   renderLayers() {
     const { data, settings, getNodeLocation, setLayerIds, graphMetadata } = this.props;
     const layerSettings = settings[HeatLayer.type];
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/index.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/index.tsx
index 9e5f7c31e..92360935c 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/index.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/index.tsx
@@ -7,6 +7,7 @@ import { ChoroplethOptions } from './choropleth-layer/ChoroplethOptions';
 import { TileLayer, BitmapLayer } from 'deck.gl';
 import { MapProps } from '../mapvis';
 import { LayerSettingsComponentType } from '../mapvis.types';
+import { MAP_PROVIDER } from '../config';
 
 export type LayerTypes = 'nodelink' | 'heatmap' | 'choropleth';
 
@@ -26,12 +27,6 @@ export const layerSettings: Record<string, React.FC<MapLayerSettingsPropTypes>>
   choropleth: ChoroplethOptions,
 };
 
-const MAP_PROVIDER = [
-  'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png',
-  'https://b.tile.openstreetmap.org/{z}/{x}/{y}.png',
-  'https://c.tile.openstreetmap.org/{z}/{x}/{y}.png',
-];
-
 export const createBaseMap = () => {
   return new TileLayer({
     data: MAP_PROVIDER,
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx
index dd407682b..48a70c7b7 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx
@@ -3,19 +3,63 @@ import { CompositeLayer, Layer } from 'deck.gl';
 import { LineLayer, ScatterplotLayer, TextLayer } from '@deck.gl/layers';
 import { CompositeLayerType, LayerProps } from '../../mapvis.types';
 import { BrushingExtension, CollisionFilterExtension } from '@deck.gl/extensions';
+import { scaleLinear, ScaleLinear, color, interpolateRgb } from 'd3';
+
+interface ColorScales {
+  [key: string]: ScaleLinear<string, string>;
+}
+
+interface NodeLinkLayerState {
+  colorScales: ColorScales;
+  [key: string]: any;
+}
 
 export class NodeLinkLayer extends CompositeLayer<CompositeLayerType> {
   static type = 'nodelink';
   private _layers: Record<string, Layer> = {};
 
+  state: NodeLinkLayerState = {
+    colorScales: {},
+  };
+
   constructor(props: LayerProps) {
     super(props);
   }
 
-  updateState({ changeFlags }: { changeFlags: any }) {
+  updateState({ props, changeFlags }: { props: any; changeFlags: any }) {
+    if (changeFlags.propsOrDataChanged) {
+      const colorScales: ColorScales = {};
+      Object.keys(props.settings[NodeLinkLayer.type].nodes).map((label) => {
+        const nodeSettings = props.settings[NodeLinkLayer.type].nodes[label];
+        const nodeDistribution = props.graphMetadata.nodes.types[label].attributes;
+        if (nodeSettings.colorByAttribute) {
+          if (nodeSettings.colorAttributeType === 'numerical') {
+            colorScales[label] = this.setNumericalColor(
+              nodeDistribution[nodeSettings?.colorAttribute]?.min,
+              nodeDistribution[nodeSettings?.colorAttribute]?.max,
+              nodeSettings.colorScale,
+            );
+          }
+        }
+      });
+      this.setState({ colorScales });
+    }
     return changeFlags.propsOrDataChanged || changeFlags.somethingChanged;
   }
 
+  setNumericalColor(min: number, max: number, colorScale: string) {
+    return scaleLinear<string>().domain([min, max]).range(['white', colorScale]).interpolate(interpolateRgb);
+  }
+
+  rgbStringToArray(rgbString: string): [number, number, number] {
+    const rgb = color(rgbString);
+    if (!rgb || !rgb.formatRgb()) return [0, 0, 0];
+    const match = rgb.formatRgb().match(/\d+/g);
+    if (!match || match.length !== 3) return [0, 0, 0];
+    const [r, g, b] = match.map(Number) as [number, number, number];
+    return [r, g, b];
+  }
+
   renderLayers() {
     const { data, settings, getNodeLocation, ml, graphMetadata, selected } = this.props;
     const layerSettings = settings[NodeLinkLayer.type];
@@ -77,7 +121,18 @@ export class NodeLinkLayer extends CompositeLayer<CompositeLayerType> {
         visible: !layerSettings?.nodes[label]?.hidden,
         data: nodes,
         pickable: true,
-        getFillColor: (d) => layerSettings?.nodes[label]?.color,
+        getFillColor: (d) => {
+          if (layerSettings?.nodes[label].colorByAttribute) {
+            const attributeValue = d.attributes[layerSettings?.nodes[label].colorAttribute];
+            if (layerSettings?.nodes[label].colorAttributeType === 'categorical') {
+              return layerSettings?.nodes[label]?.colorMapping[attributeValue];
+            } else if (layerSettings?.nodes[label].colorAttributeType === 'numerical') {
+              const colorScale = this.state.colorScales[label];
+              return this.rgbStringToArray(colorScale(attributeValue));
+            }
+          }
+          return layerSettings?.nodes[label].color;
+        },
         getPosition: (d) => getNodeLocation(d._id),
         getRadius: (d) => layerSettings?.nodes[label]?.size,
         radiusMinPixels: 5,
@@ -86,6 +141,8 @@ export class NodeLinkLayer extends CompositeLayer<CompositeLayerType> {
         stroked: true,
         updateTriggers: {
           getIcon: [selected],
+          getRadius: [layerSettings?.nodes[label].size],
+          getFillColor: [this.state.colorScales],
         },
       });
 
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
index 566817294..8d09ede74 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
@@ -3,7 +3,7 @@ import ColorPicker from '@graphpolaris/shared/lib/components/colorComponents/col
 import { Button, DropdownColorLegend, EntityPill, Icon, Input, RelationPill } from '@graphpolaris/shared/lib/components';
 import { MapProps } from '../../mapvis';
 import { LayerSettingsComponentType } from '../../mapvis.types';
-import { nodeColorHex } from '../../utils';
+import { nodeColorRGB } from '../../utils';
 
 const defaultNodeSettings = (index: number) => ({
   colorByAttribute: false,
@@ -11,7 +11,7 @@ const defaultNodeSettings = (index: number) => ({
   colorAttributeType: undefined,
   hidden: false,
   shape: 'circle',
-  color: nodeColorHex(index),
+  color: nodeColorRGB(index),
   size: 40,
 });
 
@@ -222,6 +222,65 @@ export function NodeLinkOptions({
                           })
                         }
                       />
+                      {nodeSettings.colorByAttribute && (
+                        <div>
+                          <Input
+                            inline
+                            label="Color based on"
+                            type="dropdown"
+                            value={nodeSettings.colorAttribute}
+                            options={Object.keys(graphMetadata.nodes.types[nodeType]?.attributes)}
+                            onChange={(val) =>
+                              updateLayerSettings({
+                                nodes: {
+                                  ...layerSettings.nodes,
+                                  [nodeType]: {
+                                    ...nodeSettings,
+                                    colorAttribute: String(val),
+                                    colorAttributeType: graphMetadata.nodes.types[nodeType].attributes[val].dimension,
+                                  },
+                                },
+                              })
+                            }
+                          />
+                          {nodeSettings.colorAttributeType === 'numerical' ? (
+                            <div>
+                              <p>Select color scale:</p>
+                              <DropdownColorLegend
+                                value={settings?.colorScale}
+                                onChange={(val) =>
+                                  updateLayerSettings({
+                                    nodes: { ...layerSettings.nodes, [nodeType]: { ...nodeSettings, colorScale: val } },
+                                  })
+                                }
+                              />
+                            </div>
+                          ) : nodeSettings.colorAttributeType === 'categorical' ? (
+                            <div>
+                              {graphMetadata.nodes.types[nodeType]?.attributes?.[nodeSettings?.colorAttribute]?.values.map(
+                                (attr: string) => (
+                                  <div key={attr} className="flex items-center justify-between">
+                                    <p>{attr}</p>
+                                    <ColorPicker
+                                      value={(nodeSettings?.colorMapping ?? {})[attr] ?? [0, 0, 0]}
+                                      updateValue={(val) => {
+                                        updateLayerSettings({
+                                          nodes: {
+                                            ...layerSettings.nodes,
+                                            [nodeType]: { ...nodeSettings, colorMapping: { ...nodeSettings.colorMapping, [attr]: val } },
+                                          },
+                                        });
+                                      }}
+                                    />
+                                  </div>
+                                ),
+                              )}
+                            </div>
+                          ) : (
+                            <div>Something went wrong</div>
+                          )}
+                        </div>
+                      )}
                     </div>
                   </div>
                 )}
diff --git a/libs/shared/lib/vis/visualizations/mapvis/mapvis.types.ts b/libs/shared/lib/vis/visualizations/mapvis/mapvis.types.ts
index 08ef8bd7a..6c26d6134 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/mapvis.types.ts
+++ b/libs/shared/lib/vis/visualizations/mapvis/mapvis.types.ts
@@ -25,6 +25,7 @@ export type MapNodeData = {
   colorAttribute?: string | undefined;
   colorAttributeType?: string | undefined;
   colorScale: string;
+  colorMapping?: { [id: string]: number[] };
 };
 
 export type MapEdgeData = {
diff --git a/libs/shared/lib/vis/visualizations/mapvis/utils.ts b/libs/shared/lib/vis/visualizations/mapvis/utils.ts
index d45003bd9..3593d2450 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/utils.ts
+++ b/libs/shared/lib/vis/visualizations/mapvis/utils.ts
@@ -1,6 +1,6 @@
 import { visualizationColors } from 'config';
 
-export function nodeColorHex(num: number) {
+export function nodeColorRGB(num: number) {
   const colorVal = visualizationColors.GPCat.colors[14][num % visualizationColors.GPCat.colors[14].length];
   const hex = colorVal.replace(/^#/, '');
   const r = parseInt(hex.substring(0, 2), 16);
diff --git a/libs/shared/lib/vis/visualizations/mapvis/utlis.tsx b/libs/shared/lib/vis/visualizations/mapvis/utlis.tsx
deleted file mode 100644
index 06db43a10..000000000
--- a/libs/shared/lib/vis/visualizations/mapvis/utlis.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Coordinate } from './mapvis.types';
-
-export const getDistance = (loc1: Coordinate, loc2: Coordinate): number => {
-  const [minLon, minLat]: Coordinate = loc1;
-  const [maxLon, maxLat]: Coordinate = loc2;
-  const lonDistance: number = (maxLon ?? 0) - (minLon ?? 0);
-  const latDistance: number = (maxLat ?? 0) - (minLat ?? 0);
-  const featureHypot: number = Math.hypot(lonDistance, latDistance);
-  return featureHypot;
-};
-- 
GitLab


From 19db071d963d33eb90b05e342b52120d8d9b6688 Mon Sep 17 00:00:00 2001
From: "Vink, S.A. (Sjoerd)" <s.a.vink@uu.nl>
Date: Fri, 30 Aug 2024 09:12:30 -0400
Subject: [PATCH 02/12] feat(mapAttributeColoring): hidden nodes and parse
 integers

---
 .../layers/nodelink-layer/NodeLinkLayer.tsx   | 14 ++++++++---
 .../layers/nodelink-layer/NodeLinkOptions.tsx | 24 +++++++++++--------
 2 files changed, 25 insertions(+), 13 deletions(-)

diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx
index 48a70c7b7..13303880e 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx
@@ -75,13 +75,15 @@ export class NodeLinkLayer extends CompositeLayer<CompositeLayerType> {
       return acc;
     }, {});
 
+    const hiddenNodes = new Set(data.nodes.filter((node) => layerSettings.nodes[node.label]?.hidden).map((node) => node._id));
+
     graphMetadata.edges.labels.forEach((label: string) => {
       const layerId = `${label}-edges-line`;
 
-      const edgeData = data.edges.filter((edge: any) => {
+      const edgeData = data.edges.filter((edge) => {
         const from = nodeLocations[edge.from];
         const to = nodeLocations[edge.to];
-        return from && to;
+        return from && to && !hiddenNodes.has(edge.from) && !hiddenNodes.has(edge.to);
       });
 
       this._layers[layerId] = new LineLayer({
@@ -123,10 +125,16 @@ export class NodeLinkLayer extends CompositeLayer<CompositeLayerType> {
         pickable: true,
         getFillColor: (d) => {
           if (layerSettings?.nodes[label].colorByAttribute) {
-            const attributeValue = d.attributes[layerSettings?.nodes[label].colorAttribute];
+            let attributeValue = d.attributes[layerSettings?.nodes[label].colorAttribute];
             if (layerSettings?.nodes[label].colorAttributeType === 'categorical') {
               return layerSettings?.nodes[label]?.colorMapping[attributeValue];
             } else if (layerSettings?.nodes[label].colorAttributeType === 'numerical') {
+              if (typeof attributeValue === 'string') {
+                const numericValue = parseFloat(attributeValue.replace(/[^0-9.]/g, ''));
+                if (!isNaN(numericValue)) {
+                  attributeValue = numericValue;
+                }
+              }
               const colorScale = this.state.colorScales[label];
               return this.rgbStringToArray(colorScale(attributeValue));
             }
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
index 8d09ede74..b91ed9da1 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
@@ -255,10 +255,13 @@ export function NodeLinkOptions({
                                 }
                               />
                             </div>
-                          ) : nodeSettings.colorAttributeType === 'categorical' ? (
-                            <div>
-                              {graphMetadata.nodes.types[nodeType]?.attributes?.[nodeSettings?.colorAttribute]?.values.map(
-                                (attr: string) => (
+                          ) : (
+                            nodeSettings.colorAttributeType === 'categorical' &&
+                            nodeSettings.colorAttribute && (
+                              <div>
+                                {(
+                                  graphMetadata.nodes.types[nodeType] as { attributes: { [key: string]: { values: string[] } } }
+                                )?.attributes?.[nodeSettings.colorAttribute]?.values.map((attr: string) => (
                                   <div key={attr} className="flex items-center justify-between">
                                     <p>{attr}</p>
                                     <ColorPicker
@@ -267,17 +270,18 @@ export function NodeLinkOptions({
                                         updateLayerSettings({
                                           nodes: {
                                             ...layerSettings.nodes,
-                                            [nodeType]: { ...nodeSettings, colorMapping: { ...nodeSettings.colorMapping, [attr]: val } },
+                                            [nodeType]: {
+                                              ...nodeSettings,
+                                              colorMapping: { ...nodeSettings.colorMapping, [attr]: val },
+                                            },
                                           },
                                         });
                                       }}
                                     />
                                   </div>
-                                ),
-                              )}
-                            </div>
-                          ) : (
-                            <div>Something went wrong</div>
+                                ))}
+                              </div>
+                            )
                           )}
                         </div>
                       )}
-- 
GitLab


From c9369b2ce1ed8d62615eed7544a3d8fc5cd98452 Mon Sep 17 00:00:00 2001
From: "Vink, S.A. (Sjoerd)" <s.a.vink@uu.nl>
Date: Wed, 28 Aug 2024 12:09:10 -0400
Subject: [PATCH 03/12] feat(mapAttributeColoring): coloring based on data
 attributes

---
 .../layers/nodelink-layer/NodeLinkOptions.tsx | 21 +++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
index b91ed9da1..8b2f28e7e 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
@@ -255,6 +255,27 @@ export function NodeLinkOptions({
                                 }
                               />
                             </div>
+                          ) : nodeSettings.colorAttributeType === 'categorical' ? (
+                            <div>
+                              {graphMetadata.nodes.types[nodeType]?.attributes?.[nodeSettings?.colorAttribute]?.values.map(
+                                (attr: string) => (
+                                  <div key={attr} className="flex items-center justify-between">
+                                    <p>{attr}</p>
+                                    <ColorPicker
+                                      value={(nodeSettings?.colorMapping ?? {})[attr] ?? [0, 0, 0]}
+                                      updateValue={(val) => {
+                                        updateLayerSettings({
+                                          nodes: {
+                                            ...layerSettings.nodes,
+                                            [nodeType]: { ...nodeSettings, colorMapping: { ...nodeSettings.colorMapping, [attr]: val } },
+                                          },
+                                        });
+                                      }}
+                                    />
+                                  </div>
+                                ),
+                              )}
+                            </div>
                           ) : (
                             nodeSettings.colorAttributeType === 'categorical' &&
                             nodeSettings.colorAttribute && (
-- 
GitLab


From ad603aaa4f2693f5ab8670283077f49f0755e841 Mon Sep 17 00:00:00 2001
From: "Vink, S.A. (Sjoerd)" <s.a.vink@uu.nl>
Date: Fri, 30 Aug 2024 09:12:30 -0400
Subject: [PATCH 04/12] feat(mapAttributeColoring): hidden nodes and parse
 integers

---
 .../layers/nodelink-layer/NodeLinkOptions.tsx    | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
index 8b2f28e7e..956812676 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
@@ -255,10 +255,13 @@ export function NodeLinkOptions({
                                 }
                               />
                             </div>
-                          ) : nodeSettings.colorAttributeType === 'categorical' ? (
-                            <div>
-                              {graphMetadata.nodes.types[nodeType]?.attributes?.[nodeSettings?.colorAttribute]?.values.map(
-                                (attr: string) => (
+                          ) : (
+                            nodeSettings.colorAttributeType === 'categorical' &&
+                            nodeSettings.colorAttribute && (
+                              <div>
+                                {(
+                                  graphMetadata.nodes.types[nodeType] as { attributes: { [key: string]: { values: string[] } } }
+                                )?.attributes?.[nodeSettings.colorAttribute]?.values.map((attr: string) => (
                                   <div key={attr} className="flex items-center justify-between">
                                     <p>{attr}</p>
                                     <ColorPicker
@@ -267,7 +270,10 @@ export function NodeLinkOptions({
                                         updateLayerSettings({
                                           nodes: {
                                             ...layerSettings.nodes,
-                                            [nodeType]: { ...nodeSettings, colorMapping: { ...nodeSettings.colorMapping, [attr]: val } },
+                                            [nodeType]: {
+                                              ...nodeSettings,
+                                              colorMapping: { ...nodeSettings.colorMapping, [attr]: val },
+                                            },
                                           },
                                         });
                                       }}
-- 
GitLab


From e1d7c5aaf74b07eb594215b93874908c0e409cd6 Mon Sep 17 00:00:00 2001
From: "Vink, S.A. (Sjoerd)" <s.a.vink@uu.nl>
Date: Mon, 2 Sep 2024 09:22:53 -0400
Subject: [PATCH 05/12] feat(mapAttributeColoring): fixed rebase issue

---
 .../layers/nodelink-layer/NodeLinkOptions.tsx | 27 -------------------
 1 file changed, 27 deletions(-)

diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
index 956812676..b91ed9da1 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
@@ -255,33 +255,6 @@ export function NodeLinkOptions({
                                 }
                               />
                             </div>
-                          ) : (
-                            nodeSettings.colorAttributeType === 'categorical' &&
-                            nodeSettings.colorAttribute && (
-                              <div>
-                                {(
-                                  graphMetadata.nodes.types[nodeType] as { attributes: { [key: string]: { values: string[] } } }
-                                )?.attributes?.[nodeSettings.colorAttribute]?.values.map((attr: string) => (
-                                  <div key={attr} className="flex items-center justify-between">
-                                    <p>{attr}</p>
-                                    <ColorPicker
-                                      value={(nodeSettings?.colorMapping ?? {})[attr] ?? [0, 0, 0]}
-                                      updateValue={(val) => {
-                                        updateLayerSettings({
-                                          nodes: {
-                                            ...layerSettings.nodes,
-                                            [nodeType]: {
-                                              ...nodeSettings,
-                                              colorMapping: { ...nodeSettings.colorMapping, [attr]: val },
-                                            },
-                                          },
-                                        });
-                                      }}
-                                    />
-                                  </div>
-                                ),
-                              )}
-                            </div>
                           ) : (
                             nodeSettings.colorAttributeType === 'categorical' &&
                             nodeSettings.colorAttribute && (
-- 
GitLab


From fd3bf741eb6334ec0d7a7e48b6ccebc9e6c2d78d Mon Sep 17 00:00:00 2001
From: "Vink, S.A. (Sjoerd)" <s.a.vink@uu.nl>
Date: Mon, 2 Sep 2024 10:05:09 -0400
Subject: [PATCH 06/12] feat(mapAttributeColoring): use of new tooltip style

---
 .../mapvis/components/Tooltip.tsx             | 67 ++++++++++---------
 .../layers/nodelink-layer/NodeLinkOptions.tsx |  2 +-
 .../lib/vis/visualizations/mapvis/mapvis.tsx  | 22 +++++-
 .../lib/vis/visualizations/mapvis/utils.ts    |  7 ++
 4 files changed, 63 insertions(+), 35 deletions(-)

diff --git a/libs/shared/lib/vis/visualizations/mapvis/components/Tooltip.tsx b/libs/shared/lib/vis/visualizations/mapvis/components/Tooltip.tsx
index 772e9ea72..ea5bc92fc 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/components/Tooltip.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/components/Tooltip.tsx
@@ -2,11 +2,11 @@ import React from 'react';
 import { NodeType } from '../../nodelinkvis/types';
 import { GeoJsonType } from '../mapvis.types';
 import { SearchResultType } from '../mapvis.types';
+import { TooltipProvider } from '@graphpolaris/shared/lib/components';
 
 export type NodelinkPopupProps = {
   type: 'node' | 'area' | 'location';
   data: NodeType | GeoJsonType | SearchResultType;
-  onClose: () => void;
 };
 
 const isGeoJsonType = (data: NodeType | GeoJsonType | SearchResultType): data is GeoJsonType => {
@@ -18,15 +18,30 @@ export const MapTooltip = (props: NodelinkPopupProps) => {
 
   const renderNodeDetails = (node: NodeType) => (
     <div>
-      {node.attributes &&
+      {Object.keys(node.attributes).length === 0 ? (
+        <div className="flex justify-center items-center h-full">
+          <span>No attributes</span>
+        </div>
+      ) : (
         Object.entries(node.attributes).map(([k, v]) => (
-          <div key={k} className="flex flex-row gap-3">
-            <span>{k}: </span>
-            <span className="ml-auto max-w-[10rem] text-right truncate">
-              <span title={JSON.stringify(v)}>{JSON.stringify(v)}</span>
+          <div key={k} className="flex flex-row gap-1 items-center min-h-5">
+            <span className="font-semibold truncate min-w-[40%]">{k}</span>
+            <span className="ml-auto text-right truncate grow-1 flex items-center">
+              {v !== undefined && (typeof v !== 'object' || Array.isArray(v)) && v != '' ? (
+                <span className="ml-auto text-right truncate">{typeof v === 'number' ? v.toLocaleString('de-DE') : v.toString()}</span>
+              ) : (
+                <div
+                  className={`ml-auto mt-auto h-4 w-12 border-[1px] solid border-gray`}
+                  style={{
+                    background:
+                      'repeating-linear-gradient(-45deg, transparent, transparent 6px, #eaeaea 6px, #eaeaea 8px), linear-gradient(to bottom, transparent, transparent)',
+                  }}
+                ></div>
+              )}
             </span>
           </div>
-        ))}
+        ))
+      )}
     </div>
   );
 
@@ -79,32 +94,22 @@ export const MapTooltip = (props: NodelinkPopupProps) => {
   );
 
   return (
-    <div className="text-[0.9rem] min-w-[10rem]">
-      <div className="card-body p-0">
-        <span className="px-2.5 pt-2">
-          <span>{type === 'node' ? 'Node' : 'Area'}</span>
-          <span className="float-right">
+    <TooltipProvider delayDuration={100}>
+      <div className="text-[0.9rem] min-w-[10rem]">
+        <div className="card-body p-0">
+          <div className="h-[1px] w-full bg-secondary-200"></div>
+          <div className="px-2.5 text-[0.8rem]">
             {type === 'node'
-              ? (data as NodeType)?._id
-              : isGeoJsonType(data)
-                ? data.properties?.name
-                : type === 'location'
-                  ? (data as SearchResultType)?.name
-                  : 'N/A'}
-          </span>
-        </span>
-        <div className="h-[1px] w-full bg-secondary-200"></div>
-        <div className="px-2.5 text-[0.8rem]">
-          {type === 'node'
-            ? data && 'attributes' in data
-              ? renderNodeDetails(data as NodeType)
-              : null
-            : data && isGeoJsonType(data)
-              ? renderAreaDetails(data as GeoJsonType)
-              : renderLocationDetails(data as SearchResultType)}
+              ? data && 'attributes' in data
+                ? renderNodeDetails(data as NodeType)
+                : null
+              : data && isGeoJsonType(data)
+                ? renderAreaDetails(data as GeoJsonType)
+                : renderLocationDetails(data as SearchResultType)}
+          </div>
+          <div className="h-[1px] w-full"></div>
         </div>
-        <div className="h-[1px] w-full"></div>
       </div>
-    </div>
+    </TooltipProvider>
   );
 };
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
index b91ed9da1..544e05b76 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
@@ -263,7 +263,7 @@ export function NodeLinkOptions({
                                   graphMetadata.nodes.types[nodeType] as { attributes: { [key: string]: { values: string[] } } }
                                 )?.attributes?.[nodeSettings.colorAttribute]?.values.map((attr: string) => (
                                   <div key={attr} className="flex items-center justify-between">
-                                    <p>{attr}</p>
+                                    <p className="truncate w-18">{attr}</p>
                                     <ColorPicker
                                       value={(nodeSettings?.colorMapping ?? {})[attr] ?? [0, 0, 0]}
                                       updateValue={(val) => {
diff --git a/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx b/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx
index e48b49d6f..453ca66ce 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx
@@ -7,8 +7,11 @@ import { layerTypes, createBaseMap, LayerTypes } from './layers';
 import { Node } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice';
 import { geoCentroid } from 'd3';
 import { Attribution, ActionBar, MapTooltip, MapSettings } from './components';
-import { Tooltip, TooltipContent, TooltipTrigger } from '@graphpolaris/shared/lib/components';
 import { useSelectionLayer, useCoordinateLookup } from './hooks';
+import { VisualizationTooltip } from '@graphpolaris/shared/lib/components/VisualizationTooltip';
+import { Tooltip, TooltipContent, TooltipTrigger } from '@graphpolaris/shared/lib/components/tooltip';
+import { isGeoJsonType } from './utils';
+import { NodeType } from '../nodelinkvis/types';
 
 export type MapProps = {
   layer: LayerTypes;
@@ -257,7 +260,20 @@ export const MapVis = forwardRef((props: VisualizationPropTypes<MapProps>, refEx
           <Tooltip key={index} open={true} interactive={false} boundaryElement={ref} showArrow={true}>
             <TooltipTrigger x={node.x} y={node.y} />
             <TooltipContent>
-              <MapTooltip type={node.selectedType} onClose={() => {}} data={{ ...node }} key={node._id} />
+              <VisualizationTooltip
+                name={
+                  node.selectedType === 'node'
+                    ? (node as NodeType)?._id
+                    : isGeoJsonType(node)
+                      ? node.properties?.name
+                      : node.selectedType === 'location'
+                        ? (node as SearchResultType)?.name
+                        : 'N/A'
+                }
+                colorHeader="#FB9637"
+              >
+                <MapTooltip type={node.selectedType} data={{ ...node }} key={node._id} />
+              </VisualizationTooltip>
             </TooltipContent>
           </Tooltip>
         ))}
@@ -265,7 +281,7 @@ export const MapVis = forwardRef((props: VisualizationPropTypes<MapProps>, refEx
         <Tooltip open={true} interactive={false} boundaryElement={ref} showArrow={true}>
           <TooltipTrigger x={searchResult.x} y={searchResult.y} />
           <TooltipContent>
-            <MapTooltip type="location" onClose={() => {}} data={{ ...searchResult }} key={searchResult.name} />
+            <MapTooltip type="location" data={{ ...searchResult }} key={searchResult.name} />
           </TooltipContent>
         </Tooltip>
       )}
diff --git a/libs/shared/lib/vis/visualizations/mapvis/utils.ts b/libs/shared/lib/vis/visualizations/mapvis/utils.ts
index 3593d2450..9cd704f2a 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/utils.ts
+++ b/libs/shared/lib/vis/visualizations/mapvis/utils.ts
@@ -1,4 +1,7 @@
 import { visualizationColors } from 'config';
+import { NodeType } from '../nodelinkvis/types';
+import { GeoJsonType } from './mapvis.types';
+import { SearchResultType } from './mapvis.types';
 
 export function nodeColorRGB(num: number) {
   const colorVal = visualizationColors.GPCat.colors[14][num % visualizationColors.GPCat.colors[14].length];
@@ -8,3 +11,7 @@ export function nodeColorRGB(num: number) {
   const b = parseInt(hex.substring(4, 6), 16);
   return [r, g, b];
 }
+
+export const isGeoJsonType = (data: NodeType | GeoJsonType | SearchResultType): data is GeoJsonType => {
+  return (data as GeoJsonType).properties !== undefined;
+};
-- 
GitLab


From 772124367d1b31a8bea1c66517fa353716e5aabc Mon Sep 17 00:00:00 2001
From: "Vink, S.A. (Sjoerd)" <s.a.vink@uu.nl>
Date: Mon, 2 Sep 2024 10:16:26 -0400
Subject: [PATCH 07/12] feat(mapAttributeColoring): fixed numerical color
 picker

---
 .../colorComponents/colorDropdown/index.tsx   | 34 +++++++++----------
 1 file changed, 16 insertions(+), 18 deletions(-)

diff --git a/libs/shared/lib/components/colorComponents/colorDropdown/index.tsx b/libs/shared/lib/components/colorComponents/colorDropdown/index.tsx
index f16a9a350..1ffe9ed53 100644
--- a/libs/shared/lib/components/colorComponents/colorDropdown/index.tsx
+++ b/libs/shared/lib/components/colorComponents/colorDropdown/index.tsx
@@ -51,7 +51,7 @@ export const DropdownColorLegend = ({ value, onChange }: DropdownColorLegendProp
 
   return (
     <div className="w-200 h-200 relative">
-      <DropdownContainer>
+      <DropdownContainer open={menuOpen}>
         <DropdownTrigger
           title={
             <div className="flex items-center h-4">
@@ -69,23 +69,21 @@ export const DropdownColorLegend = ({ value, onChange }: DropdownColorLegendProp
           }
           onClick={() => setMenuOpen(!menuOpen)}
         />
-        {menuOpen && (
-          <DropdownItemContainer className="absolute w-60 bg-white shadow-lg z-10">
-            <ul>
-              {Object.keys(colorStructure).map((option: string, index) => (
-                <li key={index} onClick={() => handleOptionClick(option)} className="cursor-pointer flex items-center ml-2 h-4 m-2">
-                  <ColorLegend
-                    key={index.toString() + '_colorLegend'}
-                    colors={colorStructure[option].colors}
-                    data={colorStructure[option].data}
-                    name={colorStructure[option].name}
-                    showAxis={colorStructure[option].showAxis}
-                  />
-                </li>
-              ))}
-            </ul>
-          </DropdownItemContainer>
-        )}
+        <DropdownItemContainer className="absolute w-60 bg-white shadow-lg z-10">
+          <ul>
+            {Object.keys(colorStructure).map((option: string, index) => (
+              <li key={index} onClick={() => handleOptionClick(option)} className="cursor-pointer flex items-center ml-2 h-4 m-2">
+                <ColorLegend
+                  key={index.toString() + '_colorLegend'}
+                  colors={colorStructure[option].colors}
+                  data={colorStructure[option].data}
+                  name={colorStructure[option].name}
+                  showAxis={colorStructure[option].showAxis}
+                />
+              </li>
+            ))}
+          </ul>
+        </DropdownItemContainer>
       </DropdownContainer>
     </div>
   );
-- 
GitLab


From 07e8e9fc648f7382b91ba21b55688009b0f7551f Mon Sep 17 00:00:00 2001
From: "Vink, S.A. (Sjoerd)" <s.a.vink@uu.nl>
Date: Mon, 2 Sep 2024 10:23:07 -0400
Subject: [PATCH 08/12] feat(mapAttributeColoring): handle empty category in
 attributes

---
 .../mapvis/layers/nodelink-layer/NodeLinkOptions.tsx            | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
index 544e05b76..3266577c2 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
@@ -263,7 +263,7 @@ export function NodeLinkOptions({
                                   graphMetadata.nodes.types[nodeType] as { attributes: { [key: string]: { values: string[] } } }
                                 )?.attributes?.[nodeSettings.colorAttribute]?.values.map((attr: string) => (
                                   <div key={attr} className="flex items-center justify-between">
-                                    <p className="truncate w-18">{attr}</p>
+                                    <p className="truncate w-18">{attr.length > 0 ? attr : 'Empty val'}</p>
                                     <ColorPicker
                                       value={(nodeSettings?.colorMapping ?? {})[attr] ?? [0, 0, 0]}
                                       updateValue={(val) => {
-- 
GitLab


From 98f59f14964ddfbbaff0ec33c4a07622eec02880 Mon Sep 17 00:00:00 2001
From: "Vink, S.A. (Sjoerd)" <s.a.vink@uu.nl>
Date: Wed, 4 Sep 2024 08:48:24 -0400
Subject: [PATCH 09/12] feat(mapAttributeColoring): rebasing

---
 .../layers/nodelink-layer/NodeLinkOptions.tsx | 96 ++++++-------------
 1 file changed, 30 insertions(+), 66 deletions(-)

diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
index 3266577c2..ff66a93e5 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
@@ -6,12 +6,14 @@ import { LayerSettingsComponentType } from '../../mapvis.types';
 import { nodeColorRGB } from '../../utils';
 
 const defaultNodeSettings = (index: number) => ({
+  color: nodeColorRGB(index),
+  colorMapping: {},
+  colorScale: undefined,
   colorByAttribute: false,
   colorAttribute: undefined,
   colorAttributeType: undefined,
   hidden: false,
   shape: 'circle',
-  color: nodeColorRGB(index),
   size: 40,
 });
 
@@ -161,7 +163,6 @@ export function NodeLinkOptions({
                               type="dropdown"
                               value={nodeSettings.colorAttribute}
                               options={Object.keys(graphMetadata.nodes.types[nodeType]?.attributes)}
-                              disabled={!settings.nodes}
                               onChange={(val) =>
                                 updateLayerSettings({
                                   nodes: {
@@ -177,6 +178,7 @@ export function NodeLinkOptions({
                             />
                             {nodeSettings.colorAttributeType === 'numerical' ? (
                               <div>
+                                <p>Select color scale:</p>
                                 <DropdownColorLegend
                                   value={settings?.colorScale}
                                   onChange={(val) =>
@@ -187,7 +189,32 @@ export function NodeLinkOptions({
                                 />
                               </div>
                             ) : (
-                              <div>Categorical</div>
+                              nodeSettings.colorAttributeType === 'categorical' &&
+                              nodeSettings.colorAttribute && (
+                                <div>
+                                  {(
+                                    graphMetadata.nodes.types[nodeType] as { attributes: { [key: string]: { values: string[] } } }
+                                  )?.attributes?.[nodeSettings.colorAttribute]?.values.map((attr: string) => (
+                                    <div key={attr} className="flex items-center justify-between">
+                                      <p className="truncate w-18">{attr.length > 0 ? attr : 'Empty val'}</p>
+                                      <ColorPicker
+                                        value={(nodeSettings?.colorMapping ?? {})[attr] ?? [0, 0, 0]}
+                                        updateValue={(val) => {
+                                          updateLayerSettings({
+                                            nodes: {
+                                              ...layerSettings.nodes,
+                                              [nodeType]: {
+                                                ...nodeSettings,
+                                                colorMapping: { ...nodeSettings.colorMapping, [attr]: val },
+                                              },
+                                            },
+                                          });
+                                        }}
+                                      />
+                                    </div>
+                                  ))}
+                                </div>
+                              )
                             )}
                           </div>
                         )}
@@ -222,69 +249,6 @@ export function NodeLinkOptions({
                           })
                         }
                       />
-                      {nodeSettings.colorByAttribute && (
-                        <div>
-                          <Input
-                            inline
-                            label="Color based on"
-                            type="dropdown"
-                            value={nodeSettings.colorAttribute}
-                            options={Object.keys(graphMetadata.nodes.types[nodeType]?.attributes)}
-                            onChange={(val) =>
-                              updateLayerSettings({
-                                nodes: {
-                                  ...layerSettings.nodes,
-                                  [nodeType]: {
-                                    ...nodeSettings,
-                                    colorAttribute: String(val),
-                                    colorAttributeType: graphMetadata.nodes.types[nodeType].attributes[val].dimension,
-                                  },
-                                },
-                              })
-                            }
-                          />
-                          {nodeSettings.colorAttributeType === 'numerical' ? (
-                            <div>
-                              <p>Select color scale:</p>
-                              <DropdownColorLegend
-                                value={settings?.colorScale}
-                                onChange={(val) =>
-                                  updateLayerSettings({
-                                    nodes: { ...layerSettings.nodes, [nodeType]: { ...nodeSettings, colorScale: val } },
-                                  })
-                                }
-                              />
-                            </div>
-                          ) : (
-                            nodeSettings.colorAttributeType === 'categorical' &&
-                            nodeSettings.colorAttribute && (
-                              <div>
-                                {(
-                                  graphMetadata.nodes.types[nodeType] as { attributes: { [key: string]: { values: string[] } } }
-                                )?.attributes?.[nodeSettings.colorAttribute]?.values.map((attr: string) => (
-                                  <div key={attr} className="flex items-center justify-between">
-                                    <p className="truncate w-18">{attr.length > 0 ? attr : 'Empty val'}</p>
-                                    <ColorPicker
-                                      value={(nodeSettings?.colorMapping ?? {})[attr] ?? [0, 0, 0]}
-                                      updateValue={(val) => {
-                                        updateLayerSettings({
-                                          nodes: {
-                                            ...layerSettings.nodes,
-                                            [nodeType]: {
-                                              ...nodeSettings,
-                                              colorMapping: { ...nodeSettings.colorMapping, [attr]: val },
-                                            },
-                                          },
-                                        });
-                                      }}
-                                    />
-                                  </div>
-                                ))}
-                              </div>
-                            )
-                          )}
-                        </div>
-                      )}
                     </div>
                   </div>
                 )}
-- 
GitLab


From 41e694dfa54cd63fcbd9478d0121e438e7ee2338 Mon Sep 17 00:00:00 2001
From: "Vink, S.A. (Sjoerd)" <s.a.vink@uu.nl>
Date: Wed, 4 Sep 2024 08:50:43 -0400
Subject: [PATCH 10/12] feat(mapAttributeColoring): rebasing

---
 .../mapvis/layers/nodelink-layer/NodeLinkOptions.tsx             | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
index ff66a93e5..3389f87a6 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
@@ -178,6 +178,7 @@ export function NodeLinkOptions({
                             />
                             {nodeSettings.colorAttributeType === 'numerical' ? (
                               <div>
+                                <p>Select color scale:</p>
                                 <p>Select color scale:</p>
                                 <DropdownColorLegend
                                   value={settings?.colorScale}
-- 
GitLab


From 5e3a86f0f47889aa78a123e7d8dcc3bed6e5b8c4 Mon Sep 17 00:00:00 2001
From: "Vink, S.A. (Sjoerd)" <s.a.vink@uu.nl>
Date: Wed, 4 Sep 2024 08:52:59 -0400
Subject: [PATCH 11/12] feat(mapAttributeColoring): fixed double p tag issue

---
 .../mapvis/layers/nodelink-layer/NodeLinkOptions.tsx             | 1 -
 1 file changed, 1 deletion(-)

diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
index 3389f87a6..ff66a93e5 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
@@ -178,7 +178,6 @@ export function NodeLinkOptions({
                             />
                             {nodeSettings.colorAttributeType === 'numerical' ? (
                               <div>
-                                <p>Select color scale:</p>
                                 <p>Select color scale:</p>
                                 <DropdownColorLegend
                                   value={settings?.colorScale}
-- 
GitLab


From 33e3d99736b2a340436a76ed3591a87b8390efc3 Mon Sep 17 00:00:00 2001
From: "Vink, S.A. (Sjoerd)" <s.a.vink@uu.nl>
Date: Thu, 5 Sep 2024 09:38:14 -0400
Subject: [PATCH 12/12] feat(mapAttributeColoring): fixed merge comments

---
 .../layers/nodelink-layer/NodeLinkLayer.tsx   | 22 ++++++++++---------
 .../layers/nodelink-layer/NodeLinkOptions.tsx |  3 ++-
 .../lib/vis/visualizations/mapvis/mapvis.tsx  |  2 +-
 .../vis/visualizations/mapvis/mapvis.types.ts |  2 +-
 4 files changed, 16 insertions(+), 13 deletions(-)

diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx
index 13303880e..a853da41a 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx
@@ -1,12 +1,13 @@
 import React from 'react';
 import { CompositeLayer, Layer } from 'deck.gl';
 import { LineLayer, ScatterplotLayer, TextLayer } from '@deck.gl/layers';
-import { CompositeLayerType, LayerProps } from '../../mapvis.types';
+import { CompositeLayerType, Coordinate, LayerProps } from '../../mapvis.types';
 import { BrushingExtension, CollisionFilterExtension } from '@deck.gl/extensions';
 import { scaleLinear, ScaleLinear, color, interpolateRgb } from 'd3';
+import { Node } from '@graphpolaris/shared/lib/data-access';
 
 interface ColorScales {
-  [key: string]: ScaleLinear<string, string>;
+  [label: string]: ScaleLinear<string, string>;
 }
 
 interface NodeLinkLayerState {
@@ -27,6 +28,7 @@ export class NodeLinkLayer extends CompositeLayer<CompositeLayerType> {
   }
 
   updateState({ props, changeFlags }: { props: any; changeFlags: any }) {
+    // TODO: Remove any here
     if (changeFlags.propsOrDataChanged) {
       const colorScales: ColorScales = {};
       Object.keys(props.settings[NodeLinkLayer.type].nodes).map((label) => {
@@ -67,7 +69,7 @@ export class NodeLinkLayer extends CompositeLayer<CompositeLayerType> {
     const brushingExtension = new BrushingExtension();
     const collisionFilter = new CollisionFilterExtension();
 
-    const nodeLocations = data.nodes.reduce((acc: Record<string, [number, number]>, node: any) => {
+    const nodeLocations = data.nodes.reduce((acc: Record<string, Coordinate>, node: Node) => {
       const pos = getNodeLocation(node._id);
       if (pos && (pos[0] !== 0 || pos[1] !== 0)) {
         acc[node._id] = pos;
@@ -116,7 +118,7 @@ export class NodeLinkLayer extends CompositeLayer<CompositeLayerType> {
       const layerId = `${label}-nodes-scatterplot`;
       const textLayerId = `${label}-label-target`;
 
-      const nodes = data.nodes.filter((node: any) => nodeLocations[node._id] && node.label === label);
+      const nodes = data.nodes.filter((node: Node) => nodeLocations[node._id] && node.label === label);
 
       this._layers[layerId] = new ScatterplotLayer({
         id: layerId,
@@ -141,10 +143,10 @@ export class NodeLinkLayer extends CompositeLayer<CompositeLayerType> {
           }
           return layerSettings?.nodes[label].color;
         },
-        getPosition: (d) => getNodeLocation(d._id),
-        getRadius: (d) => layerSettings?.nodes[label]?.size,
+        getPosition: (d: Node) => getNodeLocation(d._id),
+        getRadius: () => layerSettings?.nodes[label]?.size,
         radiusMinPixels: 5,
-        getLineWidth: (d: any) => (selected && selected.some((sel) => sel._id === d._id) ? 2 : 1),
+        getLineWidth: (d: Node) => (selected && selected.some((sel) => sel._id === d._id) ? 2 : 1),
         lineWidthUnits: 'pixels',
         stroked: true,
         updateTriggers: {
@@ -157,9 +159,9 @@ export class NodeLinkLayer extends CompositeLayer<CompositeLayerType> {
       this._layers[textLayerId] = new TextLayer({
         id: textLayerId,
         data: nodes,
-        getPosition: (d: any) => getNodeLocation(d._id),
-        getText: (d: any) => d.label,
-        getSize: (d: any) => (layerSettings?.nodes[label]?.size * 2) / d.label.length,
+        getPosition: (d: Node) => getNodeLocation(d._id),
+        getText: (d: Node) => d.label,
+        getSize: (d: Node) => (layerSettings?.nodes[label]?.size * 2) / d.label.length,
         getAlignmentBaseline: 'center',
         getRadius: 10,
         radiusScale: 20,
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
index ff66a93e5..70b08dd09 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx
@@ -4,6 +4,7 @@ import { Button, DropdownColorLegend, EntityPill, Icon, Input, RelationPill } fr
 import { MapProps } from '../../mapvis';
 import { LayerSettingsComponentType } from '../../mapvis.types';
 import { nodeColorRGB } from '../../utils';
+import { isEqual } from 'lodash-es';
 
 const defaultNodeSettings = (index: number) => ({
   color: nodeColorRGB(index),
@@ -55,7 +56,7 @@ export function NodeLinkOptions({
       {} as typeof edges,
     );
 
-    if (JSON.stringify(newNodes) !== JSON.stringify(nodes) || JSON.stringify(newEdges) !== JSON.stringify(edges)) {
+    if (!isEqual(newNodes, nodes) || !isEqual(newEdges, edges)) {
       updateLayerSettings({
         ...layerSettings,
         nodes: newNodes,
diff --git a/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx b/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx
index 453ca66ce..c7e6c50ba 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx
@@ -1,6 +1,6 @@
 import React, { useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react';
 import DeckGL, { DeckGLProps, DeckGLRef } from '@deck.gl/react';
-import { CompositeLayer, FlyToInterpolator, MapViewState, MapViewState, WebMercatorViewport } from '@deck.gl/core';
+import { CompositeLayer, FlyToInterpolator, MapViewState, WebMercatorViewport } from '@deck.gl/core';
 import { CompositeLayerType, Coordinate, LayerSettingsType, LocationInfo, SearchResultType } from './mapvis.types';
 import { VISComponentType, VisualizationPropTypes } from '../../common';
 import { layerTypes, createBaseMap, LayerTypes } from './layers';
diff --git a/libs/shared/lib/vis/visualizations/mapvis/mapvis.types.ts b/libs/shared/lib/vis/visualizations/mapvis/mapvis.types.ts
index 6c26d6134..fe4068e20 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/mapvis.types.ts
+++ b/libs/shared/lib/vis/visualizations/mapvis/mapvis.types.ts
@@ -25,7 +25,7 @@ export type MapNodeData = {
   colorAttribute?: string | undefined;
   colorAttributeType?: string | undefined;
   colorScale: string;
-  colorMapping?: { [id: string]: number[] };
+  colorMapping?: { [label: string]: [number, number, number] };
 };
 
 export type MapEdgeData = {
-- 
GitLab