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/README.md +1 -0
- package/dist/gridviz.js +105 -62
- package/dist/gridviz.min.js +1 -1
- package/package.json +1 -1
- package/src/core/GeoCanvas.js +50 -39
- package/src/core/Map.js +13 -0
- package/src/core/Tooltip.js +1 -0
- package/src/dataset/TiledGrid.js +34 -21
- package/src/utils/ternary.js +6 -1
package/package.json
CHANGED
package/src/core/GeoCanvas.js
CHANGED
|
@@ -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
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
package/src/core/Tooltip.js
CHANGED
|
@@ -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')
|
package/src/dataset/TiledGrid.js
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/src/utils/ternary.js
CHANGED
|
@@ -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
|
-
//
|
|
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]);
|