From 2e0fd4afdb96e7a2e5790a0e7a50db828d010928 Mon Sep 17 00:00:00 2001
From: Chris Gervang <chrisgervang@users.noreply.github.com>
Date: Wed, 6 Mar 2024 09:57:27 -0800
Subject: [PATCH] chore(examples) Use MapboxOverlay in mapbox (formerly
 safegraph) website example (#8558)

* chore(examples) Use MapboxOverlay in mapbox website example

* rename safegraph folder to mapbox

* chore(examples) use react in mapbox website example

---------

Signed-off-by: Chris Gervang <chris@gervang.com>
---
 .../website/{safegraph => mapbox}/README.md   |   0
 examples/website/mapbox/app.jsx               | 150 ++++++++++++++++++
 .../website/{safegraph => mapbox}/index.html  |   5 +-
 .../{safegraph => mapbox}/package.json        |   7 +-
 .../{safegraph => mapbox}/vite.config.js      |   0
 examples/website/safegraph/app.js             | 135 ----------------
 website/src/examples/mapbox.js                |   4 +-
 7 files changed, 159 insertions(+), 142 deletions(-)
 rename examples/website/{safegraph => mapbox}/README.md (100%)
 create mode 100644 examples/website/mapbox/app.jsx
 rename examples/website/{safegraph => mapbox}/index.html (73%)
 rename examples/website/{safegraph => mapbox}/package.json (74%)
 rename examples/website/{safegraph => mapbox}/vite.config.js (100%)
 delete mode 100644 examples/website/safegraph/app.js

diff --git a/examples/website/safegraph/README.md b/examples/website/mapbox/README.md
similarity index 100%
rename from examples/website/safegraph/README.md
rename to examples/website/mapbox/README.md
diff --git a/examples/website/mapbox/app.jsx b/examples/website/mapbox/app.jsx
new file mode 100644
index 00000000000..e4556f5c2e3
--- /dev/null
+++ b/examples/website/mapbox/app.jsx
@@ -0,0 +1,150 @@
+import React, {useCallback, useState, useRef, useMemo} from 'react';
+import {createRoot} from 'react-dom/client';
+import {MapboxOverlay} from '@deck.gl/mapbox';
+import {ArcLayer} from '@deck.gl/layers';
+import {H3HexagonLayer} from '@deck.gl/geo-layers';
+import {scaleLog} from 'd3-scale';
+import {cellToLatLng} from 'h3-js';
+import {load} from '@loaders.gl/core';
+import {CSVLoader} from '@loaders.gl/csv';
+import {Map, NavigationControl, useControl, Layer} from 'react-map-gl';
+import 'mapbox-gl/dist/mapbox-gl.css';
+
+const DATA_URL =
+  'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/safegraph/sf-pois.csv';
+
+// Set your mapbox token here
+const MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line
+
+const colorScale = scaleLog()
+  .domain([10, 100, 1000, 10000])
+  .range([
+    [255, 255, 178],
+    [254, 204, 92],
+    [253, 141, 60],
+    [227, 26, 28]
+  ]);
+
+const buildings3DLayer = {
+  id: '3d-buildings',
+  source: 'composite',
+  'source-layer': 'building',
+  filter: ['==', 'extrude', 'true'],
+  type: 'fill-extrusion',
+  minzoom: 14,
+  paint: {
+    'fill-extrusion-color': '#ccc',
+    'fill-extrusion-height': ['get', 'height']
+  }
+};
+
+function DeckGLOverlay(props) {
+  const overlay = useControl(() => new MapboxOverlay(props));
+  overlay.setProps(props);
+  return null;
+}
+
+export default function App({data}) {
+  const [selectedPOI, setSelectedPOI] = useState('8a283082aa17fff');
+  const [firstLabelLayerId, setFirstLabelLayerId] = useState();
+  const mapRef = useRef();
+
+  const onMapLoad = useCallback(() => {
+    setFirstLabelLayerId(getFirstLabelLayerId(mapRef.current.getStyle()));
+  }, []);
+
+  const selectedPOICentroid = useMemo(() => {
+    const [lat, lng] = cellToLatLng(selectedPOI);
+    return [lng, lat];
+  }, [selectedPOI]);
+
+  const arcs = useMemo(() => filterArcs(data, selectedPOI), [data, selectedPOI]);
+
+  const hexes = useMemo(() => aggregateHexes(data), [data]);
+
+  const arcLayer = new ArcLayer({
+    id: 'deckgl-connections',
+    data: arcs,
+    getSourcePosition: d => selectedPOICentroid,
+    getTargetPosition: d => [d.home_lng, d.home_lat],
+    getSourceColor: [255, 0, 128],
+    getTargetColor: [0, 200, 255],
+    getWidth: d => Math.max(2, d.count / 15)
+  });
+
+  const poiLayer = new H3HexagonLayer({
+    id: 'deckgl-pois',
+    data: hexes,
+    opacity: 0.4,
+    pickable: true,
+    autoHighlight: true,
+    onClick: ({object}) => object && setSelectedPOI(object.hex),
+    getHexagon: d => d.hex,
+    getFillColor: d => colorScale(d.count),
+    extruded: false,
+    stroked: false,
+    beforeId: firstLabelLayerId
+  });
+
+  return (
+    <Map
+      ref={mapRef}
+      mapboxAccessToken={MAPBOX_TOKEN}
+      mapStyle="mapbox://styles/mapbox/light-v9"
+      antialias={true}
+      initialViewState={{
+        longitude: -122.4034,
+        latitude: 37.7845,
+        zoom: 15.5,
+        bearing: 20,
+        pitch: 60
+      }}
+      onLoad={onMapLoad}
+    >
+      <DeckGLOverlay interleaved={true} layers={[poiLayer, arcLayer]} />
+      <NavigationControl />
+      <Layer {...buildings3DLayer} />
+    </Map>
+  );
+}
+
+function filterArcs(data, selectedPOI) {
+  if (!data) {
+    return null;
+  }
+  return data.filter(d => d.hex === selectedPOI);
+}
+
+function aggregateHexes(data) {
+  if (!data) {
+    return null;
+  }
+  const result = {};
+  for (const object of data) {
+    if (!result[object.hex]) {
+      result[object.hex] = {hex: object.hex, count: 0};
+    }
+    result[object.hex].count += object.count;
+  }
+  return Object.values(result);
+}
+
+function getFirstLabelLayerId(style) {
+  const layers = style.layers;
+  // Find the index of the first symbol (i.e. label) layer in the map style
+  for (let i = 0; i < layers.length; i++) {
+    if (layers[i].type === 'symbol') {
+      return layers[i].id;
+    }
+  }
+  return undefined;
+}
+
+export function renderToDOM(container) {
+  const root = createRoot(container);
+  root.render(<App />);
+
+  load(DATA_URL, CSVLoader).then(data => {
+    root.render(<App data={data.data} />);
+  });
+}
diff --git a/examples/website/safegraph/index.html b/examples/website/mapbox/index.html
similarity index 73%
rename from examples/website/safegraph/index.html
rename to examples/website/mapbox/index.html
index 535927f05fd..54527a0ace0 100644
--- a/examples/website/safegraph/index.html
+++ b/examples/website/mapbox/index.html
@@ -3,7 +3,6 @@
   <head>
     <meta charset='UTF-8' />
     <title>mapbox-custom-layer-example</title>
-    <link href="https://api.mapbox.com/mapbox-gl-js/v1.13.0/mapbox-gl.css" rel="stylesheet" />
     <style>
       body {
         font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
@@ -23,7 +22,7 @@
     <div id="app"></div>
   </body>
   <script type="module">
-    import {loadAndRender} from './app.js';
-    loadAndRender(document.getElementById('app'));
+    import {renderToDOM} from './app.jsx';
+    renderToDOM(document.getElementById('app'));
   </script>
 </html>
diff --git a/examples/website/safegraph/package.json b/examples/website/mapbox/package.json
similarity index 74%
rename from examples/website/safegraph/package.json
rename to examples/website/mapbox/package.json
index 7a542b50a74..55a9b6ece2c 100644
--- a/examples/website/safegraph/package.json
+++ b/examples/website/mapbox/package.json
@@ -11,8 +11,11 @@
   "dependencies": {
     "@loaders.gl/csv": "^4.1.0",
     "d3-scale": "^2.0.0",
-    "deck.gl": "^9.0.0-alpha",
-    "mapbox-gl": "^2.0.0"
+    "deck.gl": "^9.0.0-beta",
+    "mapbox-gl": "^3.0.0",
+    "react": "^18.0.0",
+    "react-dom": "^18.0.0",
+    "react-map-gl": "^7.0.0"
   },
   "devDependencies": {
     "typescript": "^4.6.0",
diff --git a/examples/website/safegraph/vite.config.js b/examples/website/mapbox/vite.config.js
similarity index 100%
rename from examples/website/safegraph/vite.config.js
rename to examples/website/mapbox/vite.config.js
diff --git a/examples/website/safegraph/app.js b/examples/website/safegraph/app.js
deleted file mode 100644
index 4a3511123e3..00000000000
--- a/examples/website/safegraph/app.js
+++ /dev/null
@@ -1,135 +0,0 @@
-import mapboxgl from 'mapbox-gl';
-import {MapboxLayer} from '@deck.gl/mapbox';
-import {ArcLayer} from '@deck.gl/layers';
-import {H3HexagonLayer} from '@deck.gl/geo-layers';
-import {scaleLog} from 'd3-scale';
-import {cellToLatLng} from 'h3-js';
-
-import {load} from '@loaders.gl/core';
-import {CSVLoader} from '@loaders.gl/csv';
-
-// Set your mapbox token here
-mapboxgl.accessToken = process.env.MapboxAccessToken; // eslint-disable-line
-
-const colorScale = scaleLog()
-  .domain([10, 100, 1000, 10000])
-  .range([
-    [255, 255, 178],
-    [254, 204, 92],
-    [253, 141, 60],
-    [227, 26, 28]
-  ]);
-
-export function renderToDOM(container, data) {
-  const map = new mapboxgl.Map({
-    container,
-    useWebGL2: true,
-    style: 'mapbox://styles/mapbox/light-v9',
-    antialias: true,
-    center: [-122.4034, 37.7845],
-    zoom: 15.5,
-    bearing: 20,
-    pitch: 60
-  });
-
-  map.addControl(new mapboxgl.NavigationControl(), 'top-left');
-
-  map.on('load', () => {
-    map.addLayer({
-      id: '3d-buildings',
-      source: 'composite',
-      'source-layer': 'building',
-      filter: ['==', 'extrude', 'true'],
-      type: 'fill-extrusion',
-      minzoom: 14,
-      paint: {
-        'fill-extrusion-color': '#ccc',
-        'fill-extrusion-height': ['get', 'height']
-      }
-    });
-
-    renderLayers(map, data);
-  });
-
-  return {
-    update: newData => renderLayers(map, newData),
-    remove: () => {
-      map.remove();
-    }
-  };
-}
-
-function renderLayers(map, data) {
-  if (!data) {
-    return;
-  }
-  let selectedPOICentroid;
-
-  const arcLayer = new MapboxLayer({
-    id: 'deckgl-connections',
-    type: ArcLayer,
-    data: [],
-    getSourcePosition: d => selectedPOICentroid,
-    getTargetPosition: d => [d.home_lng, d.home_lat],
-    getSourceColor: [255, 0, 128],
-    getTargetColor: [0, 200, 255],
-    getWidth: d => Math.max(2, d.count / 15)
-  });
-
-  const selectPOI = hex => {
-    const [lat, lng] = cellToLatLng(hex);
-    selectedPOICentroid = [lng, lat];
-    arcLayer.setProps({
-      data: data.filter(d => d.hex === hex)
-    });
-  };
-
-  const poiLayer = new MapboxLayer({
-    id: 'deckgl-pois',
-    type: H3HexagonLayer,
-    data: aggregateHexes(data),
-    opacity: 0.4,
-    pickable: true,
-    autoHighlight: true,
-    onClick: ({object}) => object && selectPOI(object.hex),
-    getHexagon: d => d.hex,
-    getFillColor: d => colorScale(d.count),
-    extruded: false,
-    stroked: false
-  });
-
-  map.addLayer(poiLayer, getFirstLabelLayerId(map.getStyle()));
-  map.addLayer(arcLayer);
-
-  selectPOI('8a283082aa17fff');
-}
-
-function aggregateHexes(data) {
-  const result = {};
-  for (const object of data) {
-    if (!result[object.hex]) {
-      result[object.hex] = {hex: object.hex, count: 0};
-    }
-    result[object.hex].count += object.count;
-  }
-  return Object.values(result);
-}
-
-function getFirstLabelLayerId(style) {
-  const layers = style.layers;
-  // Find the index of the first symbol (i.e. label) layer in the map style
-  for (let i = 0; i < layers.length; i++) {
-    if (layers[i].type === 'symbol') {
-      return layers[i].id;
-    }
-  }
-  return undefined;
-}
-
-export async function loadAndRender(container) {
-  const data = await load(
-    'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/safegraph/sf-pois.csv',
-    CSVLoader
-  );
-  renderToDOM(container, data.data);
-}
diff --git a/website/src/examples/mapbox.js b/website/src/examples/mapbox.js
index a05d9e226f8..2cdf2e18139 100644
--- a/website/src/examples/mapbox.js
+++ b/website/src/examples/mapbox.js
@@ -1,13 +1,13 @@
 import React, {Component, createRef} from 'react';
 import {DATA_URI, GITHUB_TREE} from '../constants/defaults';
-import {renderToDOM} from 'website-examples/safegraph/app';
+import {renderToDOM} from 'website-examples/mapbox/app';
 
 import {makeExample} from '../components';
 
 class MapboxDemo extends Component {
   static title = 'Who Is Visiting San Francisco?';
 
-  static code = `${GITHUB_TREE}/examples/website/safegraph`;
+  static code = `${GITHUB_TREE}/examples/website/mapbox`;
 
   static data = {
     url: `${DATA_URI}/safegraph.txt`,