diff --git a/libs/shared/lib/components/colorComponents/colorDropdown/index.tsx b/libs/shared/lib/components/colorComponents/colorDropdown/index.tsx
index 4dcea6e51a452087b9a09b1b86ef08c9506c5d09..f16a9a350127f4b81f6cbdd63d827ffecf8a31d5 100644
--- a/libs/shared/lib/components/colorComponents/colorDropdown/index.tsx
+++ b/libs/shared/lib/components/colorComponents/colorDropdown/index.tsx
@@ -1,7 +1,6 @@
 import React, { useState } from 'react';
 import { DropdownTrigger, DropdownContainer, DropdownItemContainer } from '@graphpolaris/shared/lib/components/dropdowns';
 import ColorLegend from '../colorLegend/index.js';
-import { DimensionType } from '@graphpolaris/shared/lib/schema/index.js';
 import { dataColors } from 'config';
 
 type TailwindColor = {
@@ -37,25 +36,21 @@ function generateTailwindColors(dataColors: any) {
 type DropdownColorLegendProps = {
   value: any;
   onChange: (val: any) => void;
-  dimension?: DimensionType;
-  distribution?: any;
 };
 
-export const DropdownColorLegend = ({ value, onChange, dimension, distribution }: DropdownColorLegendProps) => {
+export const DropdownColorLegend = ({ value, onChange }: DropdownColorLegendProps) => {
   const colorStructure = generateTailwindColors(dataColors);
-  const [selectedColorLegend, setSelectedColorLegend] = useState<any>(null);
-
+  const [selectedColorLegend, setSelectedColorLegend] = useState<TailwindColor | null>(null);
   const [menuOpen, setMenuOpen] = useState<boolean>(false);
-  const [selectedOption, setSelectedOption] = useState<any>('Select colormap');
 
   const handleOptionClick = (option: string) => {
-    setSelectedOption(option);
+    onChange(option);
     setSelectedColorLegend(colorStructure[option]);
     setMenuOpen(false);
   };
 
   return (
-    <div className="w-200 h-200">
+    <div className="w-200 h-200 relative">
       <DropdownContainer>
         <DropdownTrigger
           title={
@@ -68,24 +63,29 @@ export const DropdownColorLegend = ({ value, onChange, dimension, distribution }
                   showAxis={selectedColorLegend.showAxis}
                 />
               ) : (
-                <p className="ml-2">{selectedOption}</p>
+                <p className="ml-2">{value}</p>
               )}
             </div>
           }
+          onClick={() => setMenuOpen(!menuOpen)}
         />
-        <DropdownItemContainer className="w-60">
-          {Object.keys(colorStructure).map((option: any, 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>
-          ))}
-        </DropdownItemContainer>
+        {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>
+        )}
       </DropdownContainer>
     </div>
   );
diff --git a/libs/shared/lib/components/colorComponents/colorPicker/index.tsx b/libs/shared/lib/components/colorComponents/colorPicker/index.tsx
index bd9a01d16778937875963bab89eb4d10e26ee834..afe153a855b7670ec4acbaa4e05dd21eb1c60f0f 100644
--- a/libs/shared/lib/components/colorComponents/colorPicker/index.tsx
+++ b/libs/shared/lib/components/colorComponents/colorPicker/index.tsx
@@ -32,7 +32,7 @@ export default function ColorPicker({ value, updateValue }: Props) {
         <div
           className="w-5 h-5"
           style={{
-            backgroundColor: `rgb(${value[0]}, ${value[1]}, ${value[2]})`,
+            backgroundColor: `rgb(${value?.[0] ?? 0}, ${value?.[1] ?? 0}, ${value?.[2] ?? 0})`,
           }}
         />
       </div>
diff --git a/libs/shared/lib/vis/common/types.ts b/libs/shared/lib/vis/common/types.ts
index 988ab9acdfa353db11527ce74ac4cc15556ce8f2..a6693a8fbf3dbca0644ada3b2731ecd46a32a998 100644
--- a/libs/shared/lib/vis/common/types.ts
+++ b/libs/shared/lib/vis/common/types.ts
@@ -1,8 +1,8 @@
+import { FC } from 'react';
 import { GraphQueryResult } from '../../data-access/store/graphQueryResultSlice';
 import { ML } from '../../data-access/store/mlSlice';
 import { SchemaGraph } from '../../schema';
 import type { AppDispatch } from '../../data-access';
-import { FC, ReactElement } from 'react';
 import { GraphMetadata } from '../../data-access/statistics';
 import { Node, Edge } from '../../data-access/store/graphQueryResultSlice';
 import { Visualizations } from '../components/VisualizationPanel';
diff --git a/libs/shared/lib/vis/visualizations/mapvis/MapSettings.tsx b/libs/shared/lib/vis/visualizations/mapvis/MapSettings.tsx
deleted file mode 100644
index a5682a798b9b00f9a9b39f3cbf7292737411d0e1..0000000000000000000000000000000000000000
--- a/libs/shared/lib/vis/visualizations/mapvis/MapSettings.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import React, { useEffect, useMemo } from 'react';
-import { SettingsContainer } from '../../components/config';
-import { layerSettings, layerTypes } from './layers';
-import { Input } from '../../..';
-import { VisualizationSettingsPropTypes } from '../../common';
-import { MapEdgeData, MapNodeData, MapNodeOrEdgeData, MapProps } from './mapvis';
-
-export const MapSettings = ({ settings, graphMetadata, updateSettings }: VisualizationSettingsPropTypes<MapProps>) => {
-  const DataLayerSettings = settings.layer && layerSettings?.[settings.layer];
-
-  const spatialAttributes = useMemo(() => {
-    return Object.fromEntries(
-      graphMetadata.nodes.labels.map((node) => [
-        node,
-        Object.entries(graphMetadata.nodes.types[node].attributes)
-          .filter(([, value]) => value.dimension === 'numerical')
-          .map(([key]) => key),
-      ]),
-    );
-  }, [graphMetadata]);
-
-  useEffect(() => {
-    const nodes = Object.fromEntries(
-      graphMetadata.nodes.labels.map(
-        (node) =>
-          [
-            node,
-            {
-              color: [252, 185, 0],
-              hidden: false,
-              fixed: true,
-              min: 0,
-              max: 10,
-              radius: 1,
-              lat: undefined,
-              lon: undefined,
-              collapsed: false,
-              shape: 'circle',
-              size: 10,
-              ...(settings?.nodes && node in settings.nodes ? settings.nodes[node] : {}),
-            },
-          ] as [string, MapNodeData],
-      ),
-    );
-
-    const edges = Object.fromEntries(
-      graphMetadata.edges.labels.map(
-        (edge) =>
-          [
-            edge,
-            {
-              color: [171, 184, 195],
-              hidden: false,
-              fixed: true,
-              min: 0,
-              max: 10,
-              radius: 1,
-              sizeAttribute: undefined,
-              collapsed: false,
-              size: 10,
-              ...(settings?.edges && edge in settings.edges ? settings.edges[edge] : {}),
-            },
-          ] as [string, MapEdgeData],
-      ),
-    );
-
-    updateSettings({ nodes: nodes, edges: edges });
-  }, [graphMetadata]);
-
-  useEffect(() => {
-    // Autodetect a lat or lon attribute if not already set
-    Object.keys(settings.nodes).forEach((node) => {
-      if ((!settings.nodes[node].lat || !settings.nodes[node].lon) && node in spatialAttributes) {
-        const lat = spatialAttributes[node].find((attr) => attr.includes('latitude'));
-        const lon = spatialAttributes[node].find((attr) => attr.includes('longitude'));
-        if (lat && lon) {
-          updateSettings({ nodes: { ...settings.nodes, [node]: { ...settings.nodes[node], lat, lon } } });
-        }
-      }
-    });
-  }, [spatialAttributes, settings]);
-
-  return (
-    <SettingsContainer>
-      <Input
-        label="Data layer"
-        type="dropdown"
-        inline
-        value={settings.layer}
-        options={Object.keys(layerTypes)}
-        onChange={(val) => updateSettings({ layer: val as string })}
-      />
-      {DataLayerSettings && !!spatialAttributes && (
-        <DataLayerSettings
-          settings={settings}
-          graphMetadata={graphMetadata}
-          updateSettings={updateSettings}
-          spatialAttributes={spatialAttributes}
-        />
-      )}
-    </SettingsContainer>
-  );
-};
diff --git a/libs/shared/lib/vis/visualizations/mapvis/components/ActionBar.tsx b/libs/shared/lib/vis/visualizations/mapvis/components/ActionBar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..41f4e58db0ee063d34b3404ec76669d304cb386b
--- /dev/null
+++ b/libs/shared/lib/vis/visualizations/mapvis/components/ActionBar.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { Icon } from '@graphpolaris/shared/lib/components';
+import { BoundingBoxType } from '../mapvis.types';
+import { SearchBar } from './SearchBar';
+
+type Props = {
+  isSearching: boolean;
+  setSelectingRectangle: (val: boolean) => void;
+  setIsSearching: (val: boolean) => void;
+  flyToBoundingBox: (minLat: number, maxLat: number, minLon: number, maxLon: number) => void;
+};
+
+export default function ActionBar({ isSearching, setIsSearching, setSelectingRectangle, flyToBoundingBox }: Props) {
+  return (
+    <div>
+      <div className="absolute left-0 top-0 m-1">
+        <div className="cursor-pointer p-1 pb-0 bg-white shadow-md rounded" onClick={() => setSelectingRectangle(true)}>
+          <Icon component="icon-[ic--baseline-highlight-alt]" />
+        </div>
+        <div className="cursor-pointer p-1 mt-1 pb-0 bg-white shadow-md rounded" onClick={() => setIsSearching(!isSearching)}>
+          <Icon component="icon-[ic--outline-search]" />
+        </div>
+      </div>
+      {isSearching && (
+        <SearchBar
+          onSearch={(boundingBox: BoundingBoxType) => {
+            flyToBoundingBox(...boundingBox);
+            setIsSearching(false);
+          }}
+        />
+      )}
+    </div>
+  );
+}
diff --git a/libs/shared/lib/vis/visualizations/mapvis/components/Attribution.tsx b/libs/shared/lib/vis/visualizations/mapvis/components/Attribution.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..34d421dddf67372310c59645da5e42932d9bb85b
--- /dev/null
+++ b/libs/shared/lib/vis/visualizations/mapvis/components/Attribution.tsx
@@ -0,0 +1,12 @@
+import React from 'react';
+
+export default function Attribution() {
+  return (
+    <div className="absolute right-0 bottom-0 p-1 bg-secondary-200 bg-opacity-75 text-xs">
+      {'© '}
+      <a className="underline" href="http://www.openstreetmap.org/copyright" target="_blank" rel="noopener noreferrer">
+        OpenStreetMap
+      </a>
+    </div>
+  );
+}
diff --git a/libs/shared/lib/vis/visualizations/mapvis/components/MapSettings.tsx b/libs/shared/lib/vis/visualizations/mapvis/components/MapSettings.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..066629a480c33695eac077ffcd53e8c3cdcf5c18
--- /dev/null
+++ b/libs/shared/lib/vis/visualizations/mapvis/components/MapSettings.tsx
@@ -0,0 +1,97 @@
+import React, { useEffect, useMemo } from 'react';
+import { SettingsContainer } from '../../../components/config';
+import { layerSettings, LayerTypes, layerTypes } from '../layers';
+import { Input } from '../../../..';
+import { VisualizationSettingsPropTypes } from '../../../common';
+import { MapProps } from '../mapvis';
+import { LayerSettingsType } from '../mapvis.types';
+
+export const MapSettings = ({ settings, graphMetadata, updateSettings }: VisualizationSettingsPropTypes<MapProps>) => {
+  // For backwards compatibility with older saveStates, we migrate information from settings.nodes to settings.location
+  // FIXME: this can be removed once all systems have updated their saveStates.
+  if (!('location' in settings)) {
+    settings = JSON.parse(JSON.stringify(settings));  // Undo Object.preventExtensions()
+    settings.location = Object.entries(settings.nodes)
+      .map(([k, v]) => [k, {
+        lat: (v as any).lat,
+        lon: (v as any).lon
+      }]) 
+      .reduce((obj, [k, v]) => ({...obj, [k as string]: v}), {});
+  }
+
+  const DataLayerSettings = settings.layer && layerSettings?.[settings.layer];
+
+  const updateLayerSettings = (updatedKeyValue: Partial<LayerSettingsType>) => {
+    updateSettings({
+      ...settings,
+      [settings.layer]: {
+        ...settings[settings.layer],
+        ...updatedKeyValue,
+      },
+    });
+  };
+
+  const spatialAttributes = useMemo(() => {
+    return Object.fromEntries(
+      graphMetadata.nodes.labels.map((node) => [
+        node,
+        Object.entries(graphMetadata.nodes.types[node].attributes)
+          .filter(([, value]) => value.dimension === 'numerical')
+          .map(([key]) => key),
+      ]),
+    );
+  }, [graphMetadata]);
+
+  const updateSpatialAttribute = (label: string, attribute: 'lat' | 'lon', value: string) => {
+    updateSettings({
+      ...settings,
+      location: {
+        ...settings.location,
+        [label]: {
+          ...settings.location[label],
+          [attribute]: value,
+        },
+      },
+    });
+  };
+
+  useEffect(() => {
+    // Autodetect a lat or lon attribute if not already set
+    graphMetadata.nodes.labels.forEach((node) => {
+      if ((!settings.location[node]?.lat || !settings.location[node]?.lon) && node in spatialAttributes) {
+        const lat = spatialAttributes[node].find((attr) => attr.includes('latitude'));
+        const lon = spatialAttributes[node].find((attr) => attr.includes('longitude'));
+        if (lat && lon) {
+          updateSettings({
+            location: {
+              ...settings.location,
+              [node]: { ...settings.location[node], lat, lon },
+            },
+          });
+        }
+      }
+    });
+  }, [spatialAttributes, settings, updateSettings]);
+
+  return (
+    <SettingsContainer>
+      <Input
+        label="Data layer"
+        type="dropdown"
+        inline
+        value={settings.layer}
+        options={Object.keys(layerTypes)}
+        onChange={(val) => updateSettings({ layer: val as LayerTypes })}
+      />
+      {DataLayerSettings && !!spatialAttributes && (
+        <DataLayerSettings
+          graphMetadata={graphMetadata}
+          settings={settings}
+          updateLayerSettings={updateLayerSettings}
+          spatialAttributes={spatialAttributes}
+          updateSpatialAttribute={updateSpatialAttribute}
+        />
+      )}
+    </SettingsContainer>
+  );
+};
diff --git a/libs/shared/lib/vis/visualizations/mapvis/SearchBar.tsx b/libs/shared/lib/vis/visualizations/mapvis/components/SearchBar.tsx
similarity index 93%
rename from libs/shared/lib/vis/visualizations/mapvis/SearchBar.tsx
rename to libs/shared/lib/vis/visualizations/mapvis/components/SearchBar.tsx
index d6d37127e623dfd4b31ece91e4ea2bd57753ccc1..05626d492f7ea4bf653400a37f0c44dfeaac0da3 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/SearchBar.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/components/SearchBar.tsx
@@ -2,9 +2,10 @@ import { Button, Input } from '@graphpolaris/shared/lib/components';
 import { useAppDispatch } from '@graphpolaris/shared/lib/data-access';
 import { addError } from '@graphpolaris/shared/lib/data-access/store/configSlice';
 import React, { useState } from 'react';
+import { BoundingBoxType } from '../mapvis.types';
 
 interface SearchBarProps {
-  onSearch: (boundingBox: [number, number, number, number]) => void;
+  onSearch: (boundingBox: BoundingBoxType) => void;
 }
 
 export const SearchBar: React.FC<SearchBarProps> = ({ onSearch }) => {
diff --git a/libs/shared/lib/vis/visualizations/mapvis/components/Tooltip.tsx b/libs/shared/lib/vis/visualizations/mapvis/components/Tooltip.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..dd1d546bfc32d7fe25876c6f917c8e40e8c56ed9
--- /dev/null
+++ b/libs/shared/lib/vis/visualizations/mapvis/components/Tooltip.tsx
@@ -0,0 +1,85 @@
+import React from 'react';
+import { Tooltip, TooltipContent, TooltipTrigger } from '@graphpolaris/shared/lib/components';
+import { NodeType } from '../../nodelinkvis/types';
+import { GeoJsonType } from '../mapvis.types';
+
+export type NodelinkPopupProps = {
+  type: 'node' | 'area';
+  data: {
+    node: NodeType | GeoJsonType;
+    pos: {
+      x: number;
+      y: number;
+    };
+  };
+  onClose: () => void;
+};
+
+const isGeoJsonType = (data: NodeType | GeoJsonType): data is GeoJsonType => {
+  return (data as GeoJsonType).properties !== undefined;
+};
+
+export const MapTooltip = (props: NodelinkPopupProps) => {
+  const { type, data } = props;
+
+  const renderNodeDetails = (node: NodeType) => (
+    <div>
+      {node.attributes &&
+        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>
+            </span>
+          </div>
+        ))}
+    </div>
+  );
+
+  const renderAreaDetails = (geoJson: GeoJsonType) => (
+    <div>
+      <div className="flex flex-row gap-3">
+        <span>Area id: </span>
+        <span className="ml-auto max-w-[10rem] text-right truncate">
+          <span>{geoJson.properties?.regioFacetId ?? 'N/A'}</span>
+        </span>
+      </div>
+      <div className="flex flex-row gap-3">
+        <span>Nodes in area: </span>
+        <span className="ml-auto max-w-[10rem] text-right truncate">
+          <span>{geoJson.properties?.nodes?.length ?? 0}</span>
+        </span>
+      </div>
+      <div className="flex flex-row gap-3">
+        <span>Townships in area: </span>
+        <span className="ml-auto max-w-[10rem] text-right truncate">
+          <span>{geoJson.properties?.townships?.length ?? 0}</span>
+        </span>
+      </div>
+    </div>
+  );
+
+  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">
+            {type === 'node' ? (data.node as NodeType)?._id : isGeoJsonType(data.node) ? data.node.properties?.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.node && 'attributes' in data.node
+              ? renderNodeDetails(data.node as NodeType)
+              : null
+            : data.node && isGeoJsonType(data.node)
+              ? renderAreaDetails(data.node as GeoJsonType)
+              : null}
+        </div>
+        <div className="h-[1px] w-full"></div>
+      </div>
+    </div>
+  );
+};
diff --git a/libs/shared/lib/vis/visualizations/mapvis/hooks/index.ts b/libs/shared/lib/vis/visualizations/mapvis/hooks/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b0c0055061f1ac67f1429eeb5e2549338f37b1ee
--- /dev/null
+++ b/libs/shared/lib/vis/visualizations/mapvis/hooks/index.ts
@@ -0,0 +1,2 @@
+export * from './useCoordinateLookup';
+export * from './useSelectionLayer';
diff --git a/libs/shared/lib/vis/visualizations/mapvis/hooks/useCoordinateLookup.tsx b/libs/shared/lib/vis/visualizations/mapvis/hooks/useCoordinateLookup.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b313c97e1298448ebb544ccbdc40001db3fce14e
--- /dev/null
+++ b/libs/shared/lib/vis/visualizations/mapvis/hooks/useCoordinateLookup.tsx
@@ -0,0 +1,25 @@
+import { useMemo } from 'react';
+import { LocationInfo, Coordinate } from '../mapvis.types';
+import { Node } from '@graphpolaris/shared/lib/data-access';
+
+export const useCoordinateLookup = (nodes: Node[], locationSettings: Record<string, LocationInfo>) => {
+  return useMemo(() => {
+    return nodes.reduce(
+      (acc, node) => {
+        const latitude = locationSettings?.[node.label]?.lat
+          ? (node?.attributes?.[locationSettings[node.label].lat as any] as string)
+          : undefined;
+        const longitude = locationSettings?.[node.label]?.lon
+          ? (node?.attributes?.[locationSettings[node.label].lon as any] as string)
+          : undefined;
+
+        if (latitude !== undefined && longitude !== undefined) {
+          acc[node._id] = [parseFloat(longitude), parseFloat(latitude)];
+        }
+
+        return acc;
+      },
+      {} as { [id: string]: Coordinate },
+    );
+  }, [nodes, locationSettings]);
+};
diff --git a/libs/shared/lib/vis/visualizations/mapvis/hooks/useSelectionLayer.tsx b/libs/shared/lib/vis/visualizations/mapvis/hooks/useSelectionLayer.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..51aa3abe5f3431d308e3f803db5ab18621fa6c3d
--- /dev/null
+++ b/libs/shared/lib/vis/visualizations/mapvis/hooks/useSelectionLayer.tsx
@@ -0,0 +1,18 @@
+import { useMemo } from 'react';
+import { SelectionLayer } from '@deck.gl-community/editable-layers';
+import { Coordinate } from '../mapvis.types';
+
+export const useSelectionLayer = (selectingRectangle: boolean, layerIds: string[], onSelect: (pickingInfos: any[]) => void) => {
+  return useMemo(() => {
+    return (
+      selectingRectangle &&
+      new SelectionLayer({
+        id: 'selection',
+        selectionType: 'rectangle',
+        onSelect: ({ pickingInfos }: { pickingInfos: any[] }) => onSelect(pickingInfos),
+        layerIds: layerIds,
+        getTentativeFillColor: () => [22, 37, 67, 100],
+      })
+    );
+  }, [selectingRectangle, layerIds, onSelect]);
+};
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/choropleth-layer/ChoroplethLayer.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/choropleth-layer/ChoroplethLayer.tsx
index 065e675b5633bfeda624e668cee9138c994204cd..9974f74c4febd8ac6db772c946bda5a8ed46ebc5 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/choropleth-layer/ChoroplethLayer.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/choropleth-layer/ChoroplethLayer.tsx
@@ -1,290 +1,204 @@
 import React from 'react';
-import { CompositeLayer } from 'deck.gl';
-import { ArcLayer, GeoJsonLayer, ScatterplotLayer } from '@deck.gl/layers';
-import { europeData, usaData, worldData, netherlands } from '../../../../../mock-data/geo-json';
-import { Edge, Node, LayerProps } from '../../mapvis.types';
-import { RGBColor, color, geoBounds, geoCentroid, geoContains, interpolateYlGnBu, scaleSequential } from 'd3';
-
-export const circumferencesMap = {
-  netherlands: netherlands,
-  europe: europeData,
-  usa: usaData,
-  world: worldData,
+import { CompositeLayer, Layer } from 'deck.gl';
+import { ArcLayer, GeoJsonLayer } from '@deck.gl/layers';
+import { netherlands } from '../../../../../mock-data/geo-json';
+import { Edge, Node, LayerProps, CompositeLayerType, Coordinate } from '../../mapvis.types';
+import { RGBColor, color, geoBounds, geoCentroid, geoContains, scaleSequential, interpolateYlOrRd } from 'd3';
+import * as d3 from 'd3';
+
+const colorScales: Record<string, any> = {
+  green: d3.interpolateGreens,
+  blue: d3.interpolateBlues,
+  red: d3.interpolateReds,
+  orange: d3.interpolateOranges,
+  purple: d3.interpolatePurples,
 };
 
-export const dataTypeMap = {
-  nodes: 'nodes',
-  edges: 'edges',
-};
+export class ChoroplethLayer extends CompositeLayer<CompositeLayerType> {
+  static layerName = 'choropleth';
+  private _layers: Record<string, Layer> = {};
 
-export const optionsMap = {
-  nodes: {
-    graphName: 'nodeAttributes',
-    operations: {
-      average: {
-        options: true,
-      },
-      sum: {
-        options: true,
-      },
-    },
-  },
-  edges: {
-    graphName: 'edgeAttributes',
-    operations: {
-      withinEdges: {},
-      OutgoingEdges: {},
-      IncomingEdges: {},
-    },
-  },
-};
+  constructor(props: LayerProps) {
+    super(props);
+  }
 
-export const operationsMap = {
-  nodes: {
-    frequency: 'frequency',
-    average: 'average',
-    sum: 'sum',
-  },
-  edges: {
-    withinEdges: 'withinEdges',
-    OutgoingEdges: 'OutgoingEdges',
-    IncomingEdges: 'IncomingEdges',
-  },
-};
+  updateState({ props, oldProps, changeFlags }: { props: any; oldProps: any; context: any; changeFlags: any }) {
+    if (changeFlags.dataChanged) {
+      const geojsonData = this.concatData(netherlands, props.data.nodes);
+      this.setState({ geojsonData });
+    }
 
-export const graphMap = {
-  nodes: 'nodeAttributes',
-  edges: 'edgeAttributes',
-};
+    if (changeFlags.propsChanged) this.extractColorInformation();
+  }
 
-export const ChoroplethConfig = {
-  circumference: 'netherlands',
-  data: {
-    type: '',
-    operation: '',
-    attribute: '',
-  },
-  color: {},
-  edgesOnHover: true,
-  pitchOnSelect: true,
-};
+  concatData(geojson: any, nodes: Node[]) {
+    const updatedGeojson = { ...geojson };
 
-export class ChoroplethLayer extends CompositeLayer<LayerProps> {
-  static type = 'Choropleth';
+    nodes.forEach((node: Node) => {
+      const coordinates: Coordinate = this.props.getNodeLocation(node.id);
+
+      if (coordinates) {
+        updatedGeojson.features.forEach((feature: any) => {
+          if (geoContains(feature, coordinates)) {
+            feature.properties.nodes = feature.properties.nodes ?? [];
+            feature.properties.nodes.push(node.id);
+
+            feature.properties.townships.forEach((township: any) => {
+              if (geoContains(township, coordinates)) {
+                township.properties.nodes = township.properties.nodes ?? [];
+                township.properties.nodes.push(node.id);
+              }
+            });
+          }
+        });
+      }
+    });
 
-  shouldUpdateState({ changeFlags }: { changeFlags: any }) {
-    return changeFlags.propsOrDataChanged || changeFlags.somethingChanged;
+    return updatedGeojson;
   }
 
-  updateState({ props, oldProps, context, changeFlags }: { props: any; oldProps: any; context: any; changeFlags: any }) {
-    if (changeFlags.dataChanged) {
-      const nodes = props.graph.getNodes();
-      const geojson = circumferencesMap[props.config.circumference as keyof typeof circumferencesMap];
-      const data = this.concatData(geojson, nodes);
-      this.setState({ data });
-    }
+  setColorScale(min: number, max: number) {
+    const { settings } = this.props;
+    const colorSettings = settings[ChoroplethLayer.layerName]?.colorScale ?? 'blue';
+    const colorScale = colorScales[colorSettings];
+    const sequentialScale = d3.scaleSequential(colorScale).domain([min, max]);
+    this.setState({ colorScale: sequentialScale });
+  }
 
-    if (props.selected != oldProps.selected) {
-      const selected = props.selected;
-      this.setState({ selected });
+  extractColorInformation() {
+    const { settings, data } = this.props;
+    const { geojsonData } = this.state;
 
-      if (!props.isSelecting) {
-        let bounds;
-        let flyOptions;
+    let min = Infinity;
+    let max = -Infinity;
 
-        if (props.selected.length == 0) {
-          bounds = geoBounds(this.state.data as any);
-          flyOptions = { pitch: 0 };
-        } else if (props.selected.length == 1) {
-          bounds = geoBounds(props.selected[0]);
-          flyOptions = { pitch: props.config.pitchOnSelect ? 50 : 0 };
-        }
+    const updateMinMax = (value: number) => {
+      if (min > value) min = value;
+      if (max < value) max = value;
+    };
 
-        if (bounds) {
-          props.flyToBoundingBox(bounds[0][1], bounds[1][1], bounds[0][0], bounds[1][0], flyOptions);
-        }
+    (geojsonData as any).features.forEach((feature: any) => {
+      const nodes = feature.properties?.nodes ?? [];
+
+      switch (settings[ChoroplethLayer.layerName].coloringStrategy) {
+        case 'Node count':
+          updateMinMax(nodes.length);
+          break;
+
+        case 'Edge count':
+          updateMinMax(data.edges.filter((edge) => nodes.includes(edge.from) && nodes.includes(edge.to)).length);
+          break;
+
+        case 'Incoming edges':
+          updateMinMax(data.edges.filter((edge) => nodes.includes(edge.to)).length);
+          break;
+
+        case 'Outgoing edges':
+          updateMinMax(data.edges.filter((edge) => nodes.includes(edge.from)).length);
+          break;
+
+        case 'Connected edges':
+          updateMinMax(data.edges.filter((edge) => nodes.includes(edge.from) || nodes.includes(edge.to)).length);
+          break;
+
+        case 'Attribute':
+          break;
+
+        default:
+          break;
       }
-    }
+    });
 
-    if (changeFlags.propsChanged) {
-      const { type, operation, attribute } = props.config.data;
-    }
+    this.setColorScale(min, max);
   }
 
-  concatData(geojson: any, nodes: Node[]) {
-    const updatedGeojson = { ...geojson };
-    nodes.map((node: Node) => {
-      const coordinates: [number, number] = [node.attributes.long, node.attributes.lat];
-      updatedGeojson.features.map((feature: any) => {
-        const isInside = geoContains(feature, coordinates);
-        if (isInside) {
-          feature.properties.nodes = feature.properties.nodes ?? [];
-          feature.properties.nodes.push(node.id);
-          const nIncomingEdges: number = node.connectedEdges.filter((edge: string) => this.props.graph.getEdge(edge).to === node.id).length;
-          const nOutgoingEdges: number = node.connectedEdges.filter(
-            (edge: string) => this.props.graph.getEdge(edge).from === node.id,
-          ).length;
-
-          feature.properties.incomingEdges =
-            feature.properties.incomingEdges !== undefined ? feature.properties.incomingEdges + nIncomingEdges : nIncomingEdges;
-
-          feature.properties.outgoingEdges =
-            feature.properties.outgoingEdges !== undefined ? feature.properties.outgoingEdges + nOutgoingEdges : nOutgoingEdges;
-
-          feature.properties.townships.map((township: any) => {
-            const isInside = geoContains(township, coordinates);
-            if (isInside) {
-              township.properties.nodes = township.properties.nodes ?? [];
-              township.properties.nodes.push(node.id);
-              const nIncomingEdges: number = node.connectedEdges.filter(
-                (edge: string) => this.props.graph.getEdge(edge).to === node.id,
-              ).length;
-              const nOutgoingEdges: number = node.connectedEdges.filter(
-                (edge: string) => this.props.graph.getEdge(edge).from === node.id,
-              ).length;
-
-              township.properties.incomingEdges =
-                township.properties.incomingEdges !== undefined ? township.properties.incomingEdges + nIncomingEdges : nIncomingEdges;
-
-              township.properties.outgoingEdges =
-                township.properties.outgoingEdges !== undefined ? township.properties.outgoingEdges + nOutgoingEdges : nOutgoingEdges;
-            }
-          });
-          return;
-        }
-      });
-    });
-    return updatedGeojson;
+  getColorFromScale(value: number): [number, number, number] {
+    const { colorScale } = this.state;
+    if (!colorScale) return [100, 0, 0];
+
+    // @ts-ignore
+    const color = d3.rgb(colorScale(value));
+    return [color.r, color.g, color.b];
   }
 
-  setColorScale() {}
+  getColor(polygon: any): [number, number, number] {
+    const { data, settings } = this.props;
 
-  getColor(polygon: any) {
-    const length = polygon.properties.outgoingEdges ?? 1;
-    const colorScale = scaleSequential(interpolateYlGnBu);
-    const _color = color(colorScale(length)) as RGBColor;
+    const nodes = polygon.properties?.nodes ?? [];
 
-    if (_color) {
-      return [_color.r, _color.g, _color.b];
-    }
-    return [100, 0, 0];
-  }
+    switch (settings[ChoroplethLayer.layerName].coloringStrategy) {
+      case 'Node count':
+        return this.getColorFromScale(nodes.length);
 
-  renderLayers() {
-    const { graph, config, hoverObject, isSelecting, getNodeLocation } = this.props;
-    const { data, selected } = this.state;
-    const layers: any = [];
+      case 'Edge count':
+        return this.getColorFromScale(data.edges.filter((edge) => nodes.includes(edge.from) && nodes.includes(edge.to)).length);
 
-    if (isSelecting) {
-      const nodes = (selected as any).flatMap((feature: any) => feature.properties.nodes ?? []);
+      case 'Incoming edges':
+        return this.getColorFromScale(data.edges.filter((edge) => nodes.includes(edge.to)).length);
 
-      if (nodes.length > 0) {
-        const filteredEdges = graph.getEdges().filter((edge: Edge) => {
-          return nodes.some((node: string) => node === edge.from || node === edge.to);
-        });
+      case 'Outgoing edges':
+        return this.getColorFromScale(data.edges.filter((edge) => nodes.includes(edge.from)).length);
 
-        if (filteredEdges.length > 0) {
-          layers.push([
-            new ArcLayer(
-              this.getSubLayerProps({
-                id: 'selected-edges',
-                data: filteredEdges,
-                pickable: true,
-                getWidth: (d: any) => 0.5,
-                getSourcePosition: (d: any) => getNodeLocation(d.from),
-                getTargetPosition: (d: any) => getNodeLocation(d.to),
-                getSourceColor: (d: any) => [220, 220, 220],
-                getTargetColor: (d: any) => [220, 220, 220],
-              }),
-            ),
-            new ScatterplotLayer(
-              this.getSubLayerProps({
-                id: 'selected-nodes',
-                data: filteredEdges,
-                pickable: true,
-                opacity: 1,
-                filled: true,
-                radiusScale: 3,
-                radiusMinPixels: 2,
-                radiusMaxPixels: 100,
-                getFillColor: (d: any) => [0, 0, 0],
-                getRadius: (d: any) => 1,
-                getPosition: (d: any) => graph.getNodeLocation(d.to),
-              }),
-            ),
-          ]);
-        }
-      }
+      case 'Connected edges':
+        return this.getColorFromScale(data.edges.filter((edge) => nodes.includes(edge.from) || nodes.includes(edge.to)).length);
+
+      case 'Attribute':
+        return [0, 0, 0];
+
+      default:
+        return [0, 0, 0];
     }
+  }
 
-    if (hoverObject && config.edgesOnHover) {
-      const nodes = hoverObject.properties.nodes ?? [];
-      if (nodes) {
-        const filteredEdges = graph.getEdges().filter((edge: Edge) => {
-          return nodes.some((node: string) => node === edge.from || node === edge.to);
-        });
+  renderLayers() {
+    const { geojsonData } = this.state;
+    const { hoverObject, getNodeLocation, data, settings } = this.props;
+    const layerSettings = settings[ChoroplethLayer.layerName];
+
+    (geojsonData as any).features.forEach((feature: any) => {
+      const layerId = `${feature.properties.name}-area`;
+
+      this._layers[layerId] = new GeoJsonLayer({
+        id: `feature-layer-${feature.properties.name}`,
+        data: feature,
+        opacity: layerSettings?.opacity ?? 0.3,
+        pickable: true,
+        stroked: true,
+        filled: true,
+        extruded: false,
+        lineWidthScale: 0.5,
+        lineWidthMinPixels: 1,
+        getLineWidth: () => 1,
+        getLineColor: () => [220, 220, 220],
+        getFillColor: (d: any) => this.getColor(d),
+        updateTriggers: {
+          getFillColor: [layerSettings.coloringStrategy, layerSettings.colorScale, layerSettings.opacity],
+        },
+      });
+    });
 
+    if (hoverObject && layerSettings.enableBrushing) {
+      const nodes = (hoverObject as { properties?: { nodes: string[] } }).properties?.nodes ?? [];
+      if (nodes) {
+        const filteredEdges = data.edges.filter((edge) => nodes.includes(edge.from) || nodes.includes(edge.to));
+        // @ts-ignore
         const centroid = geoCentroid(hoverObject);
 
         if (filteredEdges.length > 0) {
-          layers.push([
-            new ArcLayer(
-              this.getSubLayerProps({
-                id: 'hovered-edges',
-                data: filteredEdges,
-                pickable: true,
-                getWidth: (d: any) => 0.5,
-                getSourcePosition: (d: any) => centroid,
-                getTargetPosition: (d: any) => graph.getNodeLocation(d.to),
-                getSourceColor: (d: any) => [220, 220, 220],
-                getTargetColor: (d: any) => [220, 220, 220],
-              }),
-            ),
-            new ScatterplotLayer(
-              this.getSubLayerProps({
-                id: 'hovered-nodes',
-                data: filteredEdges,
-                pickable: true,
-                opacity: 1,
-                filled: true,
-                radiusScale: 3,
-                radiusMinPixels: 2,
-                radiusMaxPixels: 100,
-                getFillColor: (d: any) => [0, 0, 0],
-                getRadius: (d: any) => 1,
-                getPosition: (d: any) => graph.getNodeLocation(d.to),
-              }),
-            ),
-          ]);
+          this._layers['hovered-edges'] = new ArcLayer({
+            id: 'hovered-edges',
+            data: filteredEdges,
+            pickable: true,
+            getWidth: () => 0.5,
+            getSourcePosition: () => centroid,
+            getTargetPosition: (d: any) => getNodeLocation(d.to),
+            getSourceColor: (d: any) => layerSettings.edges[d.label].color,
+            getTargetColor: (d: any) => layerSettings.edges[d.label].color,
+          });
         }
       }
     }
 
-    (data as any).features.forEach((feature: any) => {
-      const isFeatureSelected = (selected as any).includes(feature);
-
-      layers.push(
-        new GeoJsonLayer(
-          this.getSubLayerProps({
-            id: `feature-layer-${feature.properties.name}`,
-            data: isFeatureSelected ? feature.properties.townships : feature,
-            opacity: isFeatureSelected ? 0.5 : 0.3,
-            pickable: true,
-            stroked: true,
-            filled: true,
-            extruded: false,
-            lineWidthScale: 0.5,
-            lineWidthMinPixels: 1,
-            getLineWidth: (d: any) => 1,
-            getLineColor: (d: any) => [220, 220, 220],
-            getFillColor: (d: any) => this.getColor(d),
-          }),
-        ),
-      );
-    });
-
-    return [...layers];
+    return Object.values(this._layers);
   }
 }
-
-ChoroplethLayer.layerName = 'Choropleth';
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/choropleth-layer/ChoroplethOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/choropleth-layer/ChoroplethOptions.tsx
index a05d7e7bb27c3c425d7061d924f971acc1aa8acd..6f19671e936b46a569e4e865af62a7c1f90d38b0 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/choropleth-layer/ChoroplethOptions.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/choropleth-layer/ChoroplethOptions.tsx
@@ -1,84 +1,181 @@
-import React, { useState, useMemo, useEffect } from 'react';
+import React, { useEffect } from 'react';
 import ColorPicker from '@graphpolaris/shared/lib/components/colorComponents/colorPicker';
-import { VisualizationSettingsPropTypes } from '@graphpolaris/shared/lib/vis/common';
 import { MapProps } from '../../mapvis';
-import { EntityPill, Icon, Input, RelationPill } from '@graphpolaris/shared/lib/components';
+import { Button, DropdownColorLegend, EntityPill, Input, RelationPill } from '@graphpolaris/shared/lib/components';
+import { LayerSettingsComponentType } from '../../mapvis.types';
 
-export function ChoroplethOptions({ settings, graphMetadata, updateSettings }: VisualizationSettingsPropTypes<MapProps>) {
-  const [collapsed, setCollapsed] = useState<Record<string, boolean>>({});
+const areaColoringStrategies = ['Node count', 'Edge count', 'Incoming edges', 'Outgoing edges', 'Connected edges', 'Attribute'];
+
+export type coloringStrategiesType = 'Node count' | 'Edge count' | 'Incoming edges' | 'Outgoing edges' | 'Connected edges' | 'Attribute';
+
+export function ChoroplethOptions({
+  settings,
+  graphMetadata,
+  spatialAttributes,
+  updateLayerSettings,
+  updateSpatialAttribute,
+}: LayerSettingsComponentType<MapProps>) {
+  const layerType = 'choropleth';
+  const layerSettings = settings[layerType];
 
   useEffect(() => {
-    graphMetadata.nodes.labels.forEach((node) => {
-      updateSettings({
-        [node]: {
-          color: [0, 0, 0],
-          hidden: false,
-          fixed: true,
-          min: 0,
-          max: 10,
-          sizeAttribute: '',
-          lon: '',
-          lat: '',
-          ...settings?.[node],
+    if (!layerSettings) {
+      const initialSettingsObject = { coloringStrategy: 'Node count', colorScale: 'orange', opacity: 0.8, nodes: {}, edges: {} };
+
+      graphMetadata.nodes.labels.forEach((node) => {
+        initialSettingsObject.nodes = {
+          ...initialSettingsObject.nodes,
+          [node]: {
+            color: [0, 0, 0],
+            hidden: false,
+            fixed: true,
+            min: 0,
+            max: 10,
+            sizeAttribute: '',
+          },
+        };
+      });
+
+      graphMetadata.edges.labels.forEach((edge) => {
+        initialSettingsObject.edges = {
+          ...initialSettingsObject.edges,
+          [edge]: {
+            color: [0, 0, 0],
+            onHover: true,
+          },
+        };
+      });
+
+      updateLayerSettings({ ...initialSettingsObject });
+    }
+  }, [graphMetadata, layerType, settings, updateLayerSettings]);
+
+  const handleCollapseToggle = (type: string, itemType: 'nodes' | 'edges') => {
+    if (layerSettings) {
+      updateLayerSettings({
+        [itemType]: {
+          ...layerSettings[itemType],
+          [type]: {
+            ...layerSettings[itemType][type],
+            collapsed: !layerSettings[itemType][type]?.collapsed ?? true,
+          },
         },
       });
-    });
-  }, [graphMetadata]);
-
-  const spatialAttributes: { [id: string]: string[] } = {};
-  graphMetadata.nodes.labels.forEach((node) => {
-    spatialAttributes[node] = Object.entries(graphMetadata.nodes.types[node].attributes)
-      .filter(([, value]) => value.dimension === 'numerical')
-      .map(([key]) => key);
-  });
-
-  const handleCollapseToggle = (nodeType: string) => {
-    setCollapsed((prevCollapsed) => ({
-      ...prevCollapsed,
-      [nodeType]: !prevCollapsed[nodeType],
-    }));
+    }
   };
 
   return (
-    <div>
-      {graphMetadata.nodes.labels.map((nodeType) => (
-        <div className="mt-2" key={nodeType}>
-          <div className="flex items-center">
-            <div className="w-3/4 mr-6 cursor-pointer" onClick={() => handleCollapseToggle(nodeType)}>
-              <EntityPill title={nodeType} />
-            </div>
-            <div className="w-1/2">
-              {/* <ColorPicker
-                value={settings?.[nodeType]?.['color'] ? settings?.[nodeType]?.['color'] : [0, 0, 0]}
-                updateValue={(val: number[]) => updateSettings({ [nodeType]: { ...settings?.[nodeType], color: val } })}
-              /> */}
+    layerSettings && (
+      <div>
+        <div className="mt-2">
+          <p className="text-bold">Area color</p>
+          <Input
+            inline
+            label="Based on"
+            type="dropdown"
+            value={layerSettings?.coloringStrategy}
+            options={areaColoringStrategies}
+            onChange={(val) => updateLayerSettings({ coloringStrategy: val as coloringStrategiesType })}
+          />
+          <DropdownColorLegend value={settings?.colorScale} onChange={(val) => updateLayerSettings({ colorScale: val })} />
+          <Input
+            label="Opacity"
+            type="slider"
+            min={0}
+            max={1}
+            step={0.05}
+            unit="%"
+            value={layerSettings?.opacity ?? 0.8}
+            onChange={(val) => updateLayerSettings({ opacity: val as number })}
+          />
+        </div>
+
+        {graphMetadata.nodes.labels.map((nodeType) => {
+          const nodeSettings = layerSettings?.nodes?.[nodeType] || {};
+
+          return (
+            <div className="mt-2" key={nodeType}>
+              <div className="flex items-center">
+                <Button
+                  size="2xs"
+                  iconComponent={nodeSettings.collapsed ? 'icon-[ic--baseline-arrow-right]' : 'icon-[ic--baseline-arrow-drop-down]'}
+                  variant="ghost"
+                  onClick={() => handleCollapseToggle(nodeType, 'nodes')}
+                />
+                <div className="w-3/4 mr-6 cursor-pointer" onClick={() => handleCollapseToggle(nodeType, 'nodes')}>
+                  <EntityPill title={nodeType} />
+                </div>
+              </div>
+
+              {!nodeSettings.collapsed && (
+                <div>
+                  <Input
+                    inline
+                    label="Latitude"
+                    type="dropdown"
+                    value={settings?.location[nodeType].lat}
+                    options={[...spatialAttributes[nodeType]]}
+                    disabled={spatialAttributes[nodeType].length < 1}
+                    onChange={(val) => updateSpatialAttribute(nodeType, 'lat', val as string)}
+                  />
+                  <Input
+                    inline
+                    label="Longitude"
+                    type="dropdown"
+                    value={settings?.location[nodeType].lon}
+                    options={[...spatialAttributes[nodeType]]}
+                    disabled={spatialAttributes[nodeType].length < 1}
+                    onChange={(val) => updateSpatialAttribute(nodeType, 'lon', val as string)}
+                  />
+                </div>
+              )}
             </div>
-          </div>
-
-          {!collapsed[nodeType] && (
-            <div>
-              <Input
-                inline
-                label="Latitude"
-                type="dropdown"
-                value={settings?.[nodeType]?.lat}
-                options={[...spatialAttributes[nodeType]]}
-                disabled={!settings.node || spatialAttributes[nodeType].length < 1}
-                onChange={(val) => updateSettings({ [nodeType]: { ...settings?.[nodeType], lat: val as string } })}
-              />
-              <Input
-                inline
-                label="Longitude"
-                type="dropdown"
-                value={settings?.[nodeType]?.lon}
-                options={[...spatialAttributes[nodeType]]}
-                disabled={!settings.node || spatialAttributes[nodeType].length < 1}
-                onChange={(val) => updateSettings({ [nodeType]: { ...settings?.[nodeType], lon: val as string } })}
-              />
+          );
+        })}
+
+        {graphMetadata.edges.labels.map((edgeType) => {
+          const edgeSettings = layerSettings?.edges?.[edgeType] || {};
+
+          return (
+            <div key={edgeType} className="mt-2">
+              <div className="flex items-center">
+                <Button
+                  size="2xs"
+                  iconComponent={edgeSettings?.collapsed ? 'icon-[ic--baseline-arrow-right]' : 'icon-[ic--baseline-arrow-drop-down]'}
+                  variant="ghost"
+                  onClick={() => handleCollapseToggle(edgeType, 'edges')}
+                />
+                <div className="w-3/4 mr-6 cursor-pointer" onClick={() => handleCollapseToggle(edgeType, 'edges')}>
+                  <RelationPill title={edgeType} />
+                </div>
+              </div>
+
+              {!edgeSettings.collapsed && (
+                <div>
+                  <div className="flex justify-between">
+                    <span className="font-bold">Color</span>
+                    <ColorPicker
+                      value={edgeSettings.color}
+                      updateValue={(val) =>
+                        updateLayerSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, color: val } } })
+                      }
+                    />
+                  </div>
+
+                  <Input
+                    label="Edges on hover"
+                    type="boolean"
+                    value={layerSettings?.enableBrushing}
+                    onChange={(val) => {
+                      updateLayerSettings({ enableBrushing: val as boolean });
+                    }}
+                  />
+                </div>
+              )}
             </div>
-          )}
-        </div>
-      ))}
-    </div>
+          );
+        })}
+      </div>
+    )
   );
 }
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/choropleth-layer/newChoroplethLayer.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/choropleth-layer/newChoroplethLayer.tsx
deleted file mode 100644
index 3abe7302831c29871d64773c39d943d9a5dc6f4d..0000000000000000000000000000000000000000
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/choropleth-layer/newChoroplethLayer.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import React from 'react';
-import { CompositeLayer } from 'deck.gl';
-import { ArcLayer, GeoJsonLayer, ScatterplotLayer } from '@deck.gl/layers';
-import { netherlands } from '../../../../../mock-data/geo-json';
-import { Edge, Node, LayerProps } from '../../mapvis.types';
-import { RGBColor, color, geoBounds, geoCentroid, geoContains, interpolateYlOrRd, scaleSequential } from 'd3';
-
-export class ChoroplethLayer extends CompositeLayer<LayerProps> {
-  static type = 'Choropleth';
-
-  shouldUpdateState({ props, oldProps, context, changeFlags }: { props: any; oldProps: any; context: any; changeFlags: any }) {
-    return changeFlags.dataChanged || changeFlags.propsChanged;
-  }
-
-  updateState({ props, oldProps, context, changeFlags }: { props: any; oldProps: any; context: any; changeFlags: any }) {
-    if (changeFlags.dataChanged) {
-      const nodes = props.graph.nodes;
-      const geojson = netherlands;
-      const data = this.concatData(geojson, nodes);
-      this.setState({ data });
-    }
-  }
-
-  concatData(geojson: any, nodes: Node[]) {
-    const updatedGeojson = { ...geojson };
-
-    nodes.map((node: Node) => {
-      const coordinates: [number, number] = this.props.getNodeLocation(node.id);
-
-      if (coordinates) {
-        updatedGeojson.features.map((feature: any) => {
-          const isInside = geoContains(feature, coordinates);
-          if (isInside) {
-            feature.properties.nodes = feature.properties.nodes ?? [];
-            feature.properties.nodes.push(node.id);
-
-            feature.properties.townships.map((township: any) => {
-              const isInside = geoContains(township, coordinates);
-
-              if (isInside) {
-                township.properties.nodes = township.properties.nodes ?? [];
-                township.properties.nodes.push(node.id);
-              }
-            });
-            return;
-          }
-        });
-      }
-    });
-
-    return updatedGeojson;
-  }
-
-  getColor(polygon: any, min: number, max: number) {
-    const nodeLength = polygon.properties?.nodes?.length ?? 0;
-
-    if (nodeLength === min) return [128, 128, 128];
-
-    const colorScale = scaleSequential(interpolateYlOrRd).domain([min, max]);
-    const _color = color(colorScale(nodeLength)) as RGBColor;
-
-    return _color ? [_color.r, _color.g, _color.b] : [100, 0, 0];
-  }
-
-  renderLayers() {
-    const { data } = this.state;
-
-    const layers: any = [];
-
-    let max = 0;
-
-    (data as any).features.forEach((feature: any) => {
-      const nodeCount = feature.properties?.nodes ? feature.properties.nodes.length : 0;
-      if (nodeCount > max) max = nodeCount;
-    });
-
-    (data as any).features.forEach((feature: any) => {
-      layers.push(
-        new GeoJsonLayer(
-          this.getSubLayerProps({
-            id: `feature-layer-${feature.properties.name}`,
-            data: feature,
-            opacity: 0.3,
-            pickable: true,
-            stroked: true,
-            filled: true,
-            extruded: false,
-            lineWidthScale: 0.5,
-            lineWidthMinPixels: 1,
-            getLineWidth: (d: any) => 1,
-            getLineColor: (d: any) => [220, 220, 220],
-            getFillColor: (d: any) => this.getColor(d, 0, max),
-          }),
-        ),
-      );
-    });
-
-    return [...layers];
-  }
-}
-
-ChoroplethLayer.layerName = 'Choropleth';
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 4453d6dbc22f71f29964e37a2822ecc01836044c..52395513b83a59ebd11cd2ef183643fcf580345b 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,12 +1,17 @@
 import React from 'react';
-import { CompositeLayer, HeatmapLayer } from 'deck.gl';
+import { CompositeLayer, HeatmapLayer, Layer } from 'deck.gl';
 import * as d3 from 'd3';
 import { getDistance } from '../../utlis';
 import { CompositeLayerType, Edge, LayerProps } from '../../mapvis.types';
 import { Node } from '@graphpolaris/shared/lib/data-access';
 
 export class HeatLayer extends CompositeLayer<CompositeLayerType> {
-  static type = 'Heatmap';
+  static type = 'heatmap';
+  private _layers: Record<string, Layer> = {};
+
+  constructor(props: LayerProps) {
+    super(props);
+  }
 
   updateState({ changeFlags }: { changeFlags: any }) {
     return changeFlags.propsOrDataChanged || changeFlags.somethingChanged;
@@ -44,32 +49,29 @@ export class HeatLayer extends CompositeLayer<CompositeLayerType> {
   }
 
   renderLayers() {
-    const { data, settings, getNodeLocation, selected, setLayerIds, graphMetadata } = this.props;
+    const { data, settings, getNodeLocation, setLayerIds, graphMetadata } = this.props;
+    const layerSettings = settings[HeatLayer.type];
 
-    const layers: any[] = [];
     const layerIds: string[] = [];
 
     graphMetadata.nodes.labels.forEach((label: string) => {
       const layerId = `${label}-nodes-heatmaplayer`;
       layerIds.push(layerId);
 
-      console.log(settings.nodes[label].size);
-      layers.push(
-        new HeatmapLayer<Node>({
-          id: layerId,
-          data: data.nodes.filter((node: Node) => node.label === label),
-          visible: !settings.nodes[label].hidden,
-          getPosition: (d: any) => getNodeLocation(d.id),
-          getWeight: (d: any) => settings.nodes[label].size,
-          radiusPixels: settings.nodes[label].size,
-          aggregation: 'SUM',
-        }),
-      );
+      this._layers[layerId] = new HeatmapLayer<Node>({
+        id: layerId,
+        data: data.nodes.filter((node: Node) => node.label === label),
+        visible: !layerSettings.nodes[label].hidden,
+        getPosition: (d: any) => getNodeLocation(d.id),
+        getWeight: (d: any) => layerSettings.nodes[label].size,
+        radiusPixels: layerSettings.nodes[label].size,
+        aggregation: 'SUM',
+      });
     });
 
     setLayerIds(layerIds);
 
-    return layers;
+    return Object.values(this._layers);
   }
 }
 
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/heatmap-layer/HeatLayerOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/heatmap-layer/HeatLayerOptions.tsx
index 8b0ce65c20586fd5e727f00c19d474182b43cd84..fcd791172d41b3320150ac5aacfe09942b52d3e6 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/heatmap-layer/HeatLayerOptions.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/heatmap-layer/HeatLayerOptions.tsx
@@ -1,60 +1,104 @@
-import React, { useState, useMemo, useEffect } from 'react';
-import { VisualizationSettingsPropTypes } from '@graphpolaris/shared/lib/vis/common';
+import React, { useEffect } from 'react';
 import { MapProps } from '../../mapvis';
 import { Button, EntityPill, Input } from '@graphpolaris/shared/lib/components';
-import { MapLayerSettingsPropTypes } from '..';
+import { LayerSettingsComponentType } from '../../mapvis.types';
 
-export function HeatLayerOptions({ settings, graphMetadata, updateSettings, spatialAttributes }: MapLayerSettingsPropTypes) {
-  const handleCollapseToggle = (nodeType: string) => {
-    settings.nodes[nodeType].collapsed = !settings.nodes[nodeType].collapsed;
-    updateSettings({ nodes: settings.nodes });
+export function HeatLayerOptions({
+  settings,
+  graphMetadata,
+  updateLayerSettings,
+  spatialAttributes,
+  updateSpatialAttribute,
+}: LayerSettingsComponentType<MapProps>) {
+  const layerType = 'heatmap';
+  const layerSettings = settings[layerType];
+
+  useEffect(() => {
+    if (!layerSettings) {
+      const initialSettingsObject = { nodes: {}, edges: {} };
+
+      graphMetadata.nodes.labels.forEach((node) => {
+        initialSettingsObject.nodes = {
+          ...initialSettingsObject.nodes,
+          [node]: {
+            size: 10,
+            hidden: false,
+          },
+        };
+      });
+
+      graphMetadata.edges.labels.forEach((edge) => {
+        initialSettingsObject.edges = {
+          ...initialSettingsObject.edges,
+          [edge]: {},
+        };
+      });
+
+      updateLayerSettings({ ...initialSettingsObject });
+    }
+  }, [graphMetadata, layerType, settings, updateLayerSettings]);
+
+  const handleCollapseToggle = (type: string, itemType: 'nodes' | 'edges') => {
+    if (layerSettings) {
+      updateLayerSettings({
+        [itemType]: {
+          ...layerSettings[itemType],
+          [type]: {
+            ...layerSettings[itemType][type],
+            collapsed: !layerSettings[itemType][type]?.collapsed ?? true,
+          },
+        },
+      });
+    }
   };
 
   return (
-    <div>
-      {settings?.nodes &&
-        Object.keys(settings.nodes).map((nodeType) => {
-          const nodeSettings = settings.nodes[nodeType];
+    layerSettings && (
+      <div>
+        {graphMetadata.nodes.labels.map((nodeType) => {
+          const nodeSettings = layerSettings?.nodes?.[nodeType] || {};
+
           return (
             <div className="mt-2" key={nodeType}>
               <div className="flex items-center">
-                <div className="w-3/4 mr-6 cursor-pointer" onClick={() => handleCollapseToggle(nodeType)}>
+                <Button
+                  size="2xs"
+                  iconComponent={nodeSettings.collapsed ? 'icon-[ic--baseline-arrow-right]' : 'icon-[ic--baseline-arrow-drop-down]'}
+                  variant="ghost"
+                  onClick={() => handleCollapseToggle(nodeType, 'nodes')}
+                />
+                <div className="w-3/4 mr-6 cursor-pointer" onClick={() => handleCollapseToggle(nodeType, 'nodes')}>
                   <EntityPill title={nodeType} />
                 </div>
-                <div className="w-1/2">
-                  <Button
-                    iconComponent={nodeSettings.hidden ? 'icon-[ic--baseline-visibility-off]' : 'icon-[ic--baseline-visibility]'}
-                    variant="ghost"
-                    onClick={() =>
-                      updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, hidden: !nodeSettings.hidden } } })
-                    }
-                  />
-                </div>
               </div>
 
               {!nodeSettings.collapsed && (
                 <div>
+                  <Input
+                    label="Hidden"
+                    type="boolean"
+                    value={nodeSettings.hidden ?? false}
+                    onChange={(val) => {
+                      updateLayerSettings({ nodes: { ...layerSettings.nodes, [nodeType]: { ...nodeSettings, hidden: val } } });
+                    }}
+                  />
                   <Input
                     inline
                     label="Latitude"
                     type="dropdown"
-                    value={nodeSettings.lat}
-                    options={[...(spatialAttributes[nodeType] || [])]}
-                    disabled={!settings.node || spatialAttributes[nodeType].length < 1}
-                    onChange={(val) => {
-                      updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, lat: String(val) } } });
-                    }}
+                    value={settings?.location[nodeType].lat}
+                    options={[...spatialAttributes[nodeType]]}
+                    disabled={spatialAttributes[nodeType].length < 1}
+                    onChange={(val) => updateSpatialAttribute(nodeType, 'lat', val as string)}
                   />
                   <Input
                     inline
                     label="Longitude"
                     type="dropdown"
-                    value={nodeSettings.lon}
-                    options={[...(spatialAttributes[nodeType] || [])]}
-                    disabled={!settings.node || spatialAttributes[nodeType].length < 1}
-                    onChange={(val) => {
-                      updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, lon: String(val) } } });
-                    }}
+                    value={settings?.location[nodeType].lon}
+                    options={[...spatialAttributes[nodeType]]}
+                    disabled={spatialAttributes[nodeType].length < 1}
+                    onChange={(val) => updateSpatialAttribute(nodeType, 'lon', val as string)}
                   />
                   <Input
                     label="Size"
@@ -64,7 +108,7 @@ export function HeatLayerOptions({ settings, graphMetadata, updateSettings, spat
                     step={1}
                     value={nodeSettings.size}
                     onChange={(val) => {
-                      updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, size: Number(val) } } });
+                      updateLayerSettings({ nodes: { ...layerSettings.nodes, [nodeType]: { ...nodeSettings, size: Number(val) } } });
                     }}
                   />
                 </div>
@@ -72,6 +116,7 @@ export function HeatLayerOptions({ settings, graphMetadata, updateSettings, spat
             </div>
           );
         })}
-    </div>
+      </div>
+    )
   );
 }
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/icon-layer/IconLayer.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/icon-layer/IconLayer.tsx
deleted file mode 100644
index e4a3e1e09765baa15391b61469ae35b5d1f378ab..0000000000000000000000000000000000000000
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/icon-layer/IconLayer.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import React from 'react';
-import { CompositeLayer } from 'deck.gl';
-import { IconLayer } from '@deck.gl/layers';
-import { CompositeLayerType, LayerProps } from '../../mapvis.types';
-import { Node } from '@graphpolaris/shared/lib/data-access';
-
-const ICON_MAPPING = {
-  marker: { x: 0, y: 0, width: 128, height: 128, mask: false },
-};
-
-export class NodeIconLayer extends CompositeLayer<CompositeLayerType> {
-  static type = 'Icon';
-
-  updateState({ changeFlags }: { changeFlags: any }) {
-    return changeFlags.propsOrDataChanged || changeFlags.somethingChanged;
-  }
-
-  renderLayers() {
-    const { data, settings, getNodeLocation, setLayerIds, graphMetadata } = this.props;
-
-    const layers: any[] = [];
-    const layerIds: string[] = [];
-
-    graphMetadata.nodes.labels.forEach((label: string) => {
-      const layerId = `${label}-nodes-iconlayer`;
-      layerIds.push(layerId);
-
-      layers.push(
-        new IconLayer({
-          id: layerId,
-          data: data.nodes.filter((node: Node) => node.label === label),
-          visible: !settings.nodes[label].hidden,
-          iconAtlas: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/icon-atlas.png',
-          iconMapping: ICON_MAPPING,
-          sizeScale: 10,
-          pickable: true,
-          getIcon: (d: any) => 'marker',
-          getColor: (d: any) => settings.nodes[label].color,
-          getPosition: (d: any) => getNodeLocation(d._id),
-          getSize: (d: any) => 3,
-        }),
-      );
-    });
-
-    setLayerIds(layerIds);
-
-    return layers;
-  }
-}
-
-NodeIconLayer.layerName = 'Icon';
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/icon-layer/IconOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/icon-layer/IconOptions.tsx
deleted file mode 100644
index 3643d74b3a80b36e6ec38683bcfd45c35f014c29..0000000000000000000000000000000000000000
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/icon-layer/IconOptions.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import React, { useState, useMemo, useEffect } from 'react';
-import ColorPicker from '@graphpolaris/shared/lib/components/colorComponents/colorPicker';
-import { VisualizationSettingsPropTypes } from '@graphpolaris/shared/lib/vis/common';
-import { MapProps } from '../../mapvis';
-import { EntityPill, Icon, Input, RelationPill } from '@graphpolaris/shared/lib/components';
-
-export function IconOptions({ settings, graphMetadata, updateSettings }: VisualizationSettingsPropTypes<MapProps>) {
-  const [collapsed, setCollapsed] = useState<Record<string, boolean>>({});
-
-  useEffect(() => {
-    graphMetadata.nodes.labels.forEach((node) => {
-      updateSettings({
-        [node]: {
-          color: [0, 0, 0],
-          hidden: false,
-          fixed: true,
-          min: 0,
-          max: 10,
-          sizeAttribute: '',
-          lon: '',
-          lat: '',
-          ...settings?.[node],
-        },
-      });
-    });
-  }, [graphMetadata]);
-
-  const spatialAttributes: { [id: string]: string[] } = {};
-  graphMetadata.nodes.labels.forEach((node) => {
-    spatialAttributes[node] = Object.entries(graphMetadata.nodes.types[node].attributes)
-      .filter(([, value]) => value.dimension === 'numerical')
-      .map(([key]) => key);
-  });
-
-  const handleCollapseToggle = (nodeType: string) => {
-    setCollapsed((prevCollapsed) => ({
-      ...prevCollapsed,
-      [nodeType]: !prevCollapsed[nodeType],
-    }));
-  };
-
-  return (
-    <div>
-      {graphMetadata.nodes.labels.map((nodeType) => (
-        <div className="mt-2" key={nodeType}>
-          <div className="flex items-center">
-            <div className="w-3/4 mr-6 cursor-pointer" onClick={() => handleCollapseToggle(nodeType)}>
-              <EntityPill title={nodeType} />
-            </div>
-            <div className="w-1/2">
-              <ColorPicker
-                value={settings?.[nodeType]?.['color'] ? settings?.[nodeType]?.['color'] : [0, 0, 0]}
-                updateValue={(val: number[]) => updateSettings({ [nodeType]: { ...settings?.[nodeType], color: val } })}
-              />
-            </div>
-          </div>
-
-          {!collapsed[nodeType] && (
-            <div>
-              <Input
-                inline
-                label="Latitude"
-                type="dropdown"
-                value={settings?.[nodeType]?.lat}
-                options={[...spatialAttributes[nodeType]]}
-                disabled={!settings.node || spatialAttributes[nodeType].length < 1}
-                onChange={(val) => updateSettings({ [nodeType]: { ...settings?.[nodeType], lat: val as string } })}
-              />
-              <Input
-                inline
-                label="Longitude"
-                type="dropdown"
-                value={settings?.[nodeType]?.lon}
-                options={[...spatialAttributes[nodeType]]}
-                disabled={!settings.node || spatialAttributes[nodeType].length < 1}
-                onChange={(val) => updateSettings({ [nodeType]: { ...settings?.[nodeType], lon: val as string } })}
-              />
-
-              <div className="flex items-center gap-1">
-                <Icon component="icon-[ic--baseline-subdirectory-arrow-right]" size={16} color="text-secondary-300" />
-                <Input
-                  label="Hidden"
-                  type="boolean"
-                  value={settings?.[nodeType]?.hidden ?? false}
-                  onChange={(val: boolean) => updateSettings({ [nodeType]: { ...settings?.[nodeType], hidden: val } })}
-                />
-              </div>
-            </div>
-          )}
-        </div>
-      ))}
-    </div>
-  );
-}
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/index.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/index.tsx
index cd7d9fe0d03d3bc2a24fbf2fc505d796cd5d0f7e..9e5f7c31e76c2f3752733dd3c4c0f33ed2462500 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/index.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/layers/index.tsx
@@ -1,34 +1,29 @@
-import { ChoroplethLayer } from './choropleth-layer/newChoroplethLayer';
+import { ChoroplethLayer } from './choropleth-layer/ChoroplethLayer';
 import { HeatLayer } from './heatmap-layer/HeatLayer';
 import { NodeLinkLayer } from './nodelink-layer/NodeLinkLayer';
-import { NodeLayer } from './node-layer/NodeLayer';
-import { NodeIconLayer } from './icon-layer/IconLayer';
 import { NodeLinkOptions } from './nodelink-layer/NodeLinkOptions';
-import { IconOptions } from './icon-layer/IconOptions';
 import { HeatLayerOptions } from './heatmap-layer/HeatLayerOptions';
 import { ChoroplethOptions } from './choropleth-layer/ChoroplethOptions';
 import { TileLayer, BitmapLayer } from 'deck.gl';
-import { VisualizationSettingsPropTypes } from '../../../common';
 import { MapProps } from '../mapvis';
+import { LayerSettingsComponentType } from '../mapvis.types';
 
-export const layerTypes: Record<string, any> = {
-  // node: NodeLayer,
-  // icon: NodeIconLayer,
+export type LayerTypes = 'nodelink' | 'heatmap' | 'choropleth';
+
+export const layerTypes: Record<LayerTypes, any> = {
   nodelink: NodeLinkLayer,
   heatmap: HeatLayer,
-  // choropleth: ChoroplethLayer,
+  choropleth: ChoroplethLayer,
 };
 
-export type MapLayerSettingsPropTypes = VisualizationSettingsPropTypes<MapProps> & {
+export type MapLayerSettingsPropTypes = LayerSettingsComponentType<MapProps> & {
   spatialAttributes: { [id: string]: string[] };
 };
 
 export const layerSettings: Record<string, React.FC<MapLayerSettingsPropTypes>> = {
   nodelink: NodeLinkOptions,
   heatmap: HeatLayerOptions,
-  // node: NodeOptions,
-  // icon: IconOptions,
-  // choropleth: ChoroplethOptions,
+  choropleth: ChoroplethOptions,
 };
 
 const MAP_PROVIDER = [
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/node-layer/NodeLayer.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/node-layer/NodeLayer.tsx
deleted file mode 100644
index 82f4a3a47885bf86a2755d5a466cecc483fe72fb..0000000000000000000000000000000000000000
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/node-layer/NodeLayer.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import React from 'react';
-import { CompositeLayer } from 'deck.gl';
-import { ScatterplotLayer } from '@deck.gl/layers';
-import { CompositeLayerType, LayerProps } from '../../mapvis.types';
-import { Node } from '@graphpolaris/shared/lib/data-access';
-
-export class NodeLayer extends CompositeLayer<CompositeLayerType> {
-  static type = 'Node';
-
-  updateState({ changeFlags }: { changeFlags: any }) {
-    return changeFlags.propsOrDataChanged || changeFlags.somethingChanged;
-  }
-
-  getRadius(node: Node, config: any) {
-    if (config[node.label]?.fixed) return config[node.label].radius;
-    const sizeAttribute = config[node.label]?.sizeAttribute;
-    if (sizeAttribute) {
-      const minValue = config[node.label]?.min ?? 0;
-      const maxValue = config[node.label]?.max ?? 10;
-      const attributeValue = parseFloat(node.attributes[sizeAttribute] as string);
-
-      if (!isNaN(attributeValue)) {
-        const normalizedValue = (attributeValue - minValue) / (maxValue - minValue);
-        return Math.max(1, normalizedValue * 10);
-      }
-    }
-    return config[node.label]?.radius ?? 5;
-  }
-
-  renderLayers() {
-    const { data, settings, getNodeLocation, selected, setLayerIds, graphMetadata } = this.props;
-
-    const layers: any[] = [];
-    const layerIds: any[] = [];
-
-    graphMetadata.nodes.labels.forEach((label: string) => {
-      const layerId = `${label}-nodes-scatterplot`;
-      layerIds.push(layerId);
-
-      layers.push(
-        new ScatterplotLayer({
-          id: layerId,
-          visible: !settings.nodes[label].hidden,
-          data: data.nodes.filter((node: Node) => node.label === label),
-          pickable: true,
-          filled: true,
-          radiusScale: 6,
-          radiusMinPixels: 7,
-          radiusMaxPixels: 100,
-          lineWidthMinPixels: 1,
-          getPosition: (d: any) => getNodeLocation(d._id),
-          getFillColor: (d: any) => settings.nodes[label].color,
-          getRadius: (d: any) => settings.nodes[label].radius,
-        }),
-      );
-    });
-
-    setLayerIds(layerIds);
-
-    return layers;
-  }
-}
diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/node-layer/NodeOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/node-layer/NodeOptions.tsx
deleted file mode 100644
index 2210c29f65f41b1852a8d1c5ccf54e5c35c1cfd1..0000000000000000000000000000000000000000
--- a/libs/shared/lib/vis/visualizations/mapvis/layers/node-layer/NodeOptions.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import React, { useEffect, useMemo, useState } from 'react';
-import ColorPicker from '@graphpolaris/shared/lib/components/colorComponents/colorPicker';
-import { MapNodeOrEdgeData, MapProps } from '../../mapvis';
-import { VisualizationSettingsPropTypes } from '@graphpolaris/shared/lib/vis/common';
-import { EntityPill, Icon, Input } from '@graphpolaris/shared/lib/components';
-import { MapLayerSettingsPropTypes } from '..';
-
-export default function NodeOptions({ settings, graphMetadata, updateSettings, spatialAttributes }: MapLayerSettingsPropTypes) {
-  const handleCollapseToggle = (nodeType: string) => {
-    settings.nodes[nodeType].collapsed = !settings.nodes[nodeType].collapsed;
-    updateSettings({ nodes: settings.nodes });
-  };
-
-  return (
-    <div>
-      {settings?.nodes &&
-        Object.keys(settings.nodes).map((nodeType) => {
-          const nodeSettings = settings.nodes[nodeType];
-          return (
-            <div className="mt-2" key={nodeType}>
-              <div className="flex items-center">
-                <div className="w-3/4 mr-6 cursor-pointer" onClick={() => handleCollapseToggle(nodeType)}>
-                  <EntityPill title={nodeType} />
-                </div>
-                <div className="w-1/2">
-                  <ColorPicker
-                    value={nodeSettings.color}
-                    updateValue={(val) => {
-                      updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, color: val } } });
-                    }}
-                  />
-                </div>
-              </div>
-
-              {!nodeSettings.collapsed && (
-                <div>
-                  <Input
-                    inline
-                    label="Latitude"
-                    type="dropdown"
-                    value={nodeSettings.lat}
-                    options={[...(spatialAttributes[nodeType] || [])]}
-                    disabled={!settings.node || spatialAttributes[nodeType].length < 1}
-                    onChange={(val) => {
-                      updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, lat: String(val) } } });
-                    }}
-                  />
-                  <Input
-                    inline
-                    label="Longitude"
-                    type="dropdown"
-                    value={nodeSettings.lon}
-                    options={[...(spatialAttributes[nodeType] || [])]}
-                    disabled={!settings.node || spatialAttributes[nodeType].length < 1}
-                    onChange={(val) => {
-                      updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, lon: String(val) } } });
-                    }}
-                  />
-
-                  <div className="ml-2">
-                    <div className="flex items-center gap-1">
-                      <Icon component="icon-[ic--baseline-subdirectory-arrow-right]" size={16} color="text-secondary-300" />
-
-                      <Input
-                        label="Hidden"
-                        type="boolean"
-                        value={nodeSettings.hidden}
-                        onChange={(val) => {
-                          updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, hidden: val } } });
-                        }}
-                      />
-                    </div>
-
-                    <div>
-                      <div className="flex items-center gap-1">
-                        <Icon component="icon-[ic--baseline-subdirectory-arrow-right]" size={16} color="text-secondary-300" />
-                        <span>Radius</span>
-                      </div>
-                      <Input
-                        label="Fixed"
-                        type="boolean"
-                        value={nodeSettings.fixed}
-                        onChange={(val) => {
-                          updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, fixed: val } } });
-                        }}
-                      />
-                      {!settings?.[nodeType]?.fixed ? (
-                        <div>
-                          <Input
-                            label="Based on"
-                            type="dropdown"
-                            size="xs"
-                            options={spatialAttributes[nodeType]}
-                            value={nodeSettings.sizeAttribute}
-                            onChange={(val) => {
-                              updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, sizeAttribute: String(val) } } });
-                            }}
-                          />
-                          <div className="flex">
-                            <Input
-                              type="number"
-                              label="min"
-                              size="xs"
-                              value={nodeSettings.min}
-                              onChange={(val) => {
-                                updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, min: Number(val) } } });
-                              }}
-                            />
-                            <Input
-                              type="number"
-                              label="max"
-                              size="xs"
-                              value={nodeSettings.max}
-                              onChange={(val) => {
-                                updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, max: Number(val) } } });
-                              }}
-                            />
-                          </div>
-                        </div>
-                      ) : (
-                        <div>
-                          <Input
-                            type="slider"
-                            label="Width"
-                            min={0}
-                            max={10}
-                            step={0.5}
-                            value={nodeSettings.radius}
-                            onChange={(val) => {
-                              updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, radius: Number(val) } } });
-                            }}
-                          />
-                        </div>
-                      )}
-                    </div>
-                  </div>
-                </div>
-              )}
-            </div>
-          );
-        })}
-    </div>
-  );
-}
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 56004039f0a519784302e744f9294cf398faf5d3..b9fddcfca0c689c3420884e476e994f28b02def6 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,13 +1,13 @@
 import React from 'react';
 import { CompositeLayer, Layer } from 'deck.gl';
-import { IconLayer, LineLayer, ScatterplotLayer, TextLayer } from '@deck.gl/layers';
+import { IconLayer, LineLayer, TextLayer } from '@deck.gl/layers';
 import { CompositeLayerType, LayerProps } from '../../mapvis.types';
 import { BrushingExtension, CollisionFilterExtension } from '@deck.gl/extensions';
-import { Edge, Node } from '@graphpolaris/shared/lib/data-access';
+import { Node } from '@graphpolaris/shared/lib/data-access';
 import { createIcon } from './shapeFactory';
 
 export class NodeLinkLayer extends CompositeLayer<CompositeLayerType> {
-  static type = 'NodeLink';
+  static type = 'nodelink';
   private _layers: Record<string, Layer> = {};
 
   constructor(props: LayerProps) {
@@ -19,42 +19,56 @@ export class NodeLinkLayer extends CompositeLayer<CompositeLayerType> {
   }
 
   renderLayers() {
-    const { data, settings, getNodeLocation, selected, setLayerIds, graphMetadata } = this.props;
+    const { data, settings, getNodeLocation, ml, graphMetadata } = this.props;
+    const layerSettings = settings[NodeLinkLayer.type];
 
     const brushingExtension = new BrushingExtension();
     const collisionFilter = new CollisionFilterExtension();
 
     graphMetadata.edges.labels.forEach((label: string) => {
       const layerId = `${label}-edges-line`;
-      const edgeData =
-        selected.length > 0 ? data.edges.filter((edge: Edge) => selected.includes(edge.from) || selected.includes(edge.to)) : data.edges;
+      const edgeData = data.edges;
 
-      this._layers[layerId] = new LineLayer<Edge>({
+      this._layers[layerId] = new LineLayer({
         id: layerId,
         data: edgeData,
-        visible: !settings.edges[label].hidden,
+        visible: !layerSettings.edges[label].hidden,
         pickable: true,
-        getWidth: settings.edges[label].width,
+        getWidth: layerSettings.edges[label].width,
         getSourcePosition: (d) => getNodeLocation(d.from),
         getTargetPosition: (d) => getNodeLocation(d.to),
-        getColor: (d) => settings.edges[d.label].color,
+        getColor: (d) => layerSettings.edges[d.label].color,
         extensions: [brushingExtension],
+        brushingEnabled: layerSettings.enableBrushing,
       });
     });
+
+    if (ml.linkPrediction.enabled) {
+      this._layers['link_prediction'] = new LineLayer({
+        id: 'link-prediction-layer',
+        data: ml.linkPrediction.result,
+        pickable: false,
+        getWidth: 1,
+        getSourcePosition: (d) => getNodeLocation(d.from),
+        getTargetPosition: (d) => getNodeLocation(d.to),
+        getColor: (d) => [0, 0, 0],
+      });
+    }
+
     graphMetadata.nodes.labels.forEach((label: string) => {
       const layerId = `${label}-nodes-scatterplot`;
 
-      this._layers[layerId] = new IconLayer<Node>({
+      this._layers[layerId] = new IconLayer({
         id: layerId,
-        visible: !settings.nodes[label].hidden,
+        visible: !layerSettings.nodes[label].hidden,
         data: data.nodes.filter((node: Node) => node.label === label),
         pickable: true,
         getColor: (d) => [200, 140, 0],
-        getSize: (d) => settings.nodes[label].size,
+        getSize: (d) => layerSettings.nodes[label].size,
         getPosition: (d) => getNodeLocation(d._id),
         getIcon: (d: any) => {
           return {
-            url: createIcon(settings.nodes[label].shape, settings.nodes[label].color),
+            url: createIcon(layerSettings.nodes[label].shape, layerSettings.nodes[label].color),
             width: 24,
             height: 24,
           };
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 6c530cf3741393dae5f5981f9606d73cbf492ab5..306774b916bb3d26875f68e5f42164826024ee72 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
@@ -1,136 +1,262 @@
-import React, { useState, useEffect } from 'react';
+import React, { useEffect } from 'react';
 import ColorPicker from '@graphpolaris/shared/lib/components/colorComponents/colorPicker';
-import { Button, EntityPill, Icon, Input, RelationPill } from '@graphpolaris/shared/lib/components';
-import { MapLayerSettingsPropTypes } from '..';
+import { Button, DropdownColorLegend, EntityPill, Icon, Input, RelationPill } from '@graphpolaris/shared/lib/components';
+import { MapProps } from '../../mapvis';
+import { LayerSettingsComponentType } from '../../mapvis.types';
 
-export function NodeLinkOptions({ settings, graphMetadata, updateSettings, spatialAttributes }: MapLayerSettingsPropTypes) {
-  const handleCollapseToggle = (nodeType: string) => {
-    settings.nodes[nodeType].collapsed = !settings.nodes[nodeType].collapsed;
-    updateSettings({ nodes: settings.nodes });
-  };
+export function NodeLinkOptions({
+  settings,
+  graphMetadata,
+  updateLayerSettings,
+  spatialAttributes,
+  updateSpatialAttribute,
+}: LayerSettingsComponentType<MapProps>) {
+  const layerType = 'nodelink';
+  const layerSettings = settings[layerType];
 
   useEffect(() => {
-    graphMetadata.nodes.labels.map((nodeType) => {
-      if (settings?.[nodeType]?.lat) {
-      }
-    });
-  }, [settings.node, graphMetadata]);
+    if (!layerSettings) {
+      const initialSettingsObject = { enableBrushing: false, nodes: {}, edges: {} };
+
+      graphMetadata.nodes.labels.forEach((node) => {
+        initialSettingsObject.nodes = {
+          ...initialSettingsObject.nodes,
+          [node]: {
+            colorByAttribute: false,
+            colorAttribute: undefined,
+            colorAttributeType: undefined,
+            hidden: false,
+            shape: 'circle',
+            color: [Math.floor(Math.random() * 251), Math.floor(Math.random() * 251), 0],
+            size: 10,
+          },
+        };
+      });
+
+      graphMetadata.edges.labels.forEach((edge) => {
+        initialSettingsObject.edges = {
+          ...initialSettingsObject.edges,
+          [edge]: {
+            hidden: false,
+            width: 1,
+            sizeAttribute: '',
+            fixed: true,
+            color: [0, 0, 0],
+          },
+        };
+      });
+
+      updateLayerSettings({ ...initialSettingsObject });
+    }
+  }, [graphMetadata, settings, updateLayerSettings]);
+
+  const handleCollapseToggle = (type: string, itemType: 'nodes' | 'edges') => {
+    if (layerSettings) {
+      updateLayerSettings({
+        [itemType]: {
+          ...layerSettings[itemType],
+          [type]: {
+            ...layerSettings[itemType][type],
+            collapsed: !layerSettings[itemType][type]?.collapsed ?? true,
+          },
+        },
+      });
+    }
+  };
 
   return (
-    <div>
-      {settings?.nodes &&
-        Object.keys(settings.nodes).map((nodeType) => {
-          const nodeSettings = settings.nodes[nodeType];
+    layerSettings && (
+      <div>
+        {graphMetadata.nodes.labels.map((nodeType) => {
+          const nodeSettings = layerSettings?.nodes?.[nodeType] || {};
+
           return (
             <div className="mt-2" key={nodeType}>
               <div className="flex items-center">
-                <div className="flex flex-grow mr-2 cursor-pointer" onClick={() => handleCollapseToggle(nodeType)}>
+                <Button
+                  size="2xs"
+                  iconComponent={nodeSettings.collapsed ? 'icon-[ic--baseline-arrow-right]' : 'icon-[ic--baseline-arrow-drop-down]'}
+                  variant="ghost"
+                  onClick={() => handleCollapseToggle(nodeType, 'nodes')}
+                />
+                <div className="flex flex-grow mr-2 cursor-pointer" onClick={() => handleCollapseToggle(nodeType, 'nodes')}>
                   <EntityPill title={nodeType} />
                 </div>
-                <div className="flex items-center space-x-2">
-                  <ColorPicker
-                    value={nodeSettings.color}
-                    updateValue={(val) => {
-                      updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, color: val } } });
-                    }}
-                  />
-                  <Button
-                    iconComponent={nodeSettings.hidden ? 'icon-[ic--baseline-visibility-off]' : 'icon-[ic--baseline-visibility]'}
-                    variant="ghost"
-                    onClick={() => {
-                      updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, hidden: !nodeSettings.hidden } } });
-                    }}
-                  />
-                </div>
               </div>
 
               {!nodeSettings.collapsed && (
                 <div>
                   <Input
-                    inline
-                    label="Latitude"
-                    type="dropdown"
-                    value={nodeSettings.lat}
-                    options={[...(spatialAttributes[nodeType] || [])]}
-                    disabled={!settings.node || spatialAttributes[nodeType].length < 1}
-                    onChange={(val) => {
-                      updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, lat: String(val) } } });
-                    }}
-                  />
-                  <Input
-                    inline
-                    label="Longitude"
-                    type="dropdown"
-                    value={nodeSettings.lon}
-                    options={[...(spatialAttributes[nodeType] || [])]}
-                    disabled={!settings.node || spatialAttributes[nodeType].length < 1}
-                    onChange={(val) => {
-                      updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, lon: String(val) } } });
-                    }}
-                  />
-                  <Input
-                    inline
-                    label="Shape"
-                    type="dropdown"
-                    value={nodeSettings.shape}
-                    options={['circle', 'square', 'triangle', 'diamond', 'location', 'star']}
-                    disabled={!settings.shape}
-                    onChange={(val) => {
-                      updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, shape: String(val) as any } } });
-                    }}
-                  />
-                  <Input
-                    label="Size"
-                    type="slider"
-                    min={0}
-                    max={40}
-                    step={1}
-                    value={nodeSettings.size}
-                    onChange={(val) => {
-                      updateSettings({ nodes: { ...settings.nodes, [nodeType]: { ...nodeSettings, size: Number(val) } } });
-                    }}
+                    label="Hidden"
+                    type="boolean"
+                    value={nodeSettings.hidden}
+                    onChange={(val) =>
+                      updateLayerSettings({ nodes: { ...layerSettings.nodes, [nodeType]: { ...nodeSettings, hidden: val } } })
+                    }
                   />
+                  <div className="border-t-2 my-2">
+                    <span className="font-bold mt-2">Location attributes</span>
+                    <Input
+                      inline
+                      label="Latitude"
+                      type="dropdown"
+                      value={settings?.location[nodeType]?.lat}
+                      options={[...spatialAttributes[nodeType]]}
+                      disabled={spatialAttributes[nodeType].length < 1}
+                      onChange={(val) => updateSpatialAttribute(nodeType, 'lat', val as string)}
+                    />
+                    <Input
+                      inline
+                      label="Longitude"
+                      type="dropdown"
+                      value={settings?.location[nodeType]?.lon}
+                      options={[...spatialAttributes[nodeType]]}
+                      disabled={spatialAttributes[nodeType].length < 1}
+                      onChange={(val) => updateSpatialAttribute(nodeType, 'lon', val as string)}
+                    />
+                  </div>
+
+                  <div className="border-t-2 my-2">
+                    <div className="flex justify-between">
+                      <span className="font-bold">Color</span>
+                      {!nodeSettings.colorByAttribute && (
+                        <ColorPicker
+                          value={nodeSettings.color}
+                          updateValue={(val) => {
+                            updateLayerSettings({ nodes: { ...layerSettings.nodes, [nodeType]: { ...nodeSettings, color: val } } });
+                          }}
+                        />
+                      )}
+                    </div>
+
+                    <div>
+                      <Input
+                        label="By attribute"
+                        type="boolean"
+                        value={nodeSettings.colorByAttribute ?? false}
+                        onChange={(val) =>
+                          updateLayerSettings({ nodes: { ...layerSettings.nodes, [nodeType]: { ...nodeSettings, colorByAttribute: val } } })
+                        }
+                      />
+                      {nodeSettings.colorByAttribute && (
+                        <div>
+                          <Input
+                            inline
+                            label="Color based on"
+                            type="dropdown"
+                            value={nodeSettings.colorAttribute}
+                            options={Object.keys(graphMetadata.nodes.types[nodeType]?.attributes)}
+                            disabled={!settings.nodes}
+                            onChange={(val) =>
+                              updateLayerSettings({
+                                nodes: {
+                                  ...layerSettings.nodes,
+                                  [nodeType]: {
+                                    ...nodeSettings,
+                                    colorAttribute: String(val),
+                                    colorAttributeType: graphMetadata.nodes.types[nodeType].attributes[val].dimension,
+                                  },
+                                },
+                              })
+                            }
+                          />
+                          {nodeSettings.colorAttributeType === 'numerical' ? (
+                            <div>
+                              <DropdownColorLegend
+                                value={settings?.colorScale}
+                                onChange={(val) =>
+                                  updateLayerSettings({
+                                    nodes: { ...layerSettings.nodes, [nodeType]: { ...nodeSettings, colorScale: val } },
+                                  })
+                                }
+                              />
+                            </div>
+                          ) : (
+                            <div>Categorical</div>
+                          )}
+                        </div>
+                      )}
+                    </div>
+                  </div>
+
+                  <div className="border-t-2 my-2">
+                    <span className="font-bold mt-2">Shape & Size</span>
+                    <Input
+                      inline
+                      label="Shape"
+                      type="dropdown"
+                      value={nodeSettings.shape}
+                      options={['circle', 'square', 'triangle', 'diamond', 'location', 'star']}
+                      disabled={!settings.shape}
+                      onChange={(val) =>
+                        updateLayerSettings({
+                          nodes: { ...layerSettings.nodes, [nodeType]: { ...nodeSettings, shape: String(val) } },
+                        })
+                      }
+                    />
+                    <Input
+                      label="Size"
+                      type="slider"
+                      min={0}
+                      max={40}
+                      step={1}
+                      value={nodeSettings.size}
+                      onChange={(val) =>
+                        updateLayerSettings({
+                          nodes: { ...layerSettings.nodes, [nodeType]: { ...nodeSettings, size: Number(val) } },
+                        })
+                      }
+                    />
+                  </div>
                 </div>
               )}
             </div>
           );
         })}
-
-      {settings?.edges &&
-        Object.keys(settings.edges).map((edgeType) => {
-          const edgeSettings = settings.edges[edgeType];
+        {graphMetadata.edges.labels.map((edgeType) => {
+          const edgeSettings = layerSettings?.edges?.[edgeType] || {};
 
           return (
             <div className="mt-2" key={edgeType}>
               <div className="flex items-center">
-                <div className="w-3/4 mr-6 cursor-pointer" onClick={() => handleCollapseToggle(edgeType)}>
+                <Button
+                  size="2xs"
+                  iconComponent={edgeSettings.collapsed ? 'icon-[ic--baseline-arrow-right]' : 'icon-[ic--baseline-arrow-drop-down]'}
+                  variant="ghost"
+                  onClick={() => handleCollapseToggle(edgeType, 'edges')}
+                />
+                <div className="w-3/4 mr-6 cursor-pointer" onClick={() => handleCollapseToggle(edgeType, 'edges')}>
                   <RelationPill title={edgeType} />
                 </div>
-                <div className="w-1/2 flex">
-                  <ColorPicker
-                    value={settings.edges[edgeType].color}
-                    updateValue={(val) => updateSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, color: val } } })}
-                  />
-                  <Button
-                    iconComponent={
-                      settings.edges[edgeType].hidden ? 'icon-[ic--baseline-visibility-off]' : 'icon-[ic--baseline-visibility]'
-                    }
-                    variant="ghost"
-                    onClick={() =>
-                      updateSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, hidden: !edgeSettings.hidden } } })
-                    }
-                  />
-                </div>
               </div>
 
               {!edgeSettings.collapsed && (
                 <div>
+                  <Input
+                    label="Hidden"
+                    type="boolean"
+                    value={edgeSettings.hidden ?? false}
+                    onChange={(val) => {
+                      updateLayerSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, hidden: val } } });
+                    }}
+                  />
+
+                  <div className="flex justify-between">
+                    <span className="font-bold">Color</span>
+                    <ColorPicker
+                      value={edgeSettings.color}
+                      updateValue={(val) =>
+                        updateLayerSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, color: val } } })
+                      }
+                    />
+                  </div>
+
                   <Input
                     label="Enable brushing"
                     type="boolean"
                     value={settings.enableBrushing}
                     onChange={(val) => {
-                      updateSettings({ enableBrushing: val as boolean });
+                      updateLayerSettings({ enableBrushing: val as boolean });
                     }}
                   />
 
@@ -143,7 +269,7 @@ export function NodeLinkOptions({ settings, graphMetadata, updateSettings, spati
                       label="Fixed"
                       type="boolean"
                       value={edgeSettings.fixed}
-                      onChange={(val) => updateSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, fixed: val } } })}
+                      onChange={(val) => updateLayerSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, fixed: val } } })}
                     />
                     {!edgeSettings.fixed ? (
                       <div>
@@ -152,15 +278,17 @@ export function NodeLinkOptions({ settings, graphMetadata, updateSettings, spati
                           type="dropdown"
                           size="xs"
                           options={
-                            graphMetadata.nodes.types[edgeType]?.attributes
-                              ? Object.keys(graphMetadata.nodes.types[edgeType].attributes).filter(
-                                  (key) => graphMetadata.nodes.types[edgeType].attributes[key].dimension === 'numerical',
+                            graphMetadata.edges.types[edgeType]?.attributes
+                              ? Object.keys(graphMetadata.edges.types[edgeType].attributes).filter(
+                                  (key) => graphMetadata.edges.types[edgeType].attributes[key].dimension === 'numerical',
                                 )
                               : []
                           }
-                          value={edgeSettings.sizeAttribute ?? ''}
+                          value={edgeSettings.sizeAttribute}
                           onChange={(val) =>
-                            updateSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, sizeAttribute: String(val) } } })
+                            updateLayerSettings({
+                              edges: { ...settings.edges, [edgeType]: { ...edgeSettings, sizeAttribute: String(val) } },
+                            })
                           }
                         />
                         <div className="flex">
@@ -169,14 +297,18 @@ export function NodeLinkOptions({ settings, graphMetadata, updateSettings, spati
                             label="min"
                             size="xs"
                             value={edgeSettings.min}
-                            onChange={(val) => updateSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, min: val } } })}
+                            onChange={(val) =>
+                              updateLayerSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, min: val } } })
+                            }
                           />
                           <Input
                             type="number"
                             label="max"
                             size="xs"
                             value={edgeSettings.max}
-                            onChange={(val) => updateSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, max: val } } })}
+                            onChange={(val) =>
+                              updateLayerSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, max: val } } })
+                            }
                           />
                         </div>
                       </div>
@@ -188,9 +320,9 @@ export function NodeLinkOptions({ settings, graphMetadata, updateSettings, spati
                           min={0}
                           max={10}
                           step={0.2}
-                          value={settings.edges[edgeType].width}
+                          value={edgeSettings.width}
                           onChange={(val) =>
-                            updateSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, width: Number(val) } } })
+                            updateLayerSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, width: Number(val) } } })
                           }
                         />
                       </div>
@@ -201,6 +333,7 @@ export function NodeLinkOptions({ settings, graphMetadata, updateSettings, spati
             </div>
           );
         })}
-    </div>
+      </div>
+    )
   );
 }
diff --git a/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx b/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx
index 43a4646a66f4ab38567f47cfa4cb577bc97d24c0..b18b40972209dee73e21f66cf073cb7326b58783 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx
@@ -1,53 +1,27 @@
 import React, { useEffect, useMemo, useCallback, useState, useRef } from 'react';
 import DeckGL from '@deck.gl/react';
-import { CompositeLayer, FlyToInterpolator, Position, WebMercatorViewport } from '@deck.gl/core';
-import { SelectionLayer } from '@deck.gl-community/editable-layers';
-import { CompositeLayerType, Coordinate, Layer } from './mapvis.types';
+import { CompositeLayer, FlyToInterpolator, WebMercatorViewport } from '@deck.gl/core';
+import { CompositeLayerType, Coordinate, LayerSettingsType, LocationInfo } from './mapvis.types';
 import { VISComponentType, VisualizationPropTypes } from '../../common';
-import { layerTypes, createBaseMap, MapLayerSettingsPropTypes } from './layers';
-import { MapSettings } from './MapSettings';
+import { layerTypes, createBaseMap, LayerTypes } from './layers';
+import { MapSettings } from './components/MapSettings';
 import { Node } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice';
-import { SearchBar } from './SearchBar';
-import { Icon } from '@graphpolaris/shared/lib/components';
-
-export type MapNodeOrEdgeData = MapNodeData | MapEdgeData;
-
-export type MapNodeData = {
-  color: [number, number, number];
-  hidden: boolean;
-  fixed: boolean;
-  min: number;
-  max: number;
-  radius: number;
-  collapsed: boolean;
-  lat?: string;
-  lon?: string;
-  shape: 'circle' | 'square' | 'triangle' | 'diamond' | 'location' | 'star';
-  size: number;
-  sizeAttribute?: string;
-};
-
-export type MapEdgeData = {
-  color: [number, number, number];
-  hidden: boolean;
-  fixed: boolean;
-  min: number;
-  max: number;
-  radius: number;
-  collapsed: boolean;
-  size: number;
-  width: number;
-  sizeAttribute?: string;
-};
+import { MapTooltip } from './components/Tooltip';
+import { geoCentroid } from 'd3';
+import Attribution from './components/Attribution';
+import ActionBar from './components/ActionBar';
+import { Tooltip, TooltipContent, TooltipTrigger } from '@graphpolaris/shared/lib/components';
+import { useSelectionLayer, useCoordinateLookup } from './hooks';
 
 export type MapProps = {
-  layer: string;
-  enableBrushing: boolean;
-  nodes: Record<string, MapNodeData>;
-  edges: Record<string, MapEdgeData>;
-};
+  layer: LayerTypes;
+  location: Record<string, LocationInfo>;
+} & Partial<Record<LayerTypes, LayerSettingsType>>;
 
-const settings: MapProps = { layer: 'nodelink', enableBrushing: false, nodes: {}, edges: {} };
+const settings: MapProps = {
+  layer: 'nodelink',
+  location: {},
+};
 
 const INITIAL_VIEW_STATE = {
   latitude: 52.1006,
@@ -60,6 +34,7 @@ const INITIAL_VIEW_STATE = {
 const FLY_SPEED = 1000;
 
 export const MapVis = (props: VisualizationPropTypes<MapProps>) => {
+  const ref = useRef<HTMLDivElement>(null);
   const baseLayer = useRef(createBaseMap());
   const [viewport, setViewport] = useState<Record<string, any>>(INITIAL_VIEW_STATE);
   const [hoverObject, setHoverObject] = useState<Node | null>(null);
@@ -68,6 +43,8 @@ export const MapVis = (props: VisualizationPropTypes<MapProps>) => {
   const [layerIds, setLayerIds] = useState<string[]>([]);
   const [isSearching, setIsSearching] = useState<boolean>(false);
 
+  const coordinateLookup = useCoordinateLookup(props.data.nodes, props.settings.location);
+
   const getFittedViewport = useCallback(
     (minLat: number, maxLat: number, minLon: number, maxLon: number) => {
       const viewportWebMercator = new WebMercatorViewport(viewport).fitBounds(
@@ -106,29 +83,10 @@ export const MapVis = (props: VisualizationPropTypes<MapProps>) => {
     }
 
     const layer = {
-      component: props.settings.layer ? layerTypes?.[props.settings.layer] : layerTypes.nodelink,
-      settings: props.settings,
       id: props.settings.layer,
+      component: props.settings.layer ? layerTypes?.[props.settings.layer] : layerTypes.nodelink,
     };
 
-    const coordinateLookup: { [id: string]: Position } = props.data.nodes.reduce(
-      (acc, node) => {
-        const latitude = props.settings.nodes?.[node.label]?.lat
-          ? (node?.attributes?.[props.settings.nodes[node.label].lat as any] as string)
-          : undefined;
-        const longitude = props.settings.nodes?.[node.label]?.lon
-          ? (node?.attributes?.[props.settings.nodes[node.label].lon as any] as string)
-          : undefined;
-
-        if (latitude !== undefined && longitude !== undefined) {
-          acc[node._id] = [parseFloat(longitude), parseFloat(latitude)];
-        }
-
-        return acc;
-      },
-      {} as { [id: string]: Position },
-    );
-
     const layerProps: CompositeLayerType = {
       ...props,
       selected: selected,
@@ -139,7 +97,6 @@ export const MapVis = (props: VisualizationPropTypes<MapProps>) => {
     };
 
     if (dataLayer && dataLayer.id === layer.id) {
-      // dataLayer.updateState;
       setDataLayer({ component: dataLayer.component.clone(layerProps), id: props.settings.layer });
     } else {
       // @ts-ignore
@@ -147,97 +104,114 @@ export const MapVis = (props: VisualizationPropTypes<MapProps>) => {
     }
   }, [props.settings.layer, props.data, selected, hoverObject, props.settings]);
 
-  const selectionLayer = useMemo(
-    () =>
-      selectingRectangle &&
-      new (SelectionLayer as any)({
-        id: 'selection',
-        selectionType: 'rectangle',
-        onSelect: ({ pickingInfos }: { pickingInfos: any[] }) => {
-          if (pickingInfos.length > 0) {
-            const nodes = [];
-            const edges = [];
-
-            for (const selectedItem of pickingInfos) {
-              const { object } = selectedItem;
-              if (object._id) {
-                if (object.from & object.to) {
-                  edges.push(object);
-                } else {
-                  nodes.push(object);
-                }
-              }
-            }
-            setSelected(nodes.map((node) => node._id));
-            props.handleSelect({ nodes, edges });
-          } else {
-            props.handleSelect();
+  const selectionLayer = useSelectionLayer(selectingRectangle, layerIds, (pickingInfos: any[]) => {
+    const nodes: Node[] = [];
+    const edges: any[] = [];
+    pickingInfos.forEach(({ object }) => {
+      if (object._id) {
+        if (object.from && object.to) {
+          edges.push(object);
+        } else {
+          nodes.push(object);
+        }
+      }
+    });
+    setSelected(nodes.map((node) => node._id));
+    props.handleSelect({ nodes, edges });
+    setSelectingRectangle(false);
+  });
+
+  const coordinateToXY = useCallback(
+    (coordinate: Coordinate) => {
+      const [longitude, latitude] = coordinate;
+      return new WebMercatorViewport(viewport).project([longitude, latitude]);
+    },
+    [viewport],
+  );
+
+  useEffect(() => {
+    if (selected.length > 0) {
+      const updatedSelected = selected.map((node) => {
+        let x, y;
+        if (props.settings.layer === 'nodelink' && node.lon && node.lat) {
+          const coordinate: Coordinate = [parseFloat(node.lon), parseFloat(node.lat)];
+          [x, y] = coordinateToXY(coordinate);
+        } else if (props.settings.layer === 'choropleth') {
+          const centroid = geoCentroid(node);
+          if (centroid) {
+            [x, y] = coordinateToXY([centroid[0], centroid[1]]);
+          }
+        }
+        return { ...node, x, y };
+      });
+      setSelected(updatedSelected);
+    }
+  }, [viewport]);
+
+  const handleClick = useCallback(
+    (object: any) => {
+      if (props.data) {
+        if (!object) {
+          props.handleSelect();
+          setSelected([]);
+          return;
+        }
+        if (object.hasOwnProperty('attributes') && object.hasOwnProperty('id') && object.hasOwnProperty('label')) {
+          const objectLocation: Coordinate = coordinateLookup[object.id];
+          props.handleSelect({ nodes: [object] });
+          if (objectLocation) {
+            const [x, y] = coordinateToXY(objectLocation);
+            setSelected([{ ...object, x, y, lon: objectLocation[0], lat: objectLocation[1], selectedType: 'node' }]);
           }
-          setSelectingRectangle(false);
-        },
-        layerIds: layerIds,
-        getTentativeFillColor: () => [22, 37, 67, 100],
-      }),
-    [selectingRectangle],
+        }
+        if (object.type === 'Feature') {
+          const centroid = geoCentroid(object);
+          if (centroid) {
+            const [x, y] = coordinateToXY(centroid);
+            setSelected([{ ...object, x, y, selectedType: 'area' }]);
+          }
+          const ids = object.properties.nodes;
+          if (ids && ids.length > 0) {
+            const nodes = props.data.nodes.filter((node) => ids.includes((node as unknown as { id: string }).id));
+            props.handleSelect({ nodes: [...nodes] });
+          }
+        }
+      }
+    },
+    [props, coordinateLookup, coordinateToXY],
   );
 
   return (
-    <div className="w-full h-full flex-grow relative overflow-hidden">
-      <div className="absolute left-0 top-0 z-50 m-1">
-        <div className="cursor-pointer p-1 bg-white shadow-md rounded mb-1" onClick={() => setSelectingRectangle(true)}>
-          <Icon component="icon-[ic--baseline-highlight-alt]" />
-        </div>
-        <div className="cursor-pointer p-1 bg-white shadow-md rounded" onClick={() => setIsSearching(!isSearching)}>
-          <Icon component="icon-[ic--outline-search]" />
-        </div>
-      </div>
-      {isSearching && (
-        <SearchBar
-          onSearch={(boundingBox: [number, number, number, number]) => {
-            flyToBoundingBox(...boundingBox);
-            setIsSearching(false);
-          }}
-        />
-      )}
+    <div className="w-full h-full flex-grow relative overflow-hidden" ref={ref}>
       <DeckGL
         layers={[baseLayer.current, dataLayer?.component, selectionLayer]}
         controller={true}
         initialViewState={viewport}
         onViewStateChange={({ viewState }) => setViewport(viewState)}
-        onClick={({ object }) => {
-          if (props.data) {
-            if (!object) {
-              props.handleSelect();
-              setSelected([]);
-              return;
-            }
-            if (object.hasOwnProperty('attributes') && object.hasOwnProperty('id') && object.hasOwnProperty('label')) {
-              props.handleSelect({ nodes: [object] });
-              setSelected([object.id]);
-            }
-            if (object.type === 'Feature') {
-              const ids = object.properties.nodes;
-              if (ids.length > 0) {
-                const nodes = props.data.nodes.filter((node) => ids.includes((node as unknown as { id: string }).id));
-                props.handleSelect({ nodes: [...nodes] });
-              } else {
-                props.handleSelect();
-                setSelected([]);
-                return;
-              }
-            }
-          }
-        }}
-        onHover={({ object }) => {
-          setHoverObject(object !== undefined ? object : null);
-        }}
+        onClick={({ object }) => handleClick(object)}
+        onHover={({ object }) => setHoverObject(object !== undefined ? object : null)}
+      />
+      <ActionBar
+        isSearching={isSearching}
+        setSelectingRectangle={setSelectingRectangle}
+        setIsSearching={setIsSearching}
+        flyToBoundingBox={flyToBoundingBox}
       />
-      <div className="absolute right-0 top-0 p-1 z-50 bg-white bg-opacity-75 text-xs">
-        {'© '}
-        <a className="underline" href="http://www.openstreetmap.org/copyright" target="_blank" rel="noopener noreferrer">
-          OpenStreetMap
-        </a>
-      </div>
+      {selected.length > 0 &&
+        selected.map((node, index) => (
+          <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: { ...node }, pos: { x: node.x, y: node.y } }}
+                key={node._id}
+              />
+            </TooltipContent>
+          </Tooltip>
+        ))}
+      <Attribution />
     </div>
   );
 };
diff --git a/libs/shared/lib/vis/visualizations/mapvis/mapvis.types.ts b/libs/shared/lib/vis/visualizations/mapvis/mapvis.types.ts
index 3dcad5c67ab35ba0fa1cd47f61685b8cbcdbb326..ca106950ccbe807b868646bcf000ccc09f8851fe 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/mapvis.types.ts
+++ b/libs/shared/lib/vis/visualizations/mapvis/mapvis.types.ts
@@ -1,10 +1,62 @@
-import { CompositeLayer, Position } from 'deck.gl';
-import { MapLayerSettingsPropTypes } from './layers';
+import { CompositeLayer } from 'deck.gl';
 import { VisualizationPropTypes, VisualizationSettingsType } from '../../common';
 import { MapProps } from './mapvis';
 import { Node as QueryNode } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice';
+import { GraphMetadata } from '@graphpolaris/shared/lib/data-access/statistics';
 
-export type Coordinate = [number, number] | [];
+export type Coordinate = [number, number];
+
+export type LocationInfo = { lat: string; lon: string };
+
+export type MapNodeData = {
+  color: [number, number, number];
+  hidden: boolean;
+  fixed: boolean;
+  min: number;
+  max: number;
+  radius: number;
+  collapsed: boolean;
+  lat?: string;
+  lon?: string;
+  shape: string;
+  size: number;
+  sizeAttribute?: string;
+  colorByAttribute?: boolean;
+  colorAttribute?: string | undefined;
+  colorAttributeType?: string | undefined;
+  colorScale: string;
+};
+
+export type MapEdgeData = {
+  color: [number, number, number];
+  hidden: boolean;
+  fixed: boolean;
+  min: number;
+  max: number;
+  radius: number;
+  collapsed: boolean;
+  size: number;
+  width: number;
+  sizeAttribute?: string;
+  enableBrushing?: boolean;
+};
+
+export type LayerSettingsType = {
+  nodes: Record<string, MapNodeData>;
+  edges: Record<string, MapEdgeData>;
+  coloringStrategy?: string;
+  colorScale?: string;
+  opacity?: number;
+  [id: string]: any;
+};
+
+export type LayerSettingsComponentType<T = {}> = {
+  settings: T & VisualizationSettingsType;
+  graphMetadata: GraphMetadata;
+  spatialAttributes: { [k: string]: string[] };
+  updateSpatialAttribute: (label: string, attribute: 'lat' | 'lon', value: string) => void;
+  updateLayerSettings: (val: Partial<LayerSettingsType>) => void;
+};
 
 export interface LayerProps {
   [key: string]: any;
@@ -13,7 +65,7 @@ export interface LayerProps {
 export type CompositeLayerType = VisualizationPropTypes<MapProps> & {
   selected: any[];
   hoverObject: QueryNode | null;
-  getNodeLocation: (d: string) => Position;
+  getNodeLocation: (d: string) => Coordinate;
   flyToBoundingBox: (minLat: number, maxLat: number, minLon: number, maxLon: number, options?: { [key: string]: any }) => void;
   setLayerIds: (val: string[]) => void;
 };
@@ -44,3 +96,13 @@ export type Edge = {
   };
   [key: string]: any;
 };
+
+export type GeoJsonType = {
+  properties: {
+    name: string;
+    [id: string]: any;
+  };
+  [id: string]: any;
+};
+
+export type BoundingBoxType = [number, number, number, number];