gridviz 3.0.41 → 3.1.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.
Files changed (48) hide show
  1. package/README.md +18 -15
  2. package/dist/gridviz.js +3227 -1941
  3. package/dist/gridviz.min.js +1 -1
  4. package/package.json +6 -4
  5. package/src/button/Button.js +77 -68
  6. package/src/button/FullscreenButton.js +123 -123
  7. package/src/button/ZoomButtons.js +109 -104
  8. package/src/core/Drawable.js +25 -3
  9. package/src/core/GeoCanvas.js +15 -0
  10. package/src/core/Legend.js +267 -245
  11. package/src/core/Map.js +180 -30
  12. package/src/core/Tooltip.js +191 -185
  13. package/src/dataset/TiledGrid.js +74 -24
  14. package/src/index.js +82 -79
  15. package/src/layer/GridLayer.js +196 -194
  16. package/src/layer/LabelLayer.js +7 -4
  17. package/src/legend/ColorCategoryLegend.js +127 -125
  18. package/src/legend/ColorDiscreteLegend.js +116 -114
  19. package/src/legend/ColorLegend.js +33 -11
  20. package/src/legend/OrientationLegend.js +94 -92
  21. package/src/legend/SizeLegend.js +8 -1
  22. package/src/legend/TernaryLegend.js +317 -314
  23. package/src/legend/VennLegend.js +202 -0
  24. package/src/style/CircleColorSizeWebGLStyle.js +249 -0
  25. package/src/style/CompositionStyle.js +1 -1
  26. package/src/style/DotDensityStyle.js +8 -4
  27. package/src/style/ImageStyle.js +1 -1
  28. package/src/style/Interpolator.js +31 -6
  29. package/src/style/IsoFenceStyle.js +1 -1
  30. package/src/style/LegoStyle.js +6 -5
  31. package/src/style/MosaicStyle.js +1 -1
  32. package/src/style/NinjaStarStyle.js +1 -1
  33. package/src/style/PillarStyle.js +1 -1
  34. package/src/style/RandomWalkInterpolator.js +213 -0
  35. package/src/style/ShadingRayStyle.js +16 -2
  36. package/src/style/ShadingStyle.js +80 -72
  37. package/src/style/ShapeColorSizeStyle.js +18 -2
  38. package/src/style/SideCategoryStyle.js +1 -1
  39. package/src/style/SideStyle.js +1 -1
  40. package/src/style/SideTanakaStyle.js +1 -1
  41. package/src/style/SquareColorCategoryWebGLStyle.js +71 -20
  42. package/src/style/SquareColorWebGLStyle.js +7 -4
  43. package/src/style/StrokeStyle.js +1 -1
  44. package/src/style/TextStyle.js +1 -1
  45. package/src/style/TimeSeriesStyle.js +14 -7
  46. package/src/utils/WebGLSquareColoringAdvanced.js +47 -9
  47. package/src/utils/scale.js +20 -0
  48. package/src/utils/webGLUtils.js +52 -0
@@ -1,185 +1,191 @@
1
- //@ts-check
2
- 'use strict'
3
-
4
- import { select } from 'd3-selection'
5
- //import { transition } from "d3-transition";
6
-
7
- /**
8
- * A generic class to make a tooltip.
9
- * It is a div element, which can be moved under the mouse pointer and filled with some information in html.
10
- * @module core
11
- */
12
- export class Tooltip {
13
- /**
14
- * @param {object} opts
15
- */
16
- constructor(opts) {
17
- opts = opts || {}
18
-
19
- /** @type {string} */
20
- this.maxWidth = opts.maxWidth || '20em'
21
- /** @type {string} */
22
- this.fontSize = opts.fontSize || '1.2em'
23
- /** @type {string} */
24
- this.background = opts.background || 'white'
25
- /** @type {string} */
26
- this.padding = opts.padding || '5px'
27
- /** @type {string} */
28
- this.border = opts.border || '0px'
29
- /** @type {string} */
30
- this['border-radius'] = opts['border-radius'] || '0px'
31
- /** @type {string} */
32
- this['box-shadow'] = opts['box-shadow'] || '5px 5px 5px grey'
33
- /** @type {string} */
34
- this['font-family'] = opts['font-family'] || 'Helvetica, Arial, sans-serif'
35
-
36
- /** @type {number} */
37
- this.transitionDuration = opts.transitionDuration || 100
38
- /** @type {number} */
39
- this.xOffset = opts.xOffset || 30
40
- /** @type {number} */
41
- this.yOffset = opts.yOffset || 20
42
- /** @type {number} */ // e.g. to prevent mouse cursor covering cell being highlighted
43
- this.yMouseOffset = opts.yMouseOffset || 0
44
- /** @type {number} */
45
- this.xMouseOffset = opts.xMouseOffset || 0
46
- /** @type {HTMLElement} */
47
- this.parentElement = opts.parentElement || document.body
48
- /** @type {HTMLElement} */
49
- this.tooltipElement = opts.tooltipElement || null
50
-
51
- /**
52
- * @public
53
- * @type {import("d3-selection").Selection} */
54
- this.tooltip = opts.tooltipElement
55
- ? select(opts.tooltipElement) // Wrap the provided HTML node in a D3 selection
56
- : select(this.parentElement).append('div').attr('id', 'gridviz-tooltip').attr('class', 'gridviz-tooltip') // create default element
57
-
58
- //initialise
59
- this.tooltip.style('max-width', this.maxWidth)
60
- this.tooltip.style('overflow', 'hidden')
61
- this.tooltip.style('font-size', this.fontSize)
62
- this.tooltip.style('background', this.background)
63
- this.tooltip.style('padding', this.padding)
64
- this.tooltip.style('border', this.border)
65
- this.tooltip.style('border-radius', this['border-radius'])
66
- this.tooltip.style('box-shadow', this['box-shadow'])
67
- this.tooltip.style('font-family', this['font-family'])
68
- this.tooltip.style('position', 'absolute')
69
- this.tooltip.style('pointer-events', 'none')
70
- this.tooltip.style('opacity', '0')
71
- this.tooltip.style('text-wrap', 'nowrap')
72
- this.tooltip.style('z-index', 99999999) // important for leaflet-gridviz etc
73
-
74
- // these placeholders are needed to prevent an infinite DOM resizeObserver loop:
75
- this.tooltip.style('left', '0')
76
- this.tooltip.style('top', '0')
77
-
78
- // a tooltip element given by the user gets the class too
79
- this.tooltip.classed('gridviz-tooltip', true)
80
-
81
- // aria-labels (thanks to wahlatlas)
82
- this.tooltip.attr('role', 'tooltip').attr('aria-live', 'polite')
83
- }
84
-
85
- /** Show the tooltip */
86
- show() {
87
- // @ts-ignore
88
- this.tooltip.transition().duration(this.transitionDuration).style('opacity', 1)
89
- }
90
-
91
- /** Hide the tooltip */
92
- hide() {
93
- // @ts-ignore
94
- this.tooltip.transition().duration(this.transitionDuration).style('opacity', 0)
95
- }
96
-
97
- /**
98
- * Set the content of the tooltip.
99
- * @param {string} html
100
- */
101
- html(html) {
102
- this.tooltip.html(html)
103
- }
104
-
105
- /**
106
- * Set the position of the tooltip at the mouse event position.
107
- * @param {MouseEvent} event
108
- */
109
- setPosition(event) {
110
- // Get the bounding rect of the parent container (map2)
111
- let parentRect = this.parentElement.getBoundingClientRect()
112
-
113
- // Get the mouse position (relative to the parent container)
114
- let x = event.clientX - parentRect.left + this.xOffset // Relative to parent
115
- let y = event.clientY - parentRect.top - this.yOffset // Relative to parent
116
-
117
- // Now, apply the position to the tooltip
118
- this.tooltip.style('left', x + 'px').style('top', y + 'px')
119
-
120
- // Ensure the tooltip stays inside the parent container
121
- this.ensureTooltipInsideContainer(event, parentRect, this.tooltip.node())
122
- }
123
- /**
124
- * @function ensureTooltipInsideContainer
125
- * @description Prevents the tooltip from overflowing out of the App container (ensures that the tooltip is inside the gridviz container)
126
- * @param {MouseEvent} event
127
- * @param {DOMRect} parentRect
128
- * @param {HTMLElement} tooltipNode
129
- */
130
- ensureTooltipInsideContainer(event, parentRect, tooltipNode) {
131
- let node = tooltipNode
132
- let parentWidth = parentRect.width
133
- let parentHeight = parentRect.height
134
-
135
- // Ensure tooltip doesn't go beyond the right edge
136
- if (node.offsetLeft + node.clientWidth > parentWidth) {
137
- let left = event.clientX - node.clientWidth - this.xOffset
138
- node.style.left = left + 'px'
139
- }
140
-
141
- // Ensure tooltip doesn't go beyond the bottom edge
142
- if (node.offsetTop + node.clientHeight > parentHeight) {
143
- node.style.top = parentHeight - node.clientHeight + 'px'
144
- }
145
-
146
- // Ensure tooltip doesn't go above the top edge
147
- if (node.offsetTop < 0) {
148
- node.style.top = 0 + 'px'
149
- }
150
-
151
- // Ensure tooltip doesn't go beyond the left edge
152
- if (node.offsetLeft < 0) {
153
- node.style.left = 0 + 'px'
154
- }
155
- }
156
-
157
- /*
158
- my.mouseover = function (event, html) {
159
- if (html) my.html(html);
160
- my.setPosition(event);
161
- my.show()
162
- //this.ensureTooltipInsideContainer();
163
- };
164
-
165
- my.mousemove = function (event) {
166
- my.setPosition(event);
167
- //this.ensureTooltipInsideContainer();
168
- };
169
-
170
- my.mouseout = function () {
171
- my.hide();
172
- };*/
173
-
174
- style(k, v) {
175
- if (arguments.length == 1) return this.tooltip.style(k)
176
- this.tooltip.style(k, v)
177
- return this
178
- }
179
-
180
- attr(k, v) {
181
- if (arguments.length == 1) return this.tooltip.attr(k)
182
- this.tooltip.attr(k, v)
183
- return this
184
- }
185
- }
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { select } from 'd3-selection'
5
+ //import { transition } from "d3-transition";
6
+
7
+ /**
8
+ * A generic class to make a tooltip.
9
+ * It is a div element, which can be moved under the mouse pointer and filled with some information in html.
10
+ * @module core
11
+ */
12
+ export class Tooltip {
13
+ /**
14
+ * @param {object} opts
15
+ */
16
+ constructor(opts) {
17
+ opts = opts || {}
18
+
19
+ /** @type {string} */
20
+ this.maxWidth = opts.maxWidth || '20em'
21
+ /** @type {string} */
22
+ this.fontSize = opts.fontSize || '1rem'
23
+ /** @type {string} */
24
+ this.color = opts.color || '#171a22'
25
+ /** @type {string} */
26
+ this.lineHeight = opts.lineHeight || '1.5'
27
+ /** @type {string} */
28
+ this.background = opts.background || 'white'
29
+ /** @type {string} */
30
+ this.padding = opts.padding || '4px'
31
+ /** @type {string} */
32
+ this.border = opts.border || '1px solid #d5d5d5'
33
+ /** @type {string} */
34
+ this['border-radius'] = opts['border-radius'] || '6px'
35
+ /** @type {string} */
36
+ this['box-shadow'] = opts['box-shadow'] || '0px 4px 6px rgba(0, 0, 0, 0.1), 0px 1px 3px rgba(0, 0, 0, 0.08)'
37
+ /** @type {string} */
38
+ this['font-family'] = opts['font-family'] || "'Helvetica Neue', Arial, Helvetica, sans-serif"
39
+
40
+ /** @type {number} */
41
+ this.transitionDuration = opts.transitionDuration || 100
42
+ /** @type {number} */
43
+ this.xOffset = opts.xOffset || 30
44
+ /** @type {number} */
45
+ this.yOffset = opts.yOffset || 20
46
+ /** @type {number} */ // e.g. to prevent mouse cursor covering cell being highlighted
47
+ this.yMouseOffset = opts.yMouseOffset || 0
48
+ /** @type {number} */
49
+ this.xMouseOffset = opts.xMouseOffset || 0
50
+ /** @type {HTMLElement} */
51
+ this.parentElement = opts.parentElement || document.body
52
+ /** @type {HTMLElement} */
53
+ this.tooltipElement = opts.tooltipElement || null
54
+
55
+ /**
56
+ * @public
57
+ * @type {import("d3-selection").Selection} */
58
+ this.tooltip = opts.tooltipElement
59
+ ? select(opts.tooltipElement) // Wrap the provided HTML node in a D3 selection
60
+ : select(this.parentElement).append('div').attr('id', 'gridviz-tooltip').attr('class', 'gridviz-tooltip') // create default element
61
+
62
+ //initialise
63
+ this.tooltip.style('max-width', this.maxWidth)
64
+ this.tooltip.style('overflow', 'hidden')
65
+ this.tooltip.style('font-size', this.fontSize)
66
+ this.tooltip.style('color', this.color)
67
+ this.tooltip.style('line-height', this.lineHeight)
68
+ this.tooltip.style('background', this.background)
69
+ this.tooltip.style('padding', this.padding)
70
+ this.tooltip.style('border', this.border)
71
+ this.tooltip.style('border-radius', this['border-radius'])
72
+ this.tooltip.style('box-shadow', this['box-shadow'])
73
+ this.tooltip.style('font-family', this['font-family'])
74
+ this.tooltip.style('position', 'absolute')
75
+ this.tooltip.style('pointer-events', 'none')
76
+ this.tooltip.style('opacity', '0')
77
+ this.tooltip.style('text-wrap', 'nowrap')
78
+ this.tooltip.style('z-index', 99999999) // important for leaflet-gridviz etc
79
+
80
+ // these placeholders are needed to prevent an infinite DOM resizeObserver loop:
81
+ this.tooltip.style('left', '0')
82
+ this.tooltip.style('top', '0')
83
+
84
+ // a tooltip element given by the user gets the class too
85
+ this.tooltip.classed('gridviz-tooltip', true)
86
+
87
+ // aria-labels (thanks to wahlatlas)
88
+ this.tooltip.attr('role', 'tooltip').attr('aria-live', 'polite')
89
+ }
90
+
91
+ /** Show the tooltip */
92
+ show() {
93
+ // @ts-ignore
94
+ this.tooltip.transition().duration(this.transitionDuration).style('opacity', 1)
95
+ }
96
+
97
+ /** Hide the tooltip */
98
+ hide() {
99
+ // @ts-ignore
100
+ this.tooltip.transition().duration(this.transitionDuration).style('opacity', 0)
101
+ }
102
+
103
+ /**
104
+ * Set the content of the tooltip.
105
+ * @param {string} html
106
+ */
107
+ html(html) {
108
+ this.tooltip.html(html)
109
+ }
110
+
111
+ /**
112
+ * Set the position of the tooltip at the mouse event position.
113
+ * @param {MouseEvent} event
114
+ */
115
+ setPosition(event) {
116
+ // Get the bounding rect of the parent container (map2)
117
+ let parentRect = this.parentElement.getBoundingClientRect()
118
+
119
+ // Get the mouse position (relative to the parent container)
120
+ let x = event.clientX - parentRect.left + this.xOffset // Relative to parent
121
+ let y = event.clientY - parentRect.top - this.yOffset // Relative to parent
122
+
123
+ // Now, apply the position to the tooltip
124
+ this.tooltip.style('left', x + 'px').style('top', y + 'px')
125
+
126
+ // Ensure the tooltip stays inside the parent container
127
+ this.ensureTooltipInsideContainer(event, parentRect, this.tooltip.node())
128
+ }
129
+ /**
130
+ * @function ensureTooltipInsideContainer
131
+ * @description Prevents the tooltip from overflowing out of the App container (ensures that the tooltip is inside the gridviz container)
132
+ * @param {MouseEvent} event
133
+ * @param {DOMRect} parentRect
134
+ * @param {HTMLElement} tooltipNode
135
+ */
136
+ ensureTooltipInsideContainer(event, parentRect, tooltipNode) {
137
+ let node = tooltipNode
138
+ let parentWidth = parentRect.width
139
+ let parentHeight = parentRect.height
140
+
141
+ // Ensure tooltip doesn't go beyond the right edge
142
+ if (node.offsetLeft + node.clientWidth > parentWidth) {
143
+ let left = event.clientX - node.clientWidth - this.xOffset
144
+ node.style.left = left + 'px'
145
+ }
146
+
147
+ // Ensure tooltip doesn't go beyond the bottom edge
148
+ if (node.offsetTop + node.clientHeight > parentHeight) {
149
+ node.style.top = parentHeight - node.clientHeight + 'px'
150
+ }
151
+
152
+ // Ensure tooltip doesn't go above the top edge
153
+ if (node.offsetTop < 0) {
154
+ node.style.top = 0 + 'px'
155
+ }
156
+
157
+ // Ensure tooltip doesn't go beyond the left edge
158
+ if (node.offsetLeft < 0) {
159
+ node.style.left = 0 + 'px'
160
+ }
161
+ }
162
+
163
+ /*
164
+ my.mouseover = function (event, html) {
165
+ if (html) my.html(html);
166
+ my.setPosition(event);
167
+ my.show()
168
+ //this.ensureTooltipInsideContainer();
169
+ };
170
+
171
+ my.mousemove = function (event) {
172
+ my.setPosition(event);
173
+ //this.ensureTooltipInsideContainer();
174
+ };
175
+
176
+ my.mouseout = function () {
177
+ my.hide();
178
+ };*/
179
+
180
+ style(k, v) {
181
+ if (arguments.length == 1) return this.tooltip.style(k)
182
+ this.tooltip.style(k, v)
183
+ return this
184
+ }
185
+
186
+ attr(k, v) {
187
+ if (arguments.length == 1) return this.tooltip.attr(k)
188
+ this.tooltip.attr(k, v)
189
+ return this
190
+ }
191
+ }
@@ -5,6 +5,7 @@
5
5
 
6
6
  // internal
7
7
  import { Dataset } from '../core/Dataset.js'
8
+ import { isVisibleAt } from '../core/Drawable.js'
8
9
  //import { monitor, monitorDuration } from '../utils/Utils.js'
9
10
 
10
11
  // external
@@ -96,7 +97,7 @@ export class TiledGrid extends Dataset {
96
97
  if (!this.map || !this.map.layers) return false
97
98
  const z = this.map.getZoom()
98
99
  for (const lay of this.map.layers) {
99
- if (lay.visible && !lay.visible(z)) continue
100
+ if (!isVisibleAt(lay, z)) continue
100
101
  if (!lay.getDataset) continue
101
102
  if (lay.getDataset(z) == this) return true
102
103
  }
@@ -152,8 +153,6 @@ export class TiledGrid extends Dataset {
152
153
  const yMin = Math.max(tb.yMin, gbYMin)
153
154
  const yMax = Math.min(tb.yMax, gbYMax)
154
155
 
155
- const totalTiles = (xMax - xMin + 1) * (yMax - yMin + 1)
156
- let processedTiles = 0
157
156
  const tilePromises = []
158
157
 
159
158
  // Iterate over tiles within bounds
@@ -161,11 +160,13 @@ export class TiledGrid extends Dataset {
161
160
  for (let yT = yMin; yT <= yMax; yT++) {
162
161
  if (!this.cache[xT]) this.cache[xT] = {}
163
162
 
164
- // Skip already loaded tiles or retry failed ones
165
- if (this.cache[xT][yT] && this.cache[xT][yT] !== 'failed') {
163
+ // Skip the tiles loaded, under way, or that failed recently
164
+ const kept = this.cache[xT][yT]
165
+ const retry =
166
+ kept && kept.failed && !kept.missing && now() - kept.failed >= TiledGrid.retryFailedAfterMs
167
+ if (kept && !retry) {
166
168
  // in the view: not to be dropped
167
- if (typeof this.cache[xT][yT] === 'object') this.cache[xT][yT].lastUsed = now()
168
- ++processedTiles
169
+ if (kept.cells) kept.lastUsed = now()
169
170
  continue
170
171
  }
171
172
 
@@ -173,19 +174,40 @@ export class TiledGrid extends Dataset {
173
174
  this.cache[xT][yT] = 'loading'
174
175
 
175
176
  tilePromises.push(
176
- this.loadTile(xT, yT, signal)
177
- .then((tile) => {
177
+ this.loadTile(xT, yT, signal).then(
178
+ (tile) => {
178
179
  this.cache[xT][yT] = tile
179
180
  this._keep(tile)
180
181
 
181
182
  // Check if this is the last tile
182
- const isLastTile = ++processedTiles === totalTiles
183
+ // (tiles under way for another request count: the one that ends last is the one that draws)
184
+ const isLastTile = !this._anyLoading(xMin, xMax, yMin, yMax)
183
185
  this.checkAndRedraw(tile, isLastTile)
184
- })
185
- .catch(() => {
186
- this.cache[xT][yT] = 'failed'
187
- ++processedTiles
188
- })
186
+ },
187
+ (error) => {
188
+ if (error && error.name === 'AbortError') {
189
+ // cancelled, not failed: the next request loads it
190
+ delete this.cache[xT][yT]
191
+ return
192
+ }
193
+ // Most of the time a tile that fails is one the server doesn't have (the tiles of a grid are
194
+ // often sparse). It is remembered as failed, and not asked for again at every redraw.
195
+ // A 404 is final ('missing'). Any other failure is tried again after retryFailedAfterMs: a
196
+ // browser doesn't give the status of a response without CORS headers, so a 404 from such a
197
+ // server can't be told from a network error.
198
+ this.cache[xT][yT] = {
199
+ failed: now(),
200
+ missing: /^(404|410)\b/.test(error && error.message),
201
+ }
202
+ // it changes nothing on the map, except when it was waited for to draw
203
+ if (
204
+ this.onlyDrawWhenAllTilesReady &&
205
+ !this._anyLoading(xMin, xMax, yMin, yMax) &&
206
+ this.isShown()
207
+ )
208
+ this.map.redraw()
209
+ }
210
+ )
189
211
  )
190
212
  }
191
213
  }
@@ -194,6 +216,25 @@ export class TiledGrid extends Dataset {
194
216
  return this
195
217
  }
196
218
 
219
+ /**
220
+ * Tells if some tiles of a tiling envelope are still being loaded, by this request or any other one.
221
+ *
222
+ * @param {number} xMin
223
+ * @param {number} xMax
224
+ * @param {number} yMin
225
+ * @param {number} yMax
226
+ * @returns {boolean}
227
+ * @private
228
+ */
229
+ _anyLoading(xMin, xMax, yMin, yMax) {
230
+ for (let xT = xMin; xT <= xMax; xT++) {
231
+ const column = this.cache[xT]
232
+ if (!column) continue
233
+ for (let yT = yMin; yT <= yMax; yT++) if (column[yT] === 'loading') return true
234
+ }
235
+ return false
236
+ }
237
+
197
238
  /**
198
239
  * Load a tile.
199
240
  *
@@ -234,19 +275,20 @@ export class TiledGrid extends Dataset {
234
275
  // 1. the dataset belongs to a layer which is visible at the current zoom level
235
276
  if (!this.isShown()) return
236
277
 
278
+ // Waiting for all the tiles: drawn when the last one arrives, wherever it is (the tiles asked for are those of the
279
+ // view and of a margin around it, so the last one to arrive may be outside the view)
280
+ if (this.onlyDrawWhenAllTilesReady) {
281
+ if (isLastTile) this.map.redraw()
282
+ return
283
+ }
284
+
237
285
  // Check if tile intersects the current view
238
286
  const env = this.map.updateExtentGeo()
239
287
  const { xMin, xMax, yMin, yMax } = tile.extGeo
240
288
  if (env.xMax <= xMin || env.xMin >= xMax || env.yMax <= yMin || env.yMin >= yMax) return
241
289
 
242
290
  // Trigger redraw
243
- if (this.onlyDrawWhenAllTilesReady) {
244
- if (isLastTile) {
245
- this.map.redraw()
246
- }
247
- } else {
248
- this.map.redraw()
249
- }
291
+ this.map.redraw()
250
292
  }
251
293
 
252
294
  /**
@@ -256,7 +298,7 @@ export class TiledGrid extends Dataset {
256
298
  * @private
257
299
  */
258
300
  _keep(tile) {
259
- // a failed tile is the string 'failed', not a tile
301
+ // a failed tile is not a tile
260
302
  if (!tile || typeof tile !== 'object' || !tile.cells) return
261
303
  tile.lastUsed = now()
262
304
  tile.owner = this
@@ -316,7 +358,7 @@ export class TiledGrid extends Dataset {
316
358
  //get tile
317
359
  /** @type {object} */
318
360
  const tile = this.cache[xT][yT]
319
- if (!tile || typeof tile === 'string') continue
361
+ if (!tile || !tile.cells) continue
320
362
  tile.lastUsed = now()
321
363
 
322
364
  //get cells
@@ -342,6 +384,14 @@ export class TiledGrid extends Dataset {
342
384
  */
343
385
  TiledGrid.maxCachedCells = 1500000
344
386
 
387
+ /**
388
+ * How long, in ms, a tile that failed to load is remembered, before it is requested again. It doesn't apply to a tile the
389
+ * server answered 404 or 410 for: that one isn't requested again. It can be changed at any time.
390
+ *
391
+ * @type {number}
392
+ */
393
+ TiledGrid.retryFailedAfterMs = 30000
394
+
345
395
  function getGridTile(cells, xT, yT, gridInfo) {
346
396
  const tile = {}
347
397