gridviz 3.0.15 → 3.0.17
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 +127 -128
- package/dist/gridviz.js +7958 -7861
- package/dist/gridviz.min.js +1 -1
- package/package.json +74 -74
- package/src/button/ZoomButtons.js +101 -96
- package/src/core/Dataset.js +154 -157
- package/src/core/Drawable.js +60 -61
- package/src/core/GeoCanvas.js +409 -370
- package/src/core/Layer.js +23 -25
- package/src/core/Legend.js +108 -110
- package/src/core/Map.js +494 -495
- package/src/core/MultiResolutionDataset.js +93 -97
- package/src/core/Style.js +98 -99
- package/src/dataset/CSVGrid.js +112 -112
- package/src/dataset/GeoTIFF.js +122 -123
- package/src/dataset/JSGrid.js +55 -54
- package/src/dataset/TiledGrid.js +289 -288
- package/src/dataset/worker.js +18 -0
- package/src/index.js +77 -71
- package/src/layer/BackgroundLayer.js +162 -158
- package/src/layer/BackgroundLayerImage.js +81 -84
- package/src/layer/BackgroundLayerWMS.js +104 -108
- package/src/layer/GeoJSONLayer.js +214 -220
- package/src/layer/GridLayer.js +148 -157
- package/src/layer/LabelLayer.js +194 -193
- package/src/legend/ColorCategoryLegend.js +109 -110
- package/src/legend/ColorDiscreteLegend.js +100 -102
- package/src/legend/ColorLegend.js +115 -118
- package/src/legend/OrientationLegend.js +89 -91
- package/src/legend/SizeLegend.js +224 -224
- package/src/legend/TernaryLegend.js +302 -210
- package/src/style/CompositionStyle.js +291 -288
- package/src/style/DotDensityStyle.js +133 -134
- package/src/style/ImageStyle.js +100 -102
- package/src/style/IsoFenceStyle.js +217 -203
- package/src/style/JoyPlotStyle.js +127 -128
- package/src/style/LegoStyle.js +110 -110
- package/src/style/MosaicStyle.js +99 -99
- package/src/style/NinjaStarStyle.js +102 -102
- package/src/style/PillarStyle.js +207 -208
- package/src/style/SegmentStyle.js +105 -105
- package/src/style/ShapeColorSizeStyle.js +147 -132
- package/src/style/SideCategoryStyle.js +112 -114
- package/src/style/SideStyle.js +212 -205
- package/src/style/SquareColorCategoryWebGLStyle.js +107 -108
- package/src/style/SquareColorWebGLStyle.js +126 -126
- package/src/style/StrokeStyle.js +135 -136
- package/src/style/TanakaStyle.js +69 -68
- package/src/style/TextStyle.js +110 -111
- package/src/style/TimeSeriesStyle.js +222 -229
- package/src/utils/scale.js +218 -228
- package/src/utils/stretching.js +120 -133
- package/src/utils/ternary.js +203 -204
- package/src/utils/utils.js +58 -63
- package/src/utils/webGLUtils.js +72 -70
package/src/core/Map.js
CHANGED
|
@@ -1,495 +1,494 @@
|
|
|
1
|
-
//@ts-check
|
|
2
|
-
'use strict'
|
|
3
|
-
|
|
4
|
-
// internal imports
|
|
5
|
-
import { GeoCanvas } from './GeoCanvas.js'
|
|
6
|
-
import { Tooltip } from './Tooltip.js'
|
|
7
|
-
import { ZoomButtons } from '../button/ZoomButtons.js'
|
|
8
|
-
import { FullscreenButton } from '../button/FullscreenButton.js'
|
|
9
|
-
|
|
10
|
-
// external imports
|
|
11
|
-
import { select } from 'd3-selection'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* A gridviz application.
|
|
15
|
-
*
|
|
16
|
-
* @module core
|
|
17
|
-
* @author Joseph Davies, Julien Gaffuri
|
|
18
|
-
*/
|
|
19
|
-
export class Map {
|
|
20
|
-
/**
|
|
21
|
-
* @param {HTMLDivElement} container
|
|
22
|
-
* @param {object} opts
|
|
23
|
-
*/
|
|
24
|
-
constructor(container, opts) {
|
|
25
|
-
opts = opts || {}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* The layers.
|
|
29
|
-
* @type {Array.<import("./Layer.js").Layer>}
|
|
30
|
-
* */
|
|
31
|
-
this.layers = opts.layers || []
|
|
32
|
-
|
|
33
|
-
//get container element
|
|
34
|
-
this.container = container || document.getElementById('gridviz')
|
|
35
|
-
if (!this.container) {
|
|
36
|
-
console.error('Cannot find gridviz container element.')
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
//https://css-tricks.com/absolute-positioning-inside-relative-positioning/
|
|
41
|
-
this.container.style.position =
|
|
42
|
-
|
|
43
|
-
//set dimensions
|
|
44
|
-
/** @type {number} */
|
|
45
|
-
this.w = opts.w || this.container.offsetWidth
|
|
46
|
-
/** @type {number} */
|
|
47
|
-
this.h = opts.h || this.container.offsetHeight
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
/** @type {HTMLCanvasElement} */
|
|
51
|
-
this._canvas = opts.canvas || this.initialiseCanvas()
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* @type {GeoCanvas}
|
|
55
|
-
* @private */
|
|
56
|
-
this.geoCanvas = new GeoCanvas(this._canvas, opts.x, opts.y, opts.z, opts)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
this.
|
|
80
|
-
this.
|
|
81
|
-
this.
|
|
82
|
-
this.geoCanvas.canvas.addEventListener('
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
.
|
|
127
|
-
.
|
|
128
|
-
|
|
129
|
-
.style('
|
|
130
|
-
|
|
131
|
-
.style('
|
|
132
|
-
|
|
133
|
-
.style('
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
this.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
this.geoCanvas.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* @param {number} marginPx
|
|
204
|
-
* @returns {import('./GeoCanvas.js').Envelope}
|
|
205
|
-
* @public
|
|
206
|
-
*/
|
|
207
|
-
updateExtentGeo(marginPx = 20) {
|
|
208
|
-
return this.geoCanvas.updateExtentGeo(marginPx)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
this.tooltip.
|
|
227
|
-
|
|
228
|
-
this.tooltip.
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
this.canvasSave
|
|
233
|
-
this.canvasSave.
|
|
234
|
-
this.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
this.tooltip.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
this.canvasSave
|
|
248
|
-
this.canvasSave.
|
|
249
|
-
|
|
250
|
-
this.
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
this.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
this.geoCanvas.
|
|
266
|
-
this.geoCanvas.
|
|
267
|
-
focus.resolution / this.geoCanvas.view.z
|
|
268
|
-
|
|
269
|
-
)
|
|
270
|
-
this.geoCanvas.ctx.
|
|
271
|
-
} else {
|
|
272
|
-
this.tooltip.hide()
|
|
273
|
-
if (this.canvasSave) this.geoCanvas.ctx.drawImage(this.canvasSave, 0, 0)
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
* @
|
|
283
|
-
* @
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (layer.
|
|
293
|
-
if (!layer.cellInfoHTML) continue
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
* @param {number}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
this.geoCanvas.view.
|
|
327
|
-
this
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
/** @returns {number} */
|
|
336
|
-
getZoom() {
|
|
337
|
-
return this.geoCanvas.view.z
|
|
338
|
-
}
|
|
339
|
-
/** @param {number} z @returns {this} */
|
|
340
|
-
setZoom(z) {
|
|
341
|
-
this.geoCanvas.view.z = z
|
|
342
|
-
return this
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
this
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
this
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
*
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
// * opts.
|
|
386
|
-
// * opts.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
*
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
// * opts.
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
if (
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
this.
|
|
453
|
-
this.
|
|
454
|
-
|
|
455
|
-
this.geoCanvas.
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
//
|
|
461
|
-
// if (this.
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
this.
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
this.container.removeEventListener('
|
|
483
|
-
this.container.removeEventListener('
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// internal imports
|
|
5
|
+
import { GeoCanvas } from './GeoCanvas.js'
|
|
6
|
+
import { Tooltip } from './Tooltip.js'
|
|
7
|
+
import { ZoomButtons } from '../button/ZoomButtons.js'
|
|
8
|
+
import { FullscreenButton } from '../button/FullscreenButton.js'
|
|
9
|
+
|
|
10
|
+
// external imports
|
|
11
|
+
import { select } from 'd3-selection'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A gridviz application.
|
|
15
|
+
*
|
|
16
|
+
* @module core
|
|
17
|
+
* @author Joseph Davies, Julien Gaffuri
|
|
18
|
+
*/
|
|
19
|
+
export class Map {
|
|
20
|
+
/**
|
|
21
|
+
* @param {HTMLDivElement} container
|
|
22
|
+
* @param {object} opts
|
|
23
|
+
*/
|
|
24
|
+
constructor(container, opts) {
|
|
25
|
+
opts = opts || {}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The layers.
|
|
29
|
+
* @type {Array.<import("./Layer.js").Layer>}
|
|
30
|
+
* */
|
|
31
|
+
this.layers = opts.layers || []
|
|
32
|
+
|
|
33
|
+
//get container element
|
|
34
|
+
this.container = container || document.getElementById('gridviz')
|
|
35
|
+
if (!this.container) {
|
|
36
|
+
console.error('Cannot find gridviz container element.')
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//https://css-tricks.com/absolute-positioning-inside-relative-positioning/
|
|
41
|
+
this.container.style.position = 'relative' // container element must have relative positioning
|
|
42
|
+
|
|
43
|
+
//set dimensions
|
|
44
|
+
/** @type {number} */
|
|
45
|
+
this.w = opts.w || this.container.offsetWidth
|
|
46
|
+
/** @type {number} */
|
|
47
|
+
this.h = opts.h || this.container.offsetHeight
|
|
48
|
+
|
|
49
|
+
// Create the main canvas (for rendering to screen)
|
|
50
|
+
/** @type {HTMLCanvasElement} */
|
|
51
|
+
this._canvas = opts.canvas || this.initialiseCanvas()
|
|
52
|
+
|
|
53
|
+
/** Initialize GeoCanvas
|
|
54
|
+
* @type {GeoCanvas}
|
|
55
|
+
* @private */
|
|
56
|
+
this.geoCanvas = new GeoCanvas(this._canvas, opts.x, opts.y, opts.z, opts)
|
|
57
|
+
|
|
58
|
+
this.geoCanvas.redraw = () => {
|
|
59
|
+
this.redraw()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// legend div
|
|
63
|
+
this.legendDivId = opts.legendDivId || 'gvizLegend'
|
|
64
|
+
this.legend = select('#' + this.legendDivId)
|
|
65
|
+
if (this.legend.empty()) this.initialiseLegend()
|
|
66
|
+
|
|
67
|
+
//tooltip
|
|
68
|
+
|
|
69
|
+
// set App container as default parent element for tooltip
|
|
70
|
+
if (!opts.tooltip) opts.tooltip = {}
|
|
71
|
+
if (!opts.tooltip.parentElement) opts.tooltip.parentElement = this.container
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @private
|
|
75
|
+
* @type {Tooltip} */
|
|
76
|
+
this.tooltip = new Tooltip(opts.tooltip)
|
|
77
|
+
|
|
78
|
+
// add event listeners to container
|
|
79
|
+
this.mouseOverHandler = (e) => this.focusCell(e)
|
|
80
|
+
this.mouseMoveHandler = (e) => this.focusCell(e)
|
|
81
|
+
this.mouseOutHandler = (e) => this.tooltip.hide()
|
|
82
|
+
this.geoCanvas.canvas.addEventListener('mouseover', this.mouseOverHandler)
|
|
83
|
+
this.geoCanvas.canvas.addEventListener('mousemove', this.mouseMoveHandler)
|
|
84
|
+
this.geoCanvas.canvas.addEventListener('mouseout', this.mouseOutHandler)
|
|
85
|
+
|
|
86
|
+
// listen for resize events on the App's container and handle them
|
|
87
|
+
this.defineResizeObserver()
|
|
88
|
+
|
|
89
|
+
// add extra logic to onZoomStartFun
|
|
90
|
+
this.geoCanvas.onZoomStartFun = (e) => {
|
|
91
|
+
if (opts.onZoomStartFun) opts.onZoomStartFun(e)
|
|
92
|
+
this.tooltip.hide()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
//for mouse over
|
|
96
|
+
/**
|
|
97
|
+
* @private
|
|
98
|
+
* @type {HTMLCanvasElement|null} */
|
|
99
|
+
this.canvasSave = null
|
|
100
|
+
|
|
101
|
+
this.selectionRectangleColor = opts.selectionRectangleColor || '#FF6347'
|
|
102
|
+
this.selectionRectangleWidthPix = opts.selectionRectangleWidthPix || (() => 4) //(r,z) => {}
|
|
103
|
+
|
|
104
|
+
// transparent background (e.g. leaflet) 'red painting' fix
|
|
105
|
+
this.transparentBackground = opts.transparentBackground
|
|
106
|
+
|
|
107
|
+
//set default globalCompositeOperation
|
|
108
|
+
this.defaultGlobalCompositeOperation =
|
|
109
|
+
opts.defaultGlobalCompositeOperation || this.geoCanvas.ctx.globalCompositeOperation
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @protected
|
|
114
|
+
* @returns {HTMLCanvasElement}
|
|
115
|
+
*/
|
|
116
|
+
initialiseCanvas() {
|
|
117
|
+
const canvas = document.createElement('canvas')
|
|
118
|
+
canvas.setAttribute('width', '' + this.w)
|
|
119
|
+
canvas.setAttribute('height', '' + this.h)
|
|
120
|
+
this.container.appendChild(canvas)
|
|
121
|
+
return canvas
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
initialiseLegend() {
|
|
125
|
+
this.legend = select(this.container.id && this.container.id != '' ? '#' + this.container.id : 'body')
|
|
126
|
+
.append('div')
|
|
127
|
+
.attr('id', this.legendDivId)
|
|
128
|
+
.style('position', 'absolute')
|
|
129
|
+
.style('width', 'auto')
|
|
130
|
+
.style('height', 'auto')
|
|
131
|
+
.style('background', '#FFFFFF')
|
|
132
|
+
//.style("padding", this.padding)
|
|
133
|
+
.style('border', '0px')
|
|
134
|
+
//.style('border-radius', '5px')
|
|
135
|
+
.style('box-shadow', '3px 3px 3px grey, -3px -3px 3px #ddd')
|
|
136
|
+
.style('font-family', 'Helvetica, Arial, sans-serif')
|
|
137
|
+
.style('bottom', '15px')
|
|
138
|
+
.style('right', '15px')
|
|
139
|
+
//hide
|
|
140
|
+
//.style("visibility", "hidden")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Set/get layer stack.
|
|
145
|
+
*
|
|
146
|
+
* @param {undefined|import("./Layer.js").Layer|import("./Layer.js").Layer[]} layers
|
|
147
|
+
* @returns { this | import("./Layer.js").Layer[] }
|
|
148
|
+
*/
|
|
149
|
+
layers_(layers) {
|
|
150
|
+
if (arguments.length === 0) return this.layers
|
|
151
|
+
if (arguments.length === 1)
|
|
152
|
+
if (Array.isArray(layers)) this.layers = layers
|
|
153
|
+
else this.layers = [layers]
|
|
154
|
+
else this.layers = arguments
|
|
155
|
+
return this
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** @returns {this} */
|
|
159
|
+
redraw() {
|
|
160
|
+
//remove legend elements
|
|
161
|
+
if (this.legend) this.legend.selectAll('*').remove()
|
|
162
|
+
|
|
163
|
+
//clear
|
|
164
|
+
this.geoCanvas.initCanvasTransform()
|
|
165
|
+
this.geoCanvas.clear(this.geoCanvas.backgroundColor)
|
|
166
|
+
|
|
167
|
+
const z = this.geoCanvas.view.z
|
|
168
|
+
this.updateExtentGeo()
|
|
169
|
+
|
|
170
|
+
//go through the layers
|
|
171
|
+
for (const layer of this.layers) {
|
|
172
|
+
//check if layer is visible
|
|
173
|
+
if (layer.visible && !layer.visible(z)) continue
|
|
174
|
+
|
|
175
|
+
//set layer alpha and blend mode
|
|
176
|
+
this.geoCanvas.offscreenCtx.globalAlpha = layer.alpha ? layer.alpha(z) : 1.0
|
|
177
|
+
if (layer.blendOperation) this.geoCanvas.offscreenCtx.globalCompositeOperation = layer.blendOperation(z)
|
|
178
|
+
|
|
179
|
+
//set affin transform to draw with geographical coordinates
|
|
180
|
+
this.geoCanvas.setCanvasTransform()
|
|
181
|
+
|
|
182
|
+
//draw layer
|
|
183
|
+
layer.draw(this.geoCanvas, this.legend)
|
|
184
|
+
|
|
185
|
+
//draw layer filter
|
|
186
|
+
if (layer.filterColor) layer.drawFilter(this.geoCanvas)
|
|
187
|
+
|
|
188
|
+
//restore default alpha and blend operation
|
|
189
|
+
this.geoCanvas.offscreenCtx.globalAlpha = 1.0
|
|
190
|
+
this.geoCanvas.offscreenCtx.globalCompositeOperation = this.defaultGlobalCompositeOperation
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// one drawImage call: draw the offscreen canvas to the main canvas
|
|
194
|
+
this.geoCanvas.initCanvasTransform()
|
|
195
|
+
this.geoCanvas.ctx.drawImage(this.geoCanvas.offscreenCanvas, 0, 0)
|
|
196
|
+
|
|
197
|
+
this.canvasSave = null
|
|
198
|
+
|
|
199
|
+
return this
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @param {number} marginPx
|
|
204
|
+
* @returns {import('./GeoCanvas.js').Envelope}
|
|
205
|
+
* @public
|
|
206
|
+
*/
|
|
207
|
+
updateExtentGeo(marginPx = 20) {
|
|
208
|
+
return this.geoCanvas.updateExtentGeo(marginPx)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** @param {MouseEvent} e */
|
|
212
|
+
focusCell(e) {
|
|
213
|
+
//compute mouse geo position
|
|
214
|
+
const mousePositionGeo = {
|
|
215
|
+
x: this.geoCanvas.pixToGeoX(e.offsetX + this.tooltip.xMouseOffset),
|
|
216
|
+
y: this.geoCanvas.pixToGeoY(e.offsetY + this.tooltip.yMouseOffset),
|
|
217
|
+
}
|
|
218
|
+
/** @type {{cell:import('./Dataset.js').Cell,html:string,resolution:number} | undefined} */
|
|
219
|
+
const focus = this.getCellFocusInfo(mousePositionGeo)
|
|
220
|
+
|
|
221
|
+
// transparent background (e.g. leaflet) 'red painting' fix
|
|
222
|
+
if (this.transparentBackground) {
|
|
223
|
+
if (focus) {
|
|
224
|
+
this.tooltip.html(focus.html)
|
|
225
|
+
this.tooltip.setPosition(e)
|
|
226
|
+
this.tooltip.show()
|
|
227
|
+
} else {
|
|
228
|
+
this.tooltip.hide()
|
|
229
|
+
}
|
|
230
|
+
this.canvasSave = document.createElement('canvas')
|
|
231
|
+
this.canvasSave.setAttribute('width', '' + this.w)
|
|
232
|
+
this.canvasSave.setAttribute('height', '' + this.h)
|
|
233
|
+
this.canvasSave.getContext('2d')?.drawImage(this.geoCanvas.canvas, 0, 0)
|
|
234
|
+
this.geoCanvas.initCanvasTransform()
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (focus) {
|
|
239
|
+
this.tooltip.html(focus.html)
|
|
240
|
+
this.tooltip.setPosition(e)
|
|
241
|
+
this.tooltip.show()
|
|
242
|
+
|
|
243
|
+
//show cell position as a rectangle
|
|
244
|
+
if (!this.canvasSave) {
|
|
245
|
+
this.canvasSave = document.createElement('canvas')
|
|
246
|
+
this.canvasSave.setAttribute('width', '' + this.w)
|
|
247
|
+
this.canvasSave.setAttribute('height', '' + this.h)
|
|
248
|
+
this.canvasSave.getContext('2d')?.drawImage(this.geoCanvas.offscreenCanvas, 0, 0)
|
|
249
|
+
} else {
|
|
250
|
+
this.geoCanvas.offscreenCtx.drawImage(this.canvasSave, 0, 0)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
//draw image saved + draw rectangle
|
|
254
|
+
const rectWPix = this.selectionRectangleWidthPix
|
|
255
|
+
? this.selectionRectangleWidthPix(focus.resolution, this.geoCanvas.view.z)
|
|
256
|
+
: 4
|
|
257
|
+
this.geoCanvas.initCanvasTransform()
|
|
258
|
+
const ctx = this.geoCanvas.offscreenCtx
|
|
259
|
+
ctx.strokeStyle = this.selectionRectangleColor
|
|
260
|
+
ctx.lineWidth = rectWPix
|
|
261
|
+
ctx.beginPath()
|
|
262
|
+
|
|
263
|
+
ctx.rect(
|
|
264
|
+
this.geoCanvas.geoToPixX(focus.cell.x) - rectWPix / 2,
|
|
265
|
+
this.geoCanvas.geoToPixY(focus.cell.y) + rectWPix / 2,
|
|
266
|
+
focus.resolution / this.geoCanvas.view.z + rectWPix,
|
|
267
|
+
-focus.resolution / this.geoCanvas.view.z - rectWPix
|
|
268
|
+
)
|
|
269
|
+
ctx.stroke()
|
|
270
|
+
this.geoCanvas.ctx.drawImage(this.geoCanvas.offscreenCanvas, 0, 0)
|
|
271
|
+
} else {
|
|
272
|
+
this.tooltip.hide()
|
|
273
|
+
if (this.canvasSave) this.geoCanvas.ctx.drawImage(this.canvasSave, 0, 0)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Return the cell HTML info at a given geo position.
|
|
279
|
+
* This is usefull for user interactions, to show this info where the user clicks for example.
|
|
280
|
+
*
|
|
281
|
+
* @param {{x:number,y:number}} posGeo
|
|
282
|
+
* @returns {{cell:import('./Dataset.js').Cell,html:string,resolution:number} | undefined}
|
|
283
|
+
* @protected
|
|
284
|
+
*/
|
|
285
|
+
getCellFocusInfo(posGeo) {
|
|
286
|
+
//go through the layers, starting from top
|
|
287
|
+
const z = this.geoCanvas.view.z
|
|
288
|
+
for (let i = this.layers.length - 1; i >= 0; i--) {
|
|
289
|
+
/** @type {import("./Layer.js").Layer} */
|
|
290
|
+
const layer = this.layers[i]
|
|
291
|
+
if (layer.visible && !layer.visible(z)) continue
|
|
292
|
+
if (layer.cellInfoHTML === 'none') continue // this is necessary in order to not show tooltips for layers 'on top' (e.g. population circles on top of squares)
|
|
293
|
+
if (!layer.cellInfoHTML) continue
|
|
294
|
+
if (!layer.getDataset) continue
|
|
295
|
+
const dsc = layer.getDataset(z)
|
|
296
|
+
if (!dsc) continue
|
|
297
|
+
|
|
298
|
+
//get cell at mouse position
|
|
299
|
+
/** @type {import('./Dataset.js').Cell|undefined} */
|
|
300
|
+
const cell = dsc.getCellFromPosition(posGeo, dsc.getViewCache())
|
|
301
|
+
//console.log(cell, dsc.resolution)
|
|
302
|
+
if (!cell) return undefined
|
|
303
|
+
|
|
304
|
+
//rare case for a dataset with mixed resolutions
|
|
305
|
+
if (dsc.mixedResolution) {
|
|
306
|
+
const r = +dsc.mixedResolution(cell)
|
|
307
|
+
const html = layer.cellInfoHTML(cell, r)
|
|
308
|
+
if (!html) return undefined
|
|
309
|
+
return { cell: cell, html: html, resolution: r }
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const html = layer.cellInfoHTML(cell, dsc.getResolution())
|
|
313
|
+
if (!html) return undefined
|
|
314
|
+
return { cell: cell, html: html, resolution: dsc.getResolution() }
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* @param {number} x
|
|
320
|
+
* @param {number} y
|
|
321
|
+
* @param {number|undefined} z
|
|
322
|
+
*/
|
|
323
|
+
setView(x, y, z = undefined) {
|
|
324
|
+
this.geoCanvas.view.x = x
|
|
325
|
+
this.geoCanvas.view.y = y
|
|
326
|
+
if (z != undefined) this.geoCanvas.view.z = z
|
|
327
|
+
return this
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** @returns {import('./GeoCanvas.js').View} */
|
|
331
|
+
getView() {
|
|
332
|
+
return this.geoCanvas.view
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** @returns {number} */
|
|
336
|
+
getZoom() {
|
|
337
|
+
return this.geoCanvas.view.z
|
|
338
|
+
}
|
|
339
|
+
/** @param {number} z @returns {this} */
|
|
340
|
+
setZoom(z) {
|
|
341
|
+
this.geoCanvas.view.z = z
|
|
342
|
+
return this
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** @returns {Array.<number|undefined>} */
|
|
346
|
+
getCenterExtent() {
|
|
347
|
+
return this.geoCanvas.getCenterExtent()
|
|
348
|
+
}
|
|
349
|
+
/** @param {Array.<number>} val @returns {this} */
|
|
350
|
+
setCenterExtent(val) {
|
|
351
|
+
this.geoCanvas.setCenterExtent(val)
|
|
352
|
+
return this
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** @returns {Array.<number>} */
|
|
356
|
+
getZoomExtent() {
|
|
357
|
+
return this.geoCanvas.getZoomExtent()
|
|
358
|
+
}
|
|
359
|
+
/** @param {Array.<number>} val @returns {this} */
|
|
360
|
+
setZoomExtent(val) {
|
|
361
|
+
this.geoCanvas.setZoomExtent(val)
|
|
362
|
+
return this
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** @returns {string} */
|
|
366
|
+
getBackgroundColor() {
|
|
367
|
+
return this.geoCanvas.backgroundColor
|
|
368
|
+
}
|
|
369
|
+
/** @param {string} val @returns {this} */
|
|
370
|
+
setBackgroundColor(val) {
|
|
371
|
+
this.geoCanvas.backgroundColor = val
|
|
372
|
+
return this
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Adds a set of zoom buttons to the map
|
|
377
|
+
*
|
|
378
|
+
* @param {object} opts
|
|
379
|
+
* @returns {this}
|
|
380
|
+
*/
|
|
381
|
+
addZoomButtons(opts) {
|
|
382
|
+
// * opts.id
|
|
383
|
+
// * opts.onZoom - custom event handler function
|
|
384
|
+
// * opts.x
|
|
385
|
+
// * opts.y
|
|
386
|
+
// * opts.delta - zoom delta applied on each click
|
|
387
|
+
|
|
388
|
+
this.zoomButtons = new ZoomButtons({
|
|
389
|
+
map: this,
|
|
390
|
+
id: opts?.id || 'gridviz-zoom-buttons-' + this.container.id,
|
|
391
|
+
class: opts?.class,
|
|
392
|
+
x: opts?.x,
|
|
393
|
+
y: opts?.y,
|
|
394
|
+
onZoom: opts?.onZoom,
|
|
395
|
+
delta: opts?.delta || 0.2,
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
return this
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Adds a fullscreen toggle button to the app
|
|
403
|
+
*
|
|
404
|
+
* @param {object} opts
|
|
405
|
+
* @returns {this}
|
|
406
|
+
*/
|
|
407
|
+
addFullscreenButton(opts) {
|
|
408
|
+
// * opts.map - the gridviz map
|
|
409
|
+
// * opts.id
|
|
410
|
+
// * opts.x
|
|
411
|
+
// * opts.y
|
|
412
|
+
|
|
413
|
+
this.fullscreenButton = new FullscreenButton({
|
|
414
|
+
map: this,
|
|
415
|
+
id: opts?.id || 'gridviz-fullscreen-button',
|
|
416
|
+
class: opts?.class,
|
|
417
|
+
x: opts?.x,
|
|
418
|
+
y: opts?.y,
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
return this
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** @returns {this} */
|
|
425
|
+
setViewFromURL() {
|
|
426
|
+
this.geoCanvas.setViewFromURL()
|
|
427
|
+
return this
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* @description Add a resize event observer to the Apps container and update the canvas accordingly
|
|
432
|
+
* @memberof App
|
|
433
|
+
*/
|
|
434
|
+
defineResizeObserver() {
|
|
435
|
+
// listen to resize events
|
|
436
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
437
|
+
let container = this.container
|
|
438
|
+
// make sure canvas has been built
|
|
439
|
+
if (container.clientWidth > 0 && container.clientHeight > 0) {
|
|
440
|
+
// make sure we dont exceed loop limit first
|
|
441
|
+
// see: https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded
|
|
442
|
+
window.requestAnimationFrame(() => {
|
|
443
|
+
if (!Array.isArray(entries) || !entries.length) {
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
// update the map and canvas size
|
|
447
|
+
if (this.h !== container.clientHeight || this.w !== container.clientWidth) {
|
|
448
|
+
this.h = container.clientHeight
|
|
449
|
+
this.w = container.clientWidth
|
|
450
|
+
this.geoCanvas.h = container.clientHeight
|
|
451
|
+
this.geoCanvas.w = container.clientWidth
|
|
452
|
+
this.geoCanvas.canvas.setAttribute('width', '' + this.w)
|
|
453
|
+
this.geoCanvas.canvas.setAttribute('height', '' + this.h)
|
|
454
|
+
// offscreen canvas
|
|
455
|
+
this.geoCanvas.offscreenCanvas.setAttribute('width', '' + this.w)
|
|
456
|
+
this.geoCanvas.offscreenCanvas.setAttribute('height', '' + this.h)
|
|
457
|
+
this.redraw()
|
|
458
|
+
|
|
459
|
+
//update button positions
|
|
460
|
+
// if (this.zoomButtons) this.zoomButtons.node.style.left = this.w - 50 + 'px'
|
|
461
|
+
// if (this.fullscreenButton) this.fullscreenButton.node.style.left = this.w - 50 + 'px'
|
|
462
|
+
}
|
|
463
|
+
})
|
|
464
|
+
}
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
resizeObserver.observe(this.container)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* @description Destroy the map and it's event listeners
|
|
472
|
+
* This should significantly reduce the memory used when creating and destroying gridviz map instances (for example in leaflet-gridviz)
|
|
473
|
+
* @memberof App
|
|
474
|
+
*/
|
|
475
|
+
destroy() {
|
|
476
|
+
// clear layers
|
|
477
|
+
this.layers = []
|
|
478
|
+
this.bgLayers = []
|
|
479
|
+
|
|
480
|
+
// remove event listeners from container
|
|
481
|
+
this.container.removeEventListener('mouseover', this.mouseOverHandler)
|
|
482
|
+
this.container.removeEventListener('mousemove', this.mouseMoveHandler)
|
|
483
|
+
this.container.removeEventListener('mouseout', this.mouseOutHandler)
|
|
484
|
+
|
|
485
|
+
// remove canvas
|
|
486
|
+
this.geoCanvas.canvas.remove()
|
|
487
|
+
|
|
488
|
+
// remove legend
|
|
489
|
+
this.legend?.remove()
|
|
490
|
+
|
|
491
|
+
// remove tooltip
|
|
492
|
+
this.tooltip.tooltip?.remove()
|
|
493
|
+
}
|
|
494
|
+
}
|