@sigma/layer-maplibre 3.0.0 → 4.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (C) 2013-2024, Alexis Jacomy, Guillaume Plique, Benoît Simard https://www.sigmajs.org
1
+ Copyright (C) 2012-2026, Alexis Jacomy, Guillaume Plique, Benoît Simard https://www.sigmajs.org
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
4
4
  to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
package/README.md CHANGED
@@ -24,4 +24,4 @@ const sigma = new Sigma(graph, container);
24
24
  bindMaplibreLayer(sigma);
25
25
  ```
26
26
 
27
- Please check the related [Storybook](https://github.com/jacomyal/sigma.js/tree/main/packages/storybook/stories/3-additional-packages/layer-maplibre) for more advanced examples.
27
+ Please check the [website examples](https://www.sigmajs.org/examples/) for more advanced examples.
@@ -11,7 +11,7 @@ import { graphToLatlng, latlngToGraph } from "./utils.js";
11
11
  * @param opts.getNodeLatLng Function to retrieve lat/long values from a node's attributs (default is lat & lng)
12
12
  */
13
13
  export default function bindMaplibreLayer(sigma: Sigma, opts?: {
14
- mapOptions?: Omit<MapOptions, "container" | "center" | "zoom" | "minPitch" | "maxPitch">;
14
+ mapOptions?: Omit<MapOptions, "container" | "center" | "zoom" | "bounds" | "minPitch" | "maxPitch" | "interactive">;
15
15
  getNodeLatLng?: (nodeAttributes: Attributes) => {
16
16
  lat: number;
17
17
  lng: number;
@@ -1,10 +1,7 @@
1
- import { Map } from "maplibre-gl";
2
- import { Sigma } from "sigma";
3
- export declare const MAX_VALID_LATITUDE = 85.051129;
4
1
  /**
5
2
  * Given a geo point returns its graph coords.
6
3
  */
7
- export declare function latlngToGraph(map: Map, coord: {
4
+ export declare function latlngToGraph(coord: {
8
5
  lat: number;
9
6
  lng: number;
10
7
  }): {
@@ -12,20 +9,12 @@ export declare function latlngToGraph(map: Map, coord: {
12
9
  y: number;
13
10
  };
14
11
  /**
15
- * Given a graph coords returns its lat/lng coords.
12
+ * Given graph coords returns its lat/lng coords.
16
13
  */
17
- export declare function graphToLatlng(map: Map, coords: {
14
+ export declare function graphToLatlng(coords: {
18
15
  x: number;
19
16
  y: number;
20
17
  }): {
21
18
  lat: number;
22
19
  lng: number;
23
20
  };
24
- /**
25
- * BBOX sync : map to sigma
26
- */
27
- export declare function syncSigmaWithMap(sigma: Sigma, map: Map): void;
28
- /**
29
- * BBOX sync : sigma to map
30
- */
31
- export declare function syncMapWithSigma(sigma: Sigma, map: Map): void;
@@ -2,6 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var utils = require('@sigma/utils');
5
6
  var maplibreGl = require('maplibre-gl');
6
7
 
7
8
  function _toPrimitive(t, r) {
@@ -54,20 +55,19 @@ function _objectSpread2(e) {
54
55
  /**
55
56
  * Given a geo point returns its graph coords.
56
57
  */
57
- function latlngToGraph(map, coord) {
58
+ function latlngToGraph(coord) {
58
59
  var data = maplibreGl.MercatorCoordinate.fromLngLat(coord);
59
60
  return {
60
61
  x: data.x,
61
- // Y are reversed between geo / sigma
62
- y: map.getContainer().clientHeight - data.y
62
+ y: 1 - data.y
63
63
  };
64
64
  }
65
65
 
66
66
  /**
67
- * Given a graph coords returns its lat/lng coords.
67
+ * Given graph coords returns its lat/lng coords.
68
68
  */
69
- function graphToLatlng(map, coords) {
70
- var mcoords = new maplibreGl.MercatorCoordinate(coords.x, map.getContainer().clientHeight - coords.y, 0);
69
+ function graphToLatlng(coords) {
70
+ var mcoords = new maplibreGl.MercatorCoordinate(coords.x, 1 - coords.y, 0);
71
71
  var data = mcoords.toLngLat();
72
72
  return {
73
73
  lat: data.lat,
@@ -75,59 +75,6 @@ function graphToLatlng(map, coords) {
75
75
  };
76
76
  }
77
77
 
78
- /**
79
- * BBOX sync : map to sigma
80
- */
81
- function syncSigmaWithMap(sigma, map) {
82
- // Compute sigma center
83
- var center = sigma.viewportToFramedGraph(sigma.graphToViewport(latlngToGraph(map, map.getCenter())));
84
-
85
- // Compute sigma ratio
86
- var mapBound = map.getBounds();
87
- var northEast = sigma.graphToViewport(latlngToGraph(map, mapBound.getNorthEast()));
88
- var southWest = sigma.graphToViewport(latlngToGraph(map, mapBound.getSouthWest()));
89
- var viewportBoundDimension = {
90
- width: Math.abs(northEast.x - southWest.x),
91
- height: Math.abs(northEast.y - southWest.y)
92
- };
93
- var viewportDim = sigma.getDimensions();
94
- var ratio = Math.min(viewportBoundDimension.width / viewportDim.width, viewportBoundDimension.height / viewportDim.height) * sigma.getCamera().getState().ratio;
95
- sigma.getCamera().setState(_objectSpread2(_objectSpread2({}, center), {}, {
96
- ratio: ratio
97
- }));
98
- }
99
-
100
- /**
101
- * BBOX sync : sigma to map
102
- */
103
- function syncMapWithSigma(sigma, map) {
104
- var viewportDimensions = sigma.getDimensions();
105
-
106
- // Graph BBox
107
- var graphBottomLeft = sigma.viewportToGraph({
108
- x: 0,
109
- y: viewportDimensions.height
110
- }, {
111
- padding: 0
112
- });
113
- var graphTopRight = sigma.viewportToGraph({
114
- x: viewportDimensions.width,
115
- y: 0
116
- }, {
117
- padding: 0
118
- });
119
-
120
- // Geo BBox
121
- var geoSouthWest = graphToLatlng(map, graphBottomLeft);
122
- var geoNorthEast = graphToLatlng(map, graphTopRight);
123
-
124
- // Set map BBox
125
- var bounds = new maplibreGl.LngLatBounds([geoSouthWest, geoNorthEast]);
126
- map.fitBounds(bounds, {
127
- duration: 0
128
- });
129
- }
130
-
131
78
  /**
132
79
  * On the graph, we store the 2D projection of the geographical lat/long.
133
80
  *
@@ -139,40 +86,26 @@ function bindMaplibreLayer(sigma, opts) {
139
86
  // Keeping data for the cleanup
140
87
  var isKilled = false;
141
88
  var prevSigmaSettings = sigma.getSettings();
142
-
143
- // Creating map container
144
- var mapLayerName = "layer-maplibre";
145
- var mapContainer = sigma.createLayer(mapLayerName, "div", {
146
- style: {
147
- position: "absolute",
148
- inset: "0"
149
- },
150
- // 'edges' is the first sigma layer
151
- beforeLayer: "edges"
152
- });
153
- sigma.getContainer().prepend(mapContainer);
154
-
155
- // Initialize the map
156
- var map = new maplibreGl.Map(_objectSpread2({
157
- container: mapContainer,
158
- style: "https://demotiles.maplibre.org/style.json",
159
- center: [0, 0],
160
- zoom: 1,
161
- minPitch: 0,
162
- maxPitch: 0
163
- }, (opts === null || opts === void 0 ? void 0 : opts.mapOptions) || {}));
89
+ var prevCustomBBox = sigma.getCustomBBox();
164
90
 
165
91
  // `stagePadding: 0` is mandatory, so the bbox of the map & Sigma is the same.
166
92
  sigma.setSetting("stagePadding", 0);
167
-
168
93
  // disable camera rotation
169
94
  sigma.setSetting("enableCameraRotation", false);
170
95
 
171
- // Function that change the given graph by generating the sigma x,y coords by taking the geo coordinates
172
- // and project them in the 2D space of the map
96
+ // Mercator graph coordinates are in [0,1]. Set customBBox to make
97
+ // normalization identity, and force a synchronous refresh so the
98
+ // normalization function is updated before camera constraints are applied.
99
+ sigma.setCustomBBox({
100
+ x: [0, 1],
101
+ y: [0, 1]
102
+ });
103
+
104
+ // Function that updates graph node positions from geo coordinates to
105
+ // Mercator [0,1] space
173
106
  function updateGraphCoordinates(graph) {
174
107
  graph.updateEachNodeAttributes(function (_node, attrs) {
175
- var coords = latlngToGraph(map, opts !== null && opts !== void 0 && opts.getNodeLatLng ? opts.getNodeLatLng(attrs) : {
108
+ var coords = latlngToGraph(opts !== null && opts !== void 0 && opts.getNodeLatLng ? opts.getNodeLatLng(attrs) : {
176
109
  lat: attrs.lat,
177
110
  lng: attrs.lng
178
111
  });
@@ -183,95 +116,134 @@ function bindMaplibreLayer(sigma, opts) {
183
116
  });
184
117
  }
185
118
 
186
- // Function that sync the map with sigma
187
- function fnSyncMapWithSigma() {
188
- syncMapWithSigma(sigma, map);
189
- }
119
+ // Update graph coordinates and refresh sigma so normalization is ready
120
+ updateGraphCoordinates(sigma.getGraph());
121
+ sigma.refresh();
190
122
 
191
- // Function that sync sigma with map if it's needed
192
- function fnSyncSigmaWithMap() {
193
- if (!sigma.getCamera().isAnimated() && !map.isMoving()) {
194
- // Check that sigma & map are already in sync
195
- var southWest = graphToLatlng(map, sigma.viewportToGraph({
196
- x: 0,
197
- y: sigma.getDimensions().height
198
- }));
199
- var northEast = graphToLatlng(map, sigma.viewportToGraph({
200
- x: sigma.getDimensions().width,
201
- y: 0
202
- }));
203
- var diff = Math.max(map.getBounds().getSouthWest().distanceTo(new maplibreGl.LngLat(southWest.lng, southWest.lat)), map.getBounds().getNorthEast().distanceTo(new maplibreGl.LngLat(northEast.lng, northEast.lat)));
204
- if (diff > 1) {
205
- syncSigmaWithMap(sigma, map);
206
- }
207
- }
123
+ // Now that normalization is correct, fit the camera to the graph's
124
+ // Mercator extent so the initial view frames the nodes.
125
+ var graph = sigma.getGraph();
126
+ if (graph.order) {
127
+ sigma.getCamera().setState(utils.getCameraStateToFitViewportToNodes(sigma, graph.nodes()));
208
128
  }
209
129
 
210
- // When sigma is resize, we need to update the graph coordinate (the ref has changed)
211
- // and recompute the zoom bounds
212
- function fnOnResize() {
213
- // Avoid sync map with sigma while we do the resize
214
- // otherwise there is a sideeffect...
215
- sigma.off("afterRender", fnSyncMapWithSigma);
216
- var center = map.getCenter();
130
+ // Apply camera constraints. cameraPanBoundaries triggers cleanCameraState
131
+ // which uses graphToViewport — it needs the correct normalization.
132
+ sigma.setSetting("cameraPanBoundaries", {
133
+ boundaries: {
134
+ x: [-1, 2],
135
+ y: [0, 1]
136
+ }
137
+ });
217
138
 
218
- // Ask the map to resize
219
- map.once("resize", function () {
220
- // NB: resize can change the center of the map, and we want to keep it
221
- map.setCenter(center);
139
+ // Prevent zooming out beyond the world. The max ratio depends on the
140
+ // viewport aspect ratio: for a square graph, the Y range [0,1] fills the
141
+ // viewport height when ratio = min(width, height) / height.
142
+ function updateMaxCameraRatio() {
143
+ var _sigma$getDimensions = sigma.getDimensions(),
144
+ width = _sigma$getDimensions.width,
145
+ height = _sigma$getDimensions.height;
146
+ sigma.setSetting("maxCameraRatio", Math.min(width, height) / height);
147
+ }
148
+ updateMaxCameraRatio();
149
+
150
+ // Compute initial map bounds from sigma's current viewport, so the map's
151
+ // first render is already at the correct position
152
+ function getSigmaViewportBounds() {
153
+ var dims = sigma.getDimensions();
154
+ var graphBottomLeft = sigma.viewportToGraph({
155
+ x: 0,
156
+ y: dims.height
157
+ }, {
158
+ padding: 0
159
+ });
160
+ var graphTopRight = sigma.viewportToGraph({
161
+ x: dims.width,
162
+ y: 0
163
+ }, {
164
+ padding: 0
165
+ });
166
+ return new maplibreGl.LngLatBounds(graphToLatlng(graphBottomLeft), graphToLatlng(graphTopRight));
167
+ }
222
168
 
223
- // Map ref has changed, we need to update the graph coordinates
224
- updateGraphCoordinates(sigma.getGraph());
169
+ // Creating map container
170
+ var mapLayerName = "layer-maplibre";
171
+ var mapContainer = sigma.createLayer(mapLayerName, "div", {
172
+ style: {
173
+ position: "absolute",
174
+ inset: "0"
175
+ },
176
+ // 'stage' is the first sigma layer
177
+ beforeLayer: "stage"
178
+ });
179
+ sigma.getContainer().prepend(mapContainer);
225
180
 
226
- // Do the sync
227
- fnSyncSigmaWithMap();
181
+ // Initialize the map (non-interactive: sigma controls all interaction)
182
+ var map = new maplibreGl.Map(_objectSpread2(_objectSpread2({
183
+ container: mapContainer,
184
+ style: "https://demotiles.maplibre.org/style.json",
185
+ minPitch: 0,
186
+ maxPitch: 0
187
+ }, opts === null || opts === void 0 ? void 0 : opts.mapOptions), {}, {
188
+ interactive: false,
189
+ bounds: getSigmaViewportBounds()
190
+ }));
228
191
 
229
- // Re-enable the map sync with sigma in the next frame
230
- setTimeout(function () {
231
- fnSyncMapWithSigma();
232
- sigma.on("afterRender", fnSyncMapWithSigma);
233
- }, 0);
192
+ // Camera state memoization to skip redundant fitBounds calls
193
+ var lx = NaN,
194
+ ly = NaN,
195
+ lr = NaN,
196
+ la = NaN;
197
+
198
+ // Sync map bounds to match sigma's viewport
199
+ function syncMapFromSigma() {
200
+ var _sigma$getCamera$getS = sigma.getCamera().getState(),
201
+ x = _sigma$getCamera$getS.x,
202
+ y = _sigma$getCamera$getS.y,
203
+ ratio = _sigma$getCamera$getS.ratio,
204
+ angle = _sigma$getCamera$getS.angle;
205
+ if (x === lx && y === ly && ratio === lr && angle === la) return;
206
+ lx = x;
207
+ ly = y;
208
+ lr = ratio;
209
+ la = angle;
210
+ map.fitBounds(getSigmaViewportBounds(), {
211
+ duration: 0
234
212
  });
213
+ }
214
+
215
+ // When sigma is resized, resize the map, recompute zoom limit, and re-sync
216
+ function fnOnResize() {
235
217
  map.resize();
218
+ updateMaxCameraRatio();
219
+ lx = NaN;
220
+ syncMapFromSigma();
236
221
  }
237
222
 
238
223
  // Clean up function to remove everything
239
224
  function clean() {
240
225
  if (!isKilled) {
241
226
  isKilled = true;
242
- map.off("moveend", fnSyncSigmaWithMap);
243
227
  map.remove();
244
228
  sigma.killLayer(mapLayerName);
245
- sigma.off("afterRender", fnSyncMapWithSigma);
229
+ sigma.off("afterRender", syncMapFromSigma);
246
230
  sigma.off("resize", fnOnResize);
231
+ sigma.off("kill", clean);
247
232
 
248
233
  // Reset settings
249
234
  sigma.setSetting("stagePadding", prevSigmaSettings.stagePadding);
250
235
  sigma.setSetting("enableCameraRotation", prevSigmaSettings.enableCameraRotation);
236
+ sigma.setSetting("maxCameraRatio", prevSigmaSettings.maxCameraRatio);
237
+ sigma.setSetting("cameraPanBoundaries", prevSigmaSettings.cameraPanBoundaries);
238
+ sigma.setCustomBBox(prevCustomBBox);
251
239
  }
252
240
  }
253
241
 
254
- // Update the sigma graph for geospatial coords
255
- updateGraphCoordinates(sigma.getGraph());
256
-
257
- // When the map is ready
242
+ // When the map is ready, start syncing
258
243
  map.once("load", function () {
259
- // Compute sigma ratio bounds
260
- // /!\ must be done after the first map render
261
- map.once("moveend", function () {
262
- fnSyncSigmaWithMap();
263
- });
264
- // Do the first sync
265
- fnSyncMapWithSigma();
266
-
267
- // At each render of sigma, we do the map sync
268
- sigma.on("afterRender", fnSyncMapWithSigma);
269
- // Listen on resize
244
+ sigma.on("afterRender", syncMapFromSigma);
270
245
  sigma.on("resize", fnOnResize);
271
- // Do the cleanup
272
246
  sigma.on("kill", clean);
273
- // Keep sigma camera on the map
274
- map.on("moveend", fnSyncSigmaWithMap);
275
247
  });
276
248
  return {
277
249
  clean: clean,
@@ -2,6 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var utils = require('@sigma/utils');
5
6
  var maplibreGl = require('maplibre-gl');
6
7
 
7
8
  function _toPrimitive(t, r) {
@@ -54,20 +55,19 @@ function _objectSpread2(e) {
54
55
  /**
55
56
  * Given a geo point returns its graph coords.
56
57
  */
57
- function latlngToGraph(map, coord) {
58
+ function latlngToGraph(coord) {
58
59
  var data = maplibreGl.MercatorCoordinate.fromLngLat(coord);
59
60
  return {
60
61
  x: data.x,
61
- // Y are reversed between geo / sigma
62
- y: map.getContainer().clientHeight - data.y
62
+ y: 1 - data.y
63
63
  };
64
64
  }
65
65
 
66
66
  /**
67
- * Given a graph coords returns its lat/lng coords.
67
+ * Given graph coords returns its lat/lng coords.
68
68
  */
69
- function graphToLatlng(map, coords) {
70
- var mcoords = new maplibreGl.MercatorCoordinate(coords.x, map.getContainer().clientHeight - coords.y, 0);
69
+ function graphToLatlng(coords) {
70
+ var mcoords = new maplibreGl.MercatorCoordinate(coords.x, 1 - coords.y, 0);
71
71
  var data = mcoords.toLngLat();
72
72
  return {
73
73
  lat: data.lat,
@@ -75,59 +75,6 @@ function graphToLatlng(map, coords) {
75
75
  };
76
76
  }
77
77
 
78
- /**
79
- * BBOX sync : map to sigma
80
- */
81
- function syncSigmaWithMap(sigma, map) {
82
- // Compute sigma center
83
- var center = sigma.viewportToFramedGraph(sigma.graphToViewport(latlngToGraph(map, map.getCenter())));
84
-
85
- // Compute sigma ratio
86
- var mapBound = map.getBounds();
87
- var northEast = sigma.graphToViewport(latlngToGraph(map, mapBound.getNorthEast()));
88
- var southWest = sigma.graphToViewport(latlngToGraph(map, mapBound.getSouthWest()));
89
- var viewportBoundDimension = {
90
- width: Math.abs(northEast.x - southWest.x),
91
- height: Math.abs(northEast.y - southWest.y)
92
- };
93
- var viewportDim = sigma.getDimensions();
94
- var ratio = Math.min(viewportBoundDimension.width / viewportDim.width, viewportBoundDimension.height / viewportDim.height) * sigma.getCamera().getState().ratio;
95
- sigma.getCamera().setState(_objectSpread2(_objectSpread2({}, center), {}, {
96
- ratio: ratio
97
- }));
98
- }
99
-
100
- /**
101
- * BBOX sync : sigma to map
102
- */
103
- function syncMapWithSigma(sigma, map) {
104
- var viewportDimensions = sigma.getDimensions();
105
-
106
- // Graph BBox
107
- var graphBottomLeft = sigma.viewportToGraph({
108
- x: 0,
109
- y: viewportDimensions.height
110
- }, {
111
- padding: 0
112
- });
113
- var graphTopRight = sigma.viewportToGraph({
114
- x: viewportDimensions.width,
115
- y: 0
116
- }, {
117
- padding: 0
118
- });
119
-
120
- // Geo BBox
121
- var geoSouthWest = graphToLatlng(map, graphBottomLeft);
122
- var geoNorthEast = graphToLatlng(map, graphTopRight);
123
-
124
- // Set map BBox
125
- var bounds = new maplibreGl.LngLatBounds([geoSouthWest, geoNorthEast]);
126
- map.fitBounds(bounds, {
127
- duration: 0
128
- });
129
- }
130
-
131
78
  /**
132
79
  * On the graph, we store the 2D projection of the geographical lat/long.
133
80
  *
@@ -139,40 +86,26 @@ function bindMaplibreLayer(sigma, opts) {
139
86
  // Keeping data for the cleanup
140
87
  var isKilled = false;
141
88
  var prevSigmaSettings = sigma.getSettings();
142
-
143
- // Creating map container
144
- var mapLayerName = "layer-maplibre";
145
- var mapContainer = sigma.createLayer(mapLayerName, "div", {
146
- style: {
147
- position: "absolute",
148
- inset: "0"
149
- },
150
- // 'edges' is the first sigma layer
151
- beforeLayer: "edges"
152
- });
153
- sigma.getContainer().prepend(mapContainer);
154
-
155
- // Initialize the map
156
- var map = new maplibreGl.Map(_objectSpread2({
157
- container: mapContainer,
158
- style: "https://demotiles.maplibre.org/style.json",
159
- center: [0, 0],
160
- zoom: 1,
161
- minPitch: 0,
162
- maxPitch: 0
163
- }, (opts === null || opts === void 0 ? void 0 : opts.mapOptions) || {}));
89
+ var prevCustomBBox = sigma.getCustomBBox();
164
90
 
165
91
  // `stagePadding: 0` is mandatory, so the bbox of the map & Sigma is the same.
166
92
  sigma.setSetting("stagePadding", 0);
167
-
168
93
  // disable camera rotation
169
94
  sigma.setSetting("enableCameraRotation", false);
170
95
 
171
- // Function that change the given graph by generating the sigma x,y coords by taking the geo coordinates
172
- // and project them in the 2D space of the map
96
+ // Mercator graph coordinates are in [0,1]. Set customBBox to make
97
+ // normalization identity, and force a synchronous refresh so the
98
+ // normalization function is updated before camera constraints are applied.
99
+ sigma.setCustomBBox({
100
+ x: [0, 1],
101
+ y: [0, 1]
102
+ });
103
+
104
+ // Function that updates graph node positions from geo coordinates to
105
+ // Mercator [0,1] space
173
106
  function updateGraphCoordinates(graph) {
174
107
  graph.updateEachNodeAttributes(function (_node, attrs) {
175
- var coords = latlngToGraph(map, opts !== null && opts !== void 0 && opts.getNodeLatLng ? opts.getNodeLatLng(attrs) : {
108
+ var coords = latlngToGraph(opts !== null && opts !== void 0 && opts.getNodeLatLng ? opts.getNodeLatLng(attrs) : {
176
109
  lat: attrs.lat,
177
110
  lng: attrs.lng
178
111
  });
@@ -183,95 +116,134 @@ function bindMaplibreLayer(sigma, opts) {
183
116
  });
184
117
  }
185
118
 
186
- // Function that sync the map with sigma
187
- function fnSyncMapWithSigma() {
188
- syncMapWithSigma(sigma, map);
189
- }
119
+ // Update graph coordinates and refresh sigma so normalization is ready
120
+ updateGraphCoordinates(sigma.getGraph());
121
+ sigma.refresh();
190
122
 
191
- // Function that sync sigma with map if it's needed
192
- function fnSyncSigmaWithMap() {
193
- if (!sigma.getCamera().isAnimated() && !map.isMoving()) {
194
- // Check that sigma & map are already in sync
195
- var southWest = graphToLatlng(map, sigma.viewportToGraph({
196
- x: 0,
197
- y: sigma.getDimensions().height
198
- }));
199
- var northEast = graphToLatlng(map, sigma.viewportToGraph({
200
- x: sigma.getDimensions().width,
201
- y: 0
202
- }));
203
- var diff = Math.max(map.getBounds().getSouthWest().distanceTo(new maplibreGl.LngLat(southWest.lng, southWest.lat)), map.getBounds().getNorthEast().distanceTo(new maplibreGl.LngLat(northEast.lng, northEast.lat)));
204
- if (diff > 1) {
205
- syncSigmaWithMap(sigma, map);
206
- }
207
- }
123
+ // Now that normalization is correct, fit the camera to the graph's
124
+ // Mercator extent so the initial view frames the nodes.
125
+ var graph = sigma.getGraph();
126
+ if (graph.order) {
127
+ sigma.getCamera().setState(utils.getCameraStateToFitViewportToNodes(sigma, graph.nodes()));
208
128
  }
209
129
 
210
- // When sigma is resize, we need to update the graph coordinate (the ref has changed)
211
- // and recompute the zoom bounds
212
- function fnOnResize() {
213
- // Avoid sync map with sigma while we do the resize
214
- // otherwise there is a sideeffect...
215
- sigma.off("afterRender", fnSyncMapWithSigma);
216
- var center = map.getCenter();
130
+ // Apply camera constraints. cameraPanBoundaries triggers cleanCameraState
131
+ // which uses graphToViewport — it needs the correct normalization.
132
+ sigma.setSetting("cameraPanBoundaries", {
133
+ boundaries: {
134
+ x: [-1, 2],
135
+ y: [0, 1]
136
+ }
137
+ });
217
138
 
218
- // Ask the map to resize
219
- map.once("resize", function () {
220
- // NB: resize can change the center of the map, and we want to keep it
221
- map.setCenter(center);
139
+ // Prevent zooming out beyond the world. The max ratio depends on the
140
+ // viewport aspect ratio: for a square graph, the Y range [0,1] fills the
141
+ // viewport height when ratio = min(width, height) / height.
142
+ function updateMaxCameraRatio() {
143
+ var _sigma$getDimensions = sigma.getDimensions(),
144
+ width = _sigma$getDimensions.width,
145
+ height = _sigma$getDimensions.height;
146
+ sigma.setSetting("maxCameraRatio", Math.min(width, height) / height);
147
+ }
148
+ updateMaxCameraRatio();
149
+
150
+ // Compute initial map bounds from sigma's current viewport, so the map's
151
+ // first render is already at the correct position
152
+ function getSigmaViewportBounds() {
153
+ var dims = sigma.getDimensions();
154
+ var graphBottomLeft = sigma.viewportToGraph({
155
+ x: 0,
156
+ y: dims.height
157
+ }, {
158
+ padding: 0
159
+ });
160
+ var graphTopRight = sigma.viewportToGraph({
161
+ x: dims.width,
162
+ y: 0
163
+ }, {
164
+ padding: 0
165
+ });
166
+ return new maplibreGl.LngLatBounds(graphToLatlng(graphBottomLeft), graphToLatlng(graphTopRight));
167
+ }
222
168
 
223
- // Map ref has changed, we need to update the graph coordinates
224
- updateGraphCoordinates(sigma.getGraph());
169
+ // Creating map container
170
+ var mapLayerName = "layer-maplibre";
171
+ var mapContainer = sigma.createLayer(mapLayerName, "div", {
172
+ style: {
173
+ position: "absolute",
174
+ inset: "0"
175
+ },
176
+ // 'stage' is the first sigma layer
177
+ beforeLayer: "stage"
178
+ });
179
+ sigma.getContainer().prepend(mapContainer);
225
180
 
226
- // Do the sync
227
- fnSyncSigmaWithMap();
181
+ // Initialize the map (non-interactive: sigma controls all interaction)
182
+ var map = new maplibreGl.Map(_objectSpread2(_objectSpread2({
183
+ container: mapContainer,
184
+ style: "https://demotiles.maplibre.org/style.json",
185
+ minPitch: 0,
186
+ maxPitch: 0
187
+ }, opts === null || opts === void 0 ? void 0 : opts.mapOptions), {}, {
188
+ interactive: false,
189
+ bounds: getSigmaViewportBounds()
190
+ }));
228
191
 
229
- // Re-enable the map sync with sigma in the next frame
230
- setTimeout(function () {
231
- fnSyncMapWithSigma();
232
- sigma.on("afterRender", fnSyncMapWithSigma);
233
- }, 0);
192
+ // Camera state memoization to skip redundant fitBounds calls
193
+ var lx = NaN,
194
+ ly = NaN,
195
+ lr = NaN,
196
+ la = NaN;
197
+
198
+ // Sync map bounds to match sigma's viewport
199
+ function syncMapFromSigma() {
200
+ var _sigma$getCamera$getS = sigma.getCamera().getState(),
201
+ x = _sigma$getCamera$getS.x,
202
+ y = _sigma$getCamera$getS.y,
203
+ ratio = _sigma$getCamera$getS.ratio,
204
+ angle = _sigma$getCamera$getS.angle;
205
+ if (x === lx && y === ly && ratio === lr && angle === la) return;
206
+ lx = x;
207
+ ly = y;
208
+ lr = ratio;
209
+ la = angle;
210
+ map.fitBounds(getSigmaViewportBounds(), {
211
+ duration: 0
234
212
  });
213
+ }
214
+
215
+ // When sigma is resized, resize the map, recompute zoom limit, and re-sync
216
+ function fnOnResize() {
235
217
  map.resize();
218
+ updateMaxCameraRatio();
219
+ lx = NaN;
220
+ syncMapFromSigma();
236
221
  }
237
222
 
238
223
  // Clean up function to remove everything
239
224
  function clean() {
240
225
  if (!isKilled) {
241
226
  isKilled = true;
242
- map.off("moveend", fnSyncSigmaWithMap);
243
227
  map.remove();
244
228
  sigma.killLayer(mapLayerName);
245
- sigma.off("afterRender", fnSyncMapWithSigma);
229
+ sigma.off("afterRender", syncMapFromSigma);
246
230
  sigma.off("resize", fnOnResize);
231
+ sigma.off("kill", clean);
247
232
 
248
233
  // Reset settings
249
234
  sigma.setSetting("stagePadding", prevSigmaSettings.stagePadding);
250
235
  sigma.setSetting("enableCameraRotation", prevSigmaSettings.enableCameraRotation);
236
+ sigma.setSetting("maxCameraRatio", prevSigmaSettings.maxCameraRatio);
237
+ sigma.setSetting("cameraPanBoundaries", prevSigmaSettings.cameraPanBoundaries);
238
+ sigma.setCustomBBox(prevCustomBBox);
251
239
  }
252
240
  }
253
241
 
254
- // Update the sigma graph for geospatial coords
255
- updateGraphCoordinates(sigma.getGraph());
256
-
257
- // When the map is ready
242
+ // When the map is ready, start syncing
258
243
  map.once("load", function () {
259
- // Compute sigma ratio bounds
260
- // /!\ must be done after the first map render
261
- map.once("moveend", function () {
262
- fnSyncSigmaWithMap();
263
- });
264
- // Do the first sync
265
- fnSyncMapWithSigma();
266
-
267
- // At each render of sigma, we do the map sync
268
- sigma.on("afterRender", fnSyncMapWithSigma);
269
- // Listen on resize
244
+ sigma.on("afterRender", syncMapFromSigma);
270
245
  sigma.on("resize", fnOnResize);
271
- // Do the cleanup
272
246
  sigma.on("kill", clean);
273
- // Keep sigma camera on the map
274
- map.on("moveend", fnSyncSigmaWithMap);
275
247
  });
276
248
  return {
277
249
  clean: clean,
@@ -1,4 +1,5 @@
1
- import { MercatorCoordinate, LngLatBounds, Map, LngLat } from 'maplibre-gl';
1
+ import { getCameraStateToFitViewportToNodes } from '@sigma/utils';
2
+ import { MercatorCoordinate, Map, LngLatBounds } from 'maplibre-gl';
2
3
 
3
4
  function _toPrimitive(t, r) {
4
5
  if ("object" != typeof t || !t) return t;
@@ -50,20 +51,19 @@ function _objectSpread2(e) {
50
51
  /**
51
52
  * Given a geo point returns its graph coords.
52
53
  */
53
- function latlngToGraph(map, coord) {
54
+ function latlngToGraph(coord) {
54
55
  var data = MercatorCoordinate.fromLngLat(coord);
55
56
  return {
56
57
  x: data.x,
57
- // Y are reversed between geo / sigma
58
- y: map.getContainer().clientHeight - data.y
58
+ y: 1 - data.y
59
59
  };
60
60
  }
61
61
 
62
62
  /**
63
- * Given a graph coords returns its lat/lng coords.
63
+ * Given graph coords returns its lat/lng coords.
64
64
  */
65
- function graphToLatlng(map, coords) {
66
- var mcoords = new MercatorCoordinate(coords.x, map.getContainer().clientHeight - coords.y, 0);
65
+ function graphToLatlng(coords) {
66
+ var mcoords = new MercatorCoordinate(coords.x, 1 - coords.y, 0);
67
67
  var data = mcoords.toLngLat();
68
68
  return {
69
69
  lat: data.lat,
@@ -71,59 +71,6 @@ function graphToLatlng(map, coords) {
71
71
  };
72
72
  }
73
73
 
74
- /**
75
- * BBOX sync : map to sigma
76
- */
77
- function syncSigmaWithMap(sigma, map) {
78
- // Compute sigma center
79
- var center = sigma.viewportToFramedGraph(sigma.graphToViewport(latlngToGraph(map, map.getCenter())));
80
-
81
- // Compute sigma ratio
82
- var mapBound = map.getBounds();
83
- var northEast = sigma.graphToViewport(latlngToGraph(map, mapBound.getNorthEast()));
84
- var southWest = sigma.graphToViewport(latlngToGraph(map, mapBound.getSouthWest()));
85
- var viewportBoundDimension = {
86
- width: Math.abs(northEast.x - southWest.x),
87
- height: Math.abs(northEast.y - southWest.y)
88
- };
89
- var viewportDim = sigma.getDimensions();
90
- var ratio = Math.min(viewportBoundDimension.width / viewportDim.width, viewportBoundDimension.height / viewportDim.height) * sigma.getCamera().getState().ratio;
91
- sigma.getCamera().setState(_objectSpread2(_objectSpread2({}, center), {}, {
92
- ratio: ratio
93
- }));
94
- }
95
-
96
- /**
97
- * BBOX sync : sigma to map
98
- */
99
- function syncMapWithSigma(sigma, map) {
100
- var viewportDimensions = sigma.getDimensions();
101
-
102
- // Graph BBox
103
- var graphBottomLeft = sigma.viewportToGraph({
104
- x: 0,
105
- y: viewportDimensions.height
106
- }, {
107
- padding: 0
108
- });
109
- var graphTopRight = sigma.viewportToGraph({
110
- x: viewportDimensions.width,
111
- y: 0
112
- }, {
113
- padding: 0
114
- });
115
-
116
- // Geo BBox
117
- var geoSouthWest = graphToLatlng(map, graphBottomLeft);
118
- var geoNorthEast = graphToLatlng(map, graphTopRight);
119
-
120
- // Set map BBox
121
- var bounds = new LngLatBounds([geoSouthWest, geoNorthEast]);
122
- map.fitBounds(bounds, {
123
- duration: 0
124
- });
125
- }
126
-
127
74
  /**
128
75
  * On the graph, we store the 2D projection of the geographical lat/long.
129
76
  *
@@ -135,40 +82,26 @@ function bindMaplibreLayer(sigma, opts) {
135
82
  // Keeping data for the cleanup
136
83
  var isKilled = false;
137
84
  var prevSigmaSettings = sigma.getSettings();
138
-
139
- // Creating map container
140
- var mapLayerName = "layer-maplibre";
141
- var mapContainer = sigma.createLayer(mapLayerName, "div", {
142
- style: {
143
- position: "absolute",
144
- inset: "0"
145
- },
146
- // 'edges' is the first sigma layer
147
- beforeLayer: "edges"
148
- });
149
- sigma.getContainer().prepend(mapContainer);
150
-
151
- // Initialize the map
152
- var map = new Map(_objectSpread2({
153
- container: mapContainer,
154
- style: "https://demotiles.maplibre.org/style.json",
155
- center: [0, 0],
156
- zoom: 1,
157
- minPitch: 0,
158
- maxPitch: 0
159
- }, (opts === null || opts === void 0 ? void 0 : opts.mapOptions) || {}));
85
+ var prevCustomBBox = sigma.getCustomBBox();
160
86
 
161
87
  // `stagePadding: 0` is mandatory, so the bbox of the map & Sigma is the same.
162
88
  sigma.setSetting("stagePadding", 0);
163
-
164
89
  // disable camera rotation
165
90
  sigma.setSetting("enableCameraRotation", false);
166
91
 
167
- // Function that change the given graph by generating the sigma x,y coords by taking the geo coordinates
168
- // and project them in the 2D space of the map
92
+ // Mercator graph coordinates are in [0,1]. Set customBBox to make
93
+ // normalization identity, and force a synchronous refresh so the
94
+ // normalization function is updated before camera constraints are applied.
95
+ sigma.setCustomBBox({
96
+ x: [0, 1],
97
+ y: [0, 1]
98
+ });
99
+
100
+ // Function that updates graph node positions from geo coordinates to
101
+ // Mercator [0,1] space
169
102
  function updateGraphCoordinates(graph) {
170
103
  graph.updateEachNodeAttributes(function (_node, attrs) {
171
- var coords = latlngToGraph(map, opts !== null && opts !== void 0 && opts.getNodeLatLng ? opts.getNodeLatLng(attrs) : {
104
+ var coords = latlngToGraph(opts !== null && opts !== void 0 && opts.getNodeLatLng ? opts.getNodeLatLng(attrs) : {
172
105
  lat: attrs.lat,
173
106
  lng: attrs.lng
174
107
  });
@@ -179,95 +112,134 @@ function bindMaplibreLayer(sigma, opts) {
179
112
  });
180
113
  }
181
114
 
182
- // Function that sync the map with sigma
183
- function fnSyncMapWithSigma() {
184
- syncMapWithSigma(sigma, map);
185
- }
115
+ // Update graph coordinates and refresh sigma so normalization is ready
116
+ updateGraphCoordinates(sigma.getGraph());
117
+ sigma.refresh();
186
118
 
187
- // Function that sync sigma with map if it's needed
188
- function fnSyncSigmaWithMap() {
189
- if (!sigma.getCamera().isAnimated() && !map.isMoving()) {
190
- // Check that sigma & map are already in sync
191
- var southWest = graphToLatlng(map, sigma.viewportToGraph({
192
- x: 0,
193
- y: sigma.getDimensions().height
194
- }));
195
- var northEast = graphToLatlng(map, sigma.viewportToGraph({
196
- x: sigma.getDimensions().width,
197
- y: 0
198
- }));
199
- var diff = Math.max(map.getBounds().getSouthWest().distanceTo(new LngLat(southWest.lng, southWest.lat)), map.getBounds().getNorthEast().distanceTo(new LngLat(northEast.lng, northEast.lat)));
200
- if (diff > 1) {
201
- syncSigmaWithMap(sigma, map);
202
- }
203
- }
119
+ // Now that normalization is correct, fit the camera to the graph's
120
+ // Mercator extent so the initial view frames the nodes.
121
+ var graph = sigma.getGraph();
122
+ if (graph.order) {
123
+ sigma.getCamera().setState(getCameraStateToFitViewportToNodes(sigma, graph.nodes()));
204
124
  }
205
125
 
206
- // When sigma is resize, we need to update the graph coordinate (the ref has changed)
207
- // and recompute the zoom bounds
208
- function fnOnResize() {
209
- // Avoid sync map with sigma while we do the resize
210
- // otherwise there is a sideeffect...
211
- sigma.off("afterRender", fnSyncMapWithSigma);
212
- var center = map.getCenter();
126
+ // Apply camera constraints. cameraPanBoundaries triggers cleanCameraState
127
+ // which uses graphToViewport — it needs the correct normalization.
128
+ sigma.setSetting("cameraPanBoundaries", {
129
+ boundaries: {
130
+ x: [-1, 2],
131
+ y: [0, 1]
132
+ }
133
+ });
213
134
 
214
- // Ask the map to resize
215
- map.once("resize", function () {
216
- // NB: resize can change the center of the map, and we want to keep it
217
- map.setCenter(center);
135
+ // Prevent zooming out beyond the world. The max ratio depends on the
136
+ // viewport aspect ratio: for a square graph, the Y range [0,1] fills the
137
+ // viewport height when ratio = min(width, height) / height.
138
+ function updateMaxCameraRatio() {
139
+ var _sigma$getDimensions = sigma.getDimensions(),
140
+ width = _sigma$getDimensions.width,
141
+ height = _sigma$getDimensions.height;
142
+ sigma.setSetting("maxCameraRatio", Math.min(width, height) / height);
143
+ }
144
+ updateMaxCameraRatio();
145
+
146
+ // Compute initial map bounds from sigma's current viewport, so the map's
147
+ // first render is already at the correct position
148
+ function getSigmaViewportBounds() {
149
+ var dims = sigma.getDimensions();
150
+ var graphBottomLeft = sigma.viewportToGraph({
151
+ x: 0,
152
+ y: dims.height
153
+ }, {
154
+ padding: 0
155
+ });
156
+ var graphTopRight = sigma.viewportToGraph({
157
+ x: dims.width,
158
+ y: 0
159
+ }, {
160
+ padding: 0
161
+ });
162
+ return new LngLatBounds(graphToLatlng(graphBottomLeft), graphToLatlng(graphTopRight));
163
+ }
218
164
 
219
- // Map ref has changed, we need to update the graph coordinates
220
- updateGraphCoordinates(sigma.getGraph());
165
+ // Creating map container
166
+ var mapLayerName = "layer-maplibre";
167
+ var mapContainer = sigma.createLayer(mapLayerName, "div", {
168
+ style: {
169
+ position: "absolute",
170
+ inset: "0"
171
+ },
172
+ // 'stage' is the first sigma layer
173
+ beforeLayer: "stage"
174
+ });
175
+ sigma.getContainer().prepend(mapContainer);
221
176
 
222
- // Do the sync
223
- fnSyncSigmaWithMap();
177
+ // Initialize the map (non-interactive: sigma controls all interaction)
178
+ var map = new Map(_objectSpread2(_objectSpread2({
179
+ container: mapContainer,
180
+ style: "https://demotiles.maplibre.org/style.json",
181
+ minPitch: 0,
182
+ maxPitch: 0
183
+ }, opts === null || opts === void 0 ? void 0 : opts.mapOptions), {}, {
184
+ interactive: false,
185
+ bounds: getSigmaViewportBounds()
186
+ }));
224
187
 
225
- // Re-enable the map sync with sigma in the next frame
226
- setTimeout(function () {
227
- fnSyncMapWithSigma();
228
- sigma.on("afterRender", fnSyncMapWithSigma);
229
- }, 0);
188
+ // Camera state memoization to skip redundant fitBounds calls
189
+ var lx = NaN,
190
+ ly = NaN,
191
+ lr = NaN,
192
+ la = NaN;
193
+
194
+ // Sync map bounds to match sigma's viewport
195
+ function syncMapFromSigma() {
196
+ var _sigma$getCamera$getS = sigma.getCamera().getState(),
197
+ x = _sigma$getCamera$getS.x,
198
+ y = _sigma$getCamera$getS.y,
199
+ ratio = _sigma$getCamera$getS.ratio,
200
+ angle = _sigma$getCamera$getS.angle;
201
+ if (x === lx && y === ly && ratio === lr && angle === la) return;
202
+ lx = x;
203
+ ly = y;
204
+ lr = ratio;
205
+ la = angle;
206
+ map.fitBounds(getSigmaViewportBounds(), {
207
+ duration: 0
230
208
  });
209
+ }
210
+
211
+ // When sigma is resized, resize the map, recompute zoom limit, and re-sync
212
+ function fnOnResize() {
231
213
  map.resize();
214
+ updateMaxCameraRatio();
215
+ lx = NaN;
216
+ syncMapFromSigma();
232
217
  }
233
218
 
234
219
  // Clean up function to remove everything
235
220
  function clean() {
236
221
  if (!isKilled) {
237
222
  isKilled = true;
238
- map.off("moveend", fnSyncSigmaWithMap);
239
223
  map.remove();
240
224
  sigma.killLayer(mapLayerName);
241
- sigma.off("afterRender", fnSyncMapWithSigma);
225
+ sigma.off("afterRender", syncMapFromSigma);
242
226
  sigma.off("resize", fnOnResize);
227
+ sigma.off("kill", clean);
243
228
 
244
229
  // Reset settings
245
230
  sigma.setSetting("stagePadding", prevSigmaSettings.stagePadding);
246
231
  sigma.setSetting("enableCameraRotation", prevSigmaSettings.enableCameraRotation);
232
+ sigma.setSetting("maxCameraRatio", prevSigmaSettings.maxCameraRatio);
233
+ sigma.setSetting("cameraPanBoundaries", prevSigmaSettings.cameraPanBoundaries);
234
+ sigma.setCustomBBox(prevCustomBBox);
247
235
  }
248
236
  }
249
237
 
250
- // Update the sigma graph for geospatial coords
251
- updateGraphCoordinates(sigma.getGraph());
252
-
253
- // When the map is ready
238
+ // When the map is ready, start syncing
254
239
  map.once("load", function () {
255
- // Compute sigma ratio bounds
256
- // /!\ must be done after the first map render
257
- map.once("moveend", function () {
258
- fnSyncSigmaWithMap();
259
- });
260
- // Do the first sync
261
- fnSyncMapWithSigma();
262
-
263
- // At each render of sigma, we do the map sync
264
- sigma.on("afterRender", fnSyncMapWithSigma);
265
- // Listen on resize
240
+ sigma.on("afterRender", syncMapFromSigma);
266
241
  sigma.on("resize", fnOnResize);
267
- // Do the cleanup
268
242
  sigma.on("kill", clean);
269
- // Keep sigma camera on the map
270
- map.on("moveend", fnSyncSigmaWithMap);
271
243
  });
272
244
  return {
273
245
  clean: clean,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigma/layer-maplibre",
3
- "version": "3.0.0",
3
+ "version": "4.0.0-alpha.1",
4
4
  "description": "A plugin to set a geographical map in background",
5
5
  "main": "dist/sigma-layer-maplibre.cjs.js",
6
6
  "module": "dist/sigma-layer-maplibre.esm.js",
@@ -28,14 +28,20 @@
28
28
  }
29
29
  ],
30
30
  "license": "MIT",
31
+ "scripts": {
32
+ "test": "vitest run"
33
+ },
31
34
  "preconstruct": {
32
35
  "entrypoints": [
33
36
  "index.ts"
34
37
  ]
35
38
  },
39
+ "dependencies": {
40
+ "@sigma/utils": "^4.0.0-alpha.1"
41
+ },
36
42
  "peerDependencies": {
37
43
  "maplibre-gl": "^4.5.0",
38
- "sigma": ">=3.0.0-beta.10"
44
+ "sigma": ">=4.0.0-alpha.0"
39
45
  },
40
46
  "exports": {
41
47
  ".": {
@@ -45,5 +51,5 @@
45
51
  },
46
52
  "./package.json": "./package.json"
47
53
  },
48
- "gitHead": "f0187cb13da41310bf375caf7105db2f18bc89d2"
54
+ "gitHead": "629d2d5400b721c001f4301a244f2f4f74565961"
49
55
  }