gridviz 3.0.18 → 3.0.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gridviz",
3
- "version": "3.0.18",
3
+ "version": "3.0.19",
4
4
  "description": "Visualization tool for gridded statistics",
5
5
  "keywords": [
6
6
  "statistics",
@@ -78,54 +78,24 @@ export class GeoCanvas {
78
78
  this.extGeo = { xMin: NaN, xMax: NaN, yMin: NaN, yMax: NaN }
79
79
  this.updateExtentGeo()
80
80
 
81
- //rely on d3 zoom for pan/zoom
81
+ //rely on d3 for zoom
82
82
  if (!opts.disableZoom) {
83
83
  let tP = zoomIdentity
84
+ // @ts-ignore
85
+ let debounceTimeout = null // Add a debounce timeout variable
84
86
  const z = d3zoom()
85
- //to make the zooming a bit faster
87
+ // to make the zooming a bit faster
86
88
  .wheelDelta((e) => -e.deltaY * (e.deltaMode === 1 ? 0.07 : e.deltaMode ? 1 : 0.004))
87
89
  .on('zoom', (e) => {
88
90
  const t = e.transform
89
- const f = tP.k / t.k
90
-
91
- // Get container's bounding rect to adjust for offsets
92
- const containerRect = this.canvas.getBoundingClientRect()
93
-
94
- // Adjust for container's offset and zoom center
95
- const offsetX = e.sourceEvent.offsetX - containerRect.left
96
- const offsetY = e.sourceEvent.offsetY - containerRect.top
97
-
98
- if (f === 1) {
99
- // Pan logic
91
+ const zoomFactor = tP.k / t.k
92
+ if (zoomFactor == 1) {
93
+ //pan
100
94
  const dx = tP.x - t.x
101
95
  const dy = tP.y - t.y
102
96
  this.pan(dx * this.view.z, -dy * this.view.z)
103
97
  } else {
104
- const se = e.sourceEvent
105
-
106
- if (se instanceof WheelEvent) {
107
- // Zoom at mouse position, adjusted by container offset
108
- this.zoom(f, this.pixToGeoX(offsetX), this.pixToGeoY(offsetY))
109
- } else if (se instanceof TouchEvent) {
110
- if (!se.targetTouches.length) return
111
-
112
- // Compute average position of the touches
113
- let tx = 0,
114
- ty = 0
115
- for (let tt of se.targetTouches) {
116
- tx += tt.clientX
117
- ty += tt.clientY
118
- }
119
- tx /= se.targetTouches.length
120
- ty /= se.targetTouches.length
121
-
122
- // Adjust for container's offset
123
- tx -= containerRect.left
124
- ty -= containerRect.top
125
-
126
- // Zoom at the average touch position
127
- this.zoom(f, this.pixToGeoX(tx), this.pixToGeoY(ty))
128
- }
98
+ handleZoom(e, zoomFactor)
129
99
  }
130
100
  tP = t
131
101
 
@@ -144,13 +114,48 @@ export class GeoCanvas {
144
114
  if (this.onZoomStartFun) this.onZoomStartFun(e)
145
115
  })
146
116
  .on('end', (e) => {
147
- // end of zoom event
117
+ // end of pan/zoom event
148
118
  this.redraw()
149
119
  this.canvasSave = { c: null, dx: 0, dy: 0, f: 1 }
150
120
 
151
121
  if (this.onZoomEndFun) this.onZoomEndFun(e)
152
122
  })
123
+ // @ts-ignore
153
124
  z(select(this.canvas))
125
+
126
+ const handleZoom = (event, zoomFactor) => {
127
+ // cancel ongoing data requests
128
+ this.cancelCurrentRequests()
129
+ const se = event.sourceEvent
130
+
131
+ if (se instanceof WheelEvent) {
132
+ //zoom at the mouse position
133
+ this.zoom(
134
+ zoomFactor,
135
+ // @ts-ignore
136
+ this.pixToGeoX(se.offsetX),
137
+ // @ts-ignore
138
+ this.pixToGeoY(se.offsetY)
139
+ )
140
+ } else if (se instanceof TouchEvent) {
141
+ //compute average position of the touches
142
+ let tx = 0,
143
+ ty = 0
144
+ for (let tt of se.targetTouches) {
145
+ tx += tt.clientX
146
+ ty += tt.clientY
147
+ }
148
+ tx /= se.targetTouches.length
149
+ ty /= se.targetTouches.length
150
+
151
+ // Adjust for container's offset
152
+ // tx -= containerRect.left
153
+ // ty -= containerRect.top
154
+
155
+ //zoom at this average position
156
+ this.zoom(zoomFactor, this.pixToGeoX(tx), this.pixToGeoY(ty))
157
+ }
158
+ }
154
159
  }
155
160
 
156
161
  //center extent
@@ -225,6 +230,11 @@ export class GeoCanvas {
225
230
  throw new Error('Method redraw not implemented.')
226
231
  }
227
232
 
233
+ /** When the zoom level changes, ensures that any ongoing requests are aborted before new ones are initiated. */
234
+ cancelCurrentRequests() {
235
+ throw new Error('Method cancelCurrentRequests not implemented.')
236
+ }
237
+
228
238
  /**
229
239
  * Clear. To be used before a redraw for example.
230
240
  * @param {string} color
@@ -320,6 +330,7 @@ export class GeoCanvas {
320
330
  this.canvasSave.f /= f
321
331
  this.canvasSave.dx = this.geoToPixX(xGeo) * (1 - this.canvasSave.f)
322
332
  this.canvasSave.dy = this.geoToPixY(yGeo) * (1 - this.canvasSave.f)
333
+ this.clear(this.backgroundColor)
323
334
  this.offscreenCtx.drawImage(
324
335
  this.canvasSave.c,
325
336
  this.canvasSave.dx,
package/src/core/Map.js CHANGED
@@ -58,6 +58,19 @@ export class Map {
58
58
  this.geoCanvas.redraw = () => {
59
59
  this.redraw()
60
60
  }
61
+ this.geoCanvas.cancelCurrentRequests = () => {
62
+ // when the zoom level changes, avoid drawing outdated tiles, and ensure that requests are properly aborted when necessary
63
+ for (const layer of this.layers) {
64
+ //multires
65
+ if (layer.dataset?.datasets) {
66
+ for (const dataset of layer.dataset?.datasets) {
67
+ if (dataset?.cancelCurrentRequests) dataset.cancelCurrentRequests()
68
+ }
69
+ }
70
+ //single res
71
+ if (layer.dataset?.cancelCurrentRequests) layer.dataset?.cancelCurrentRequests()
72
+ }
73
+ }
61
74
 
62
75
  // legend div
63
76
  this.legend = opts.legendContainer
@@ -69,6 +69,7 @@ export class Tooltip {
69
69
  this.tooltip.style('pointer-events', 'none')
70
70
  this.tooltip.style('opacity', '0')
71
71
  this.tooltip.style('text-wrap', 'nowrap')
72
+ this.tooltip.style('z-index', 99999999) // important for leaflet-gridviz etc
72
73
 
73
74
  // these placeholders are needed to prevent an infinite DOM resizeObserver loop:
74
75
  this.tooltip.style('left', '0')
@@ -67,8 +67,7 @@ export class TiledGrid extends Dataset {
67
67
  this.infoLoadingStatus = 'failed'
68
68
  }
69
69
  })()
70
- } else if (this.infoLoadingStatus === 'loaded' || this.infoLoadingStatus === 'failed')
71
- this.map.redraw()
70
+ } else if (this.infoLoadingStatus === 'loaded' || this.infoLoadingStatus === 'failed') this.map.redraw()
72
71
  return this
73
72
  }
74
73
 
@@ -106,6 +105,10 @@ export class TiledGrid extends Dataset {
106
105
  async getData(extGeo) {
107
106
  if (!this.info) return this
108
107
 
108
+ // Create an AbortController for the current data request
109
+ this.abortController = new AbortController()
110
+ const signal = this.abortController.signal
111
+
109
112
  // Get the tiling envelope and check bounds
110
113
  const tb = this.getTilingEnvelope(extGeo)
111
114
  if (!tb) return this
@@ -117,10 +120,8 @@ export class TiledGrid extends Dataset {
117
120
  const yMin = Math.max(tb.yMin, gbYMin)
118
121
  const yMax = Math.min(tb.yMax, gbYMax)
119
122
 
120
- // Count the total number of tiles
121
123
  const totalTiles = (xMax - xMin + 1) * (yMax - yMin + 1)
122
-
123
- let processedTiles = 0 // Counter for loaded tiles
124
+ let processedTiles = 0
124
125
  const tilePromises = []
125
126
 
126
127
  // Iterate over tiles within bounds
@@ -128,8 +129,8 @@ export class TiledGrid extends Dataset {
128
129
  for (let yT = yMin; yT <= yMax; yT++) {
129
130
  if (!this.cache[xT]) this.cache[xT] = {}
130
131
 
131
- // Skip already loaded or loading tiles
132
- if (this.cache[xT][yT]) {
132
+ // Skip already loaded tiles or retry failed ones
133
+ if (this.cache[xT][yT] && this.cache[xT][yT] !== 'failed') {
133
134
  ++processedTiles
134
135
  continue
135
136
  }
@@ -137,21 +138,16 @@ export class TiledGrid extends Dataset {
137
138
  // Mark tile as loading
138
139
  this.cache[xT][yT] = 'loading'
139
140
 
140
- // Prepare the tile request and store it in tilePromises
141
141
  tilePromises.push(
142
- this.loadTile(xT, yT)
142
+ this.loadTile(xT, yT, signal)
143
143
  .then((tile) => {
144
- // Before we increment processedTiles, check if it's the last tile
145
- const isLastTile = ++processedTiles === totalTiles
146
-
147
- // Store the tile in the cache
148
144
  this.cache[xT][yT] = tile
149
145
 
150
- // Pass the isLastTile argument to checkAndRedraw
146
+ // Check if this is the last tile
147
+ const isLastTile = ++processedTiles === totalTiles
151
148
  this.checkAndRedraw(tile, isLastTile)
152
149
  })
153
150
  .catch(() => {
154
- // Mark as failed but allow processing to continue
155
151
  this.cache[xT][yT] = 'failed'
156
152
  ++processedTiles
157
153
  })
@@ -159,30 +155,47 @@ export class TiledGrid extends Dataset {
159
155
  }
160
156
  }
161
157
 
162
- // Handle all tile promises, including failures
163
158
  await Promise.allSettled(tilePromises)
164
159
  return this
165
160
  }
166
161
 
167
- async loadTile(xT, yT) {
162
+ /**
163
+ * Load a tile.
164
+ *
165
+ * @param {number} xT
166
+ * @param {number} yT
167
+ * @param {AbortSignal} signal
168
+ * @returns {Promise<any>}
169
+ */
170
+ async loadTile(xT, yT, signal) {
168
171
  try {
169
- const data = await csv(`${this.url}${xT}/${yT}.csv`)
172
+ const data = await csv(`${this.url}${xT}/${yT}.csv`, { signal })
170
173
 
171
- // Preprocess or filter the data if needed
172
174
  const cells = this.preprocess ? data.filter((cell) => this.preprocess(cell) !== false) : data
173
175
 
174
176
  if (!this.info) throw new Error('Tile info unknown')
175
177
 
176
178
  return getGridTile(cells, xT, yT, this.info)
177
179
  } catch (error) {
178
- console.warn(`Failed to load tile ${xT}, ${yT}:`, error)
180
+ if (error.name === 'AbortError') {
181
+ console.warn(`Tile request for ${xT}, ${yT} was aborted.`)
182
+ }
179
183
  throw error
180
184
  }
181
185
  }
182
186
 
187
+ /**
188
+ * Cancel ongoing data requests when zoom level changes.
189
+ */
190
+ cancelCurrentRequests() {
191
+ if (this.abortController) {
192
+ this.abortController.abort()
193
+ }
194
+ }
195
+
183
196
  checkAndRedraw(tile, isLastTile) {
184
197
  // Check if any visible layer depends on this dataset
185
- //check if redraw is really needed, that is if:
198
+ // check if redraw is really needed, that is if:
186
199
  // 1. the dataset belongs to a layer which is visible at the current zoom level
187
200
  let needsRedraw = false
188
201
  //go through the layers
@@ -15,7 +15,7 @@ export const ternaryClassifier = (properties, totalFunction, opts = {}) => {
15
15
  //parameter to decide wether to use mixed classes m0, m1, m2.
16
16
  const withMixedClasses = opts.withMixedClasses != undefined ? opts.withMixedClasses : true
17
17
 
18
- //paramerter decide wether to use a central class, and the size of this central class.
18
+ //parameter to decide wether to use a central class, and the size of this central class.
19
19
  //set to 0 or undefined for not showing any central class. Set to 1 for a central class that contains the mix classes
20
20
  const cc = opts.centerCoefficient ? 1 - opts.centerCoefficient : undefined
21
21
 
@@ -130,6 +130,11 @@ export const ternaryColorClassifier = (properties, totalFunction, colors, opts =
130
130
  return fun
131
131
  }
132
132
 
133
+
134
+
135
+
136
+ // OLD VERSIONS
137
+
133
138
  /*
134
139
  const orderedIndexesDec = arr => [...arr.keys()].sort((a, b) => arr[b] - arr[a]);
135
140
  const orderedIndexesInc = arr => [...arr.keys()].sort((a, b) => arr[a] - arr[b]);