gridviz 2.0.28 → 2.0.31
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/dist/gridviz.js +34368 -0
- package/dist/gridviz.min.js +1 -1
- package/package.json +10 -6
- package/src/App.js +654 -0
- package/src/BackgroundLayer.js +182 -0
- package/src/BackgroundLayerWMS.js +131 -0
- package/src/Dataset.js +76 -0
- package/src/DatasetComponent.js +103 -0
- package/src/GeoCanvas.js +419 -0
- package/src/LabelLayer.js +190 -0
- package/src/Layer.js +92 -0
- package/src/Legend.js +86 -0
- package/src/LineLayer.js +150 -0
- package/src/Style.js +114 -0
- package/src/Tooltip.js +150 -0
- package/src/dataset/COGeo.js +24 -0
- package/src/dataset/CSVGrid.js +104 -0
- package/src/dataset/GeoTIFF.js +127 -0
- package/src/dataset/GridTile.js +41 -0
- package/src/dataset/ParquetGrid.js +122 -0
- package/src/dataset/TiledGrid.js +296 -0
- package/src/index.js +177 -0
- package/src/legend/ColorCategoryLegend.js +124 -0
- package/src/legend/ColorDiscreteLegend.js +105 -0
- package/src/legend/ColorLegend.js +125 -0
- package/src/legend/SegmentOrientationLegend.js +87 -0
- package/src/legend/SegmentWidthLegend.js +97 -0
- package/src/legend/SizeLegend.js +141 -0
- package/src/style/CompositionStyle.js +300 -0
- package/src/style/ContourStyle.js +54 -0
- package/src/style/DotDensityStyle.js +156 -0
- package/src/style/JoyPlotStyle.js +135 -0
- package/src/style/KernelSmoothingOldStyle.js +221 -0
- package/src/style/KernelSmoothingStyle.js +115 -0
- package/src/style/KernelSmoothingWGLStyle.js +130 -0
- package/src/style/LegoStyle.js +112 -0
- package/src/style/MosaicStyle.js +107 -0
- package/src/style/NinjaStarStyle.js +124 -0
- package/src/style/PillarStyle.js +230 -0
- package/src/style/SegmentStyle.js +142 -0
- package/src/style/ShapeColorSizeStyle.js +126 -0
- package/src/style/SideCatStyle.js +172 -0
- package/src/style/SideStyle.js +218 -0
- package/src/style/SquareColorCatWGLStyle.js +120 -0
- package/src/style/SquareColorWGLStyle.js +146 -0
- package/src/style/StrokeStyle.js +131 -0
- package/src/style/TanakaStyle.js +160 -0
- package/src/style/TextStyle.js +132 -0
- package/src/utils/Utils.js +35 -0
- package/src/utils/WebGLRectangleColoring.js +106 -0
- package/src/utils/WebGLSquareColoring.js +118 -0
- package/src/utils/WebGLSquareColoringAdvanced.js +230 -0
- package/src/utils/WebGLSquareColoringCatAdvanced.js +133 -0
- package/src/utils/WebGLSquareColoringKS.js +153 -0
- package/src/utils/stretching.js +131 -0
- package/src/utils/webGLUtils.js +71 -0
- package/src/utils/zoomUtils.js +142 -0
package/src/Legend.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
import { select } from 'd3-selection'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A legend container.
|
|
8
|
+
*
|
|
9
|
+
* @author Joseph Davies, Julien Gaffuri
|
|
10
|
+
*/
|
|
11
|
+
export class Legend {
|
|
12
|
+
/**
|
|
13
|
+
* @param {Object} opts
|
|
14
|
+
*/
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
opts = opts || {}
|
|
17
|
+
|
|
18
|
+
/** @type {string} */
|
|
19
|
+
this.id = opts.id
|
|
20
|
+
|
|
21
|
+
//TODO stop using it. Use style method below instead.
|
|
22
|
+
|
|
23
|
+
/** @type {number} @deprecated */
|
|
24
|
+
this.top = opts.top
|
|
25
|
+
/** @type {number} @deprecated */
|
|
26
|
+
this.bottom = opts.bottom
|
|
27
|
+
/** @type {number} @deprecated */
|
|
28
|
+
this.left = opts.left
|
|
29
|
+
/** @type {number} @deprecated */
|
|
30
|
+
this.right = opts.right
|
|
31
|
+
/** @type {string} @deprecated */
|
|
32
|
+
this.background = opts.background || 'none'
|
|
33
|
+
/** @type {string} @deprecated */
|
|
34
|
+
this.padding = opts.padding || '5px'
|
|
35
|
+
/** @type {string} @deprecated */
|
|
36
|
+
this.border = opts.border || '0px'
|
|
37
|
+
/** @type {string} @deprecated */
|
|
38
|
+
this['border-radius'] = opts['border-radius'] || 'none'
|
|
39
|
+
/** @type {string} @deprecated */
|
|
40
|
+
this['box-shadow'] = opts['box-shadow'] || 'none'
|
|
41
|
+
/** @type {string} @deprecated */
|
|
42
|
+
this['font-family'] = opts['font-family'] || 'Helvetica, Arial, sans-serif'
|
|
43
|
+
/** @type {string} @deprecated */
|
|
44
|
+
this.width = opts.width
|
|
45
|
+
/** @type {string} @deprecated */
|
|
46
|
+
this.height = opts.height
|
|
47
|
+
|
|
48
|
+
//the div element
|
|
49
|
+
if (this.id) this.div = select('#' + this.id)
|
|
50
|
+
|
|
51
|
+
if (!this.div || this.div.empty()) {
|
|
52
|
+
this.div = select(document.createElement('div'))
|
|
53
|
+
if (this.id) this.div.attr('id', this.id)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//set style
|
|
57
|
+
this.div.style('background', this.background)
|
|
58
|
+
this.div.style('padding', this.padding)
|
|
59
|
+
this.div.style('border', this.border)
|
|
60
|
+
this.div.style('border-radius', this['border-radius'])
|
|
61
|
+
this.div.style('box-shadow', this['box-shadow'])
|
|
62
|
+
this.div.style('font-family', this['font-family'])
|
|
63
|
+
|
|
64
|
+
if (this.width) this.div.style('width', this.width)
|
|
65
|
+
if (this.height) this.div.style('height', this.height)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Apply a style to the legend div.
|
|
70
|
+
* @param {string} k
|
|
71
|
+
* @param {string} v
|
|
72
|
+
* @returns {this}
|
|
73
|
+
*/
|
|
74
|
+
style(k, v) {
|
|
75
|
+
this.div.style(k, v)
|
|
76
|
+
return this
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {Object} opts
|
|
81
|
+
* @abstract
|
|
82
|
+
*/
|
|
83
|
+
update(opts) {
|
|
84
|
+
console.error('Legend update not implemented yet.')
|
|
85
|
+
}
|
|
86
|
+
}
|
package/src/LineLayer.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
import { json } from 'd3-fetch'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @author Joseph Davies, Julien Gaffuri
|
|
8
|
+
*/
|
|
9
|
+
export class LineLayer {
|
|
10
|
+
/**
|
|
11
|
+
* @param {object} opts
|
|
12
|
+
*/
|
|
13
|
+
constructor(opts) {
|
|
14
|
+
opts = opts || {}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @private
|
|
18
|
+
* @type {string} */
|
|
19
|
+
this.url = opts.url
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A preprocess to run on each feature after loading.
|
|
23
|
+
* It can be used to apply some specific treatment before, format the label data, project coordinates, etc.
|
|
24
|
+
* Return false if the label should not be kept.
|
|
25
|
+
* @private
|
|
26
|
+
* @type {function(object):boolean} */
|
|
27
|
+
this.preprocess = opts.preprocess
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @private
|
|
31
|
+
* @type {function(object,number):string} */
|
|
32
|
+
this.color = opts.color || ((f, zf) => 'gray')
|
|
33
|
+
/**
|
|
34
|
+
* @private
|
|
35
|
+
* @type {function(object,number):number} */
|
|
36
|
+
this.width = opts.width || ((f, zf) => 2)
|
|
37
|
+
/**
|
|
38
|
+
* @private
|
|
39
|
+
* @type {function(object,number):Array.<number>|undefined} */
|
|
40
|
+
this.lineDash = opts.lineDash || ((f, zf) => undefined)
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @private
|
|
44
|
+
* @type {Array.<object> | undefined} */
|
|
45
|
+
this.fs = undefined
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @private
|
|
49
|
+
* @type {string} */
|
|
50
|
+
this.loadingStatus = 'notLoaded'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Draw the layer.
|
|
55
|
+
* @param {import("./GeoCanvas").GeoCanvas} cg The canvas where to draw the layer.
|
|
56
|
+
* @returns {void}
|
|
57
|
+
*/
|
|
58
|
+
draw(cg) {
|
|
59
|
+
//load data, if not done yet.
|
|
60
|
+
if (!this.fs) {
|
|
61
|
+
this.load(cg.redraw)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//TODO sort lines by width ?
|
|
66
|
+
|
|
67
|
+
//zoom factor
|
|
68
|
+
const zf = cg.getZf()
|
|
69
|
+
|
|
70
|
+
//draw in geo coordinates
|
|
71
|
+
cg.setCanvasTransform()
|
|
72
|
+
|
|
73
|
+
for (const f of this.fs) {
|
|
74
|
+
const cs = f.geometry.coordinates
|
|
75
|
+
if (cs.length < 2) continue
|
|
76
|
+
|
|
77
|
+
//set color
|
|
78
|
+
const col = this.color(f, zf)
|
|
79
|
+
if (!col || col == 'none') continue
|
|
80
|
+
cg.ctx.strokeStyle = col
|
|
81
|
+
|
|
82
|
+
//set linewidth
|
|
83
|
+
const wP = this.width(f, zf)
|
|
84
|
+
if (!wP || wP < 0) continue
|
|
85
|
+
cg.ctx.lineWidth = wP * zf
|
|
86
|
+
|
|
87
|
+
//set line dash
|
|
88
|
+
const ldP = this.lineDash(f, zf)
|
|
89
|
+
if (ldP) cg.ctx.setLineDash(ldP)
|
|
90
|
+
|
|
91
|
+
//draw line
|
|
92
|
+
cg.ctx.beginPath()
|
|
93
|
+
cg.ctx.moveTo(cs[0][0], cs[0][1])
|
|
94
|
+
for (let i = 1; i < cs.length; i++) cg.ctx.lineTo(cs[i][0], cs[i][1])
|
|
95
|
+
cg.ctx.stroke()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
//...
|
|
99
|
+
cg.ctx.setLineDash([])
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Load data for labels, from URL this.url
|
|
104
|
+
* @param {function():void} callback
|
|
105
|
+
* @private
|
|
106
|
+
*/
|
|
107
|
+
async load(callback) {
|
|
108
|
+
if (!this.url) {
|
|
109
|
+
console.log('Failed loading boundaries: No URL specified. ' + this.url)
|
|
110
|
+
this.loadingStatus = 'failed'
|
|
111
|
+
this.labels = []
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
//check if data already loaded
|
|
116
|
+
if (this.loadingStatus != 'notLoaded') return
|
|
117
|
+
|
|
118
|
+
//load data
|
|
119
|
+
this.loadingStatus = 'loading'
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const data_ = await json(this.url)
|
|
123
|
+
|
|
124
|
+
/** @type { Array.<object> } */
|
|
125
|
+
const data = data_.features
|
|
126
|
+
|
|
127
|
+
//preprocess/filter
|
|
128
|
+
if (this.preprocess) {
|
|
129
|
+
this.fs = []
|
|
130
|
+
for (const c of data) {
|
|
131
|
+
const b = this.preprocess(c)
|
|
132
|
+
if (b == false) continue
|
|
133
|
+
this.fs.push(c)
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
//store labels
|
|
137
|
+
this.fs = data
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this.loadingStatus = 'loaded'
|
|
141
|
+
|
|
142
|
+
//redraw
|
|
143
|
+
if (callback) callback()
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.log('Failed loading boundaries from ' + this.url)
|
|
146
|
+
this.fs = []
|
|
147
|
+
this.loadingStatus = 'failed'
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
package/src/Style.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Statistics of a set of values
|
|
6
|
+
* @typedef {{min:number,max:number}} Stat */
|
|
7
|
+
|
|
8
|
+
/** @typedef {"square"|"circle"|"diamond"|"donut"|"none"} Shape */
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A style, to show a grid dataset.
|
|
12
|
+
*
|
|
13
|
+
* @abstract
|
|
14
|
+
*
|
|
15
|
+
* @author Joseph Davies, Julien Gaffuri
|
|
16
|
+
*/
|
|
17
|
+
export class Style {
|
|
18
|
+
/**
|
|
19
|
+
* @abstract
|
|
20
|
+
* @param {{filter?:function(import('./Dataset').Cell):boolean,offset?:function(import('./Dataset').Cell,number,number):{dx:number,dy:number},minZoom?:number,maxZoom?:number}} opts
|
|
21
|
+
* minZoom: The minimum zoom level when to show the layer. maxZoom: The maximum zoom level when to show the layer
|
|
22
|
+
*/
|
|
23
|
+
constructor(opts) {
|
|
24
|
+
opts = opts || {}
|
|
25
|
+
|
|
26
|
+
/** A filter function to apply to the cell list, to filter out some cells not to be drawn (such as for example the cells with value=0).
|
|
27
|
+
* @protected
|
|
28
|
+
* @type {function(import('./Dataset').Cell):boolean} */
|
|
29
|
+
this.filter = opts.filter || (() => true)
|
|
30
|
+
|
|
31
|
+
/** An offset. This is to alter the position of all symbols in a given direction. In geographical unit.
|
|
32
|
+
* @protected
|
|
33
|
+
* @type {function(import('./Dataset').Cell,number,number):{dx:number,dy:number}} */
|
|
34
|
+
this.offset = opts.offset || ((c, r, zf) => ({ dx: 0, dy: 0 }))
|
|
35
|
+
|
|
36
|
+
/** The minimum zoom factor: Below this level, the layer is not shown.
|
|
37
|
+
* @type {number}
|
|
38
|
+
* */
|
|
39
|
+
this.minZoom = opts.minZoom || 0
|
|
40
|
+
|
|
41
|
+
/** The maximum zoom factor: Above this level, the layer is not shown.
|
|
42
|
+
* @type {number}
|
|
43
|
+
* */
|
|
44
|
+
this.maxZoom = opts.maxZoom || Infinity
|
|
45
|
+
|
|
46
|
+
//ensure acceptable values for the zoom limits.
|
|
47
|
+
if (this.minZoom >= this.maxZoom)
|
|
48
|
+
throw new Error('Unexpected zoom limits for layer. Zoom min should be smaller than zoom max.')
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @public
|
|
52
|
+
* @type {Array.<import("./Legend").Legend>} */
|
|
53
|
+
this.legends = []
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Draw cells.
|
|
58
|
+
*
|
|
59
|
+
* @param {Array.<import('./Dataset').Cell>} cells The cells to draw.
|
|
60
|
+
* @param {number} resolution Their resolution (in geographic unit)
|
|
61
|
+
* @param {import("./GeoCanvas").GeoCanvas} cg The canvas where to draw them.
|
|
62
|
+
* @abstract
|
|
63
|
+
*/
|
|
64
|
+
draw(cells, resolution, cg) {
|
|
65
|
+
throw new Error('Method draw not implemented.')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//getters and setters
|
|
69
|
+
|
|
70
|
+
/** @returns {function(import('./Dataset').Cell,number,number):{dx:number,dy:number}} */
|
|
71
|
+
getOffset() {
|
|
72
|
+
return this.offset
|
|
73
|
+
}
|
|
74
|
+
/** @param {function(import('./Dataset').Cell,number,number):{dx:number,dy:number}} val @returns {this} */
|
|
75
|
+
setOffset(val) {
|
|
76
|
+
this.offset = val
|
|
77
|
+
return this
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Hide all legend elements of the style, if any
|
|
81
|
+
* @param {object} opts
|
|
82
|
+
* @returns {this} */
|
|
83
|
+
updateLegends(opts) {
|
|
84
|
+
for (const lg of this.legends) lg.update(opts)
|
|
85
|
+
return this
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Compute some statistics on a value of some cells.
|
|
90
|
+
* This is used to define how to draw specifically the cells within the view.
|
|
91
|
+
* TODO: compute median ?
|
|
92
|
+
*
|
|
93
|
+
* @param {Array.<import('./Dataset').Cell>} cells
|
|
94
|
+
* @param {function(import('./Dataset').Cell):number} valFun
|
|
95
|
+
* @param {boolean} ignoreZeros
|
|
96
|
+
* @returns {Stat | undefined}
|
|
97
|
+
*/
|
|
98
|
+
static getStatistics(cells, valFun, ignoreZeros) {
|
|
99
|
+
if (!cells || cells.length == 0) return undefined
|
|
100
|
+
let min = Infinity
|
|
101
|
+
let max = -Infinity
|
|
102
|
+
//let sum = 0
|
|
103
|
+
//let nb = 0
|
|
104
|
+
for (const cell of cells) {
|
|
105
|
+
const v = +valFun(cell)
|
|
106
|
+
if (ignoreZeros && !v) continue
|
|
107
|
+
if (v < min) min = v
|
|
108
|
+
if (v > max) max = v
|
|
109
|
+
//sum += v
|
|
110
|
+
//nb++
|
|
111
|
+
}
|
|
112
|
+
return { min: min, max: max }
|
|
113
|
+
}
|
|
114
|
+
}
|
package/src/Tooltip.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
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
|
+
*/
|
|
11
|
+
export class Tooltip {
|
|
12
|
+
/**
|
|
13
|
+
* @param {object} opts
|
|
14
|
+
*/
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
opts = opts || {}
|
|
17
|
+
|
|
18
|
+
/** @type {string} */
|
|
19
|
+
this.div = opts.div || 'tooltip_eurostat'
|
|
20
|
+
/** @type {string} */
|
|
21
|
+
this.maxWidth = opts.maxWidth || '20em'
|
|
22
|
+
/** @type {string} */
|
|
23
|
+
this.fontSize = opts.fontSize || '1.2em'
|
|
24
|
+
/** @type {string} */
|
|
25
|
+
this.background = opts.background || 'white'
|
|
26
|
+
/** @type {string} */
|
|
27
|
+
this.padding = opts.padding || '5px'
|
|
28
|
+
/** @type {string} */
|
|
29
|
+
this.border = opts.border || '0px'
|
|
30
|
+
/** @type {string} */
|
|
31
|
+
this['border-radius'] = opts['border-radius'] || '5px'
|
|
32
|
+
/** @type {string} */
|
|
33
|
+
this['box-shadow'] = opts['box-shadow'] || '5px 5px 5px grey'
|
|
34
|
+
/** @type {string} */
|
|
35
|
+
this['font-family'] = opts['font-family'] || 'Helvetica, Arial, sans-serif'
|
|
36
|
+
|
|
37
|
+
/** @type {number} */
|
|
38
|
+
this.transitionDuration = opts.transitionDuration || 100
|
|
39
|
+
/** @type {number} */
|
|
40
|
+
this.xOffset = opts.xOffset || 30
|
|
41
|
+
/** @type {number} */
|
|
42
|
+
this.yOffset = opts.yOffset || 20
|
|
43
|
+
/** @type {number} */ // e.g. to prevent mouse cursor covering cell being highlighted
|
|
44
|
+
this.yMouseOffset = opts.yMouseOffset || 0
|
|
45
|
+
/** @type {number} */
|
|
46
|
+
this.xMouseOffset = opts.xMouseOffset || 0
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @private
|
|
50
|
+
* @type {import("d3-selection").Selection} */
|
|
51
|
+
this.tooltip = select('#' + this.div)
|
|
52
|
+
if (this.tooltip.empty()) this.tooltip = select('body').append('div').attr('id', this.div)
|
|
53
|
+
|
|
54
|
+
//initialise
|
|
55
|
+
this.tooltip.style('max-width', this.maxWidth)
|
|
56
|
+
this.tooltip.style('overflow', 'hidden')
|
|
57
|
+
this.tooltip.style('font-size', this.fontSize)
|
|
58
|
+
this.tooltip.style('background', this.background)
|
|
59
|
+
this.tooltip.style('padding', this.padding)
|
|
60
|
+
this.tooltip.style('border', this.border)
|
|
61
|
+
this.tooltip.style('border-radius', this['border-radius'])
|
|
62
|
+
this.tooltip.style('box-shadow', this['box-shadow'])
|
|
63
|
+
this.tooltip.style('font-family', this['font-family'])
|
|
64
|
+
this.tooltip.style('position', 'absolute')
|
|
65
|
+
this.tooltip.style('pointer-events', 'none')
|
|
66
|
+
this.tooltip.style('opacity', '0')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Show the tooltip */
|
|
70
|
+
show() {
|
|
71
|
+
// @ts-ignore
|
|
72
|
+
this.tooltip.transition().duration(this.transitionDuration).style('opacity', 1)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Hide the tooltip */
|
|
76
|
+
hide() {
|
|
77
|
+
// @ts-ignore
|
|
78
|
+
this.tooltip.transition().duration(this.transitionDuration).style('opacity', 0)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Set the content of the tooltip.
|
|
83
|
+
* @param {string} html
|
|
84
|
+
*/
|
|
85
|
+
html(html) {
|
|
86
|
+
this.tooltip.html(html)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Set the position of the tooltip at the mouse event position.
|
|
91
|
+
* @param {MouseEvent} event
|
|
92
|
+
*/
|
|
93
|
+
setPosition(event) {
|
|
94
|
+
this.tooltip
|
|
95
|
+
.style('left', event.pageX + this.xOffset + 'px')
|
|
96
|
+
.style('top', event.pageY - this.yOffset + 'px')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/*
|
|
100
|
+
my.mouseover = function (event, html) {
|
|
101
|
+
if (html) my.html(html);
|
|
102
|
+
my.setPosition(event);
|
|
103
|
+
my.show()
|
|
104
|
+
//this.ensureTooltipOnScreen();
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
my.mousemove = function (event) {
|
|
108
|
+
my.setPosition(event);
|
|
109
|
+
//this.ensureTooltipOnScreen();
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
my.mouseout = function () {
|
|
113
|
+
my.hide();
|
|
114
|
+
};*/
|
|
115
|
+
|
|
116
|
+
style(k, v) {
|
|
117
|
+
if (arguments.length == 1) return this.tooltip.style(k)
|
|
118
|
+
this.tooltip.style(k, v)
|
|
119
|
+
return this
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
attr(k, v) {
|
|
123
|
+
if (arguments.length == 1) return this.tooltip.attr(k)
|
|
124
|
+
this.tooltip.attr(k, v)
|
|
125
|
+
return this
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @function ensureTooltipOnScreen
|
|
130
|
+
* @description Prevents the tooltip from overflowing off screen
|
|
131
|
+
*/
|
|
132
|
+
/*my.ensureTooltipOnScreen = function () {
|
|
133
|
+
// TODO: parent needs to be the all-encompassing container, not the map SVG id otherwise it just uses the last SVG which will be an inset SVG.
|
|
134
|
+
let parent = document.getElementById(config.parentContainerId);
|
|
135
|
+
let bbox = parent.getBBox();
|
|
136
|
+
let parentWidth = bbox.width;
|
|
137
|
+
let parentHeight = bbox.height;
|
|
138
|
+
let node = tooltip.node();
|
|
139
|
+
//too far right
|
|
140
|
+
if (node.offsetLeft > parentWidth - node.clientWidth) {
|
|
141
|
+
node.style.left = node.offsetLeft - (node.clientWidth + config.xOffset * 2) + "px";
|
|
142
|
+
|
|
143
|
+
}
|
|
144
|
+
//too far down
|
|
145
|
+
if (node.offsetTop + node.clientHeight > parentHeight) {
|
|
146
|
+
node.style.top = node.offsetTop - (node.clientHeight + config.yOffset * 2) + "px";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
}*/
|
|
150
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
import { DatasetComponent } from '../DatasetComponent.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @todo
|
|
8
|
+
* A dataset composed of a COGeo file.
|
|
9
|
+
* @see https://www.cogeo.org/
|
|
10
|
+
*
|
|
11
|
+
* @author Joseph Davies, Julien Gaffuri
|
|
12
|
+
*/
|
|
13
|
+
export class COGeo extends DatasetComponent {
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @param {String} url
|
|
17
|
+
* @param {{preprocess?:(function(import("../Dataset").Cell):boolean)}} opts
|
|
18
|
+
*/
|
|
19
|
+
constructor(url, opts = {}) {
|
|
20
|
+
super(url, 0, opts)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//TODO Use https://geotiffjs.github.io/
|
|
24
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
/** @typedef {{ dims: object, crs: string, tileSizeCell: number, originPoint: {x:number,y:number}, resolutionGeo: number, tilingBounds:import("../Dataset").Envelope }} GridInfo */
|
|
5
|
+
|
|
6
|
+
import { csv } from 'd3-fetch'
|
|
7
|
+
import { DatasetComponent } from '../DatasetComponent.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A dataset composed of a single CSV file (not tiled).
|
|
11
|
+
*
|
|
12
|
+
* @author Joseph Davies, Julien Gaffuri
|
|
13
|
+
*/
|
|
14
|
+
export class CSVGrid extends DatasetComponent {
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} url The URL of the dataset.
|
|
17
|
+
* @param {number} resolution The dataset resolution in geographical unit.
|
|
18
|
+
* @param {{preprocess?:(function(import("../Dataset").Cell):boolean)}} opts
|
|
19
|
+
*/
|
|
20
|
+
constructor(url, resolution, opts = {}) {
|
|
21
|
+
super(url, resolution, opts)
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @private
|
|
25
|
+
* @type {Array.<import("../Dataset").Cell>} */
|
|
26
|
+
this.cells = []
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @type {string}
|
|
30
|
+
* @private */
|
|
31
|
+
this.infoLoadingStatus = 'notLoaded'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Request data within a geographic envelope.
|
|
36
|
+
*
|
|
37
|
+
* @param {import("../Dataset").Envelope|undefined} e
|
|
38
|
+
* @param {function():void} redraw
|
|
39
|
+
*/
|
|
40
|
+
getData(e, redraw) {
|
|
41
|
+
//check if data already loaded
|
|
42
|
+
if (this.infoLoadingStatus != 'notLoaded') return this
|
|
43
|
+
|
|
44
|
+
//load data
|
|
45
|
+
this.infoLoadingStatus = 'loading'
|
|
46
|
+
;(async () => {
|
|
47
|
+
try {
|
|
48
|
+
const data = await csv(this.url)
|
|
49
|
+
|
|
50
|
+
//convert coordinates in numbers
|
|
51
|
+
for (const c of data) {
|
|
52
|
+
c.x = +c.x
|
|
53
|
+
c.y = +c.y
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//preprocess/filter
|
|
57
|
+
if (this.preprocess) {
|
|
58
|
+
this.cells = []
|
|
59
|
+
for (const c of data) {
|
|
60
|
+
const b = this.preprocess(c)
|
|
61
|
+
if (b == false) continue
|
|
62
|
+
this.cells.push(c)
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
this.cells = data
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//TODO check if redraw is necessary
|
|
69
|
+
//that is if the dataset belongs to a layer which is visible at the current zoom level
|
|
70
|
+
|
|
71
|
+
//execute the callback, usually a draw function
|
|
72
|
+
if (redraw) redraw()
|
|
73
|
+
|
|
74
|
+
this.infoLoadingStatus = 'loaded'
|
|
75
|
+
} catch (error) {
|
|
76
|
+
//mark as failed
|
|
77
|
+
this.infoLoadingStatus = 'failed'
|
|
78
|
+
this.cells = []
|
|
79
|
+
}
|
|
80
|
+
})()
|
|
81
|
+
|
|
82
|
+
return this
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Fill the view cache with all cells which are within a geographical envelope.
|
|
87
|
+
*
|
|
88
|
+
* @param {import("../Dataset").Envelope} extGeo
|
|
89
|
+
* @returns {void}
|
|
90
|
+
*/
|
|
91
|
+
updateViewCache(extGeo) {
|
|
92
|
+
//data not loaded yet
|
|
93
|
+
if (!this.cells) return
|
|
94
|
+
|
|
95
|
+
this.cellsViewCache = []
|
|
96
|
+
for (const cell of this.cells) {
|
|
97
|
+
if (+cell.x + this.resolution < extGeo.xMin) continue
|
|
98
|
+
if (+cell.x - this.resolution > extGeo.xMax) continue
|
|
99
|
+
if (+cell.y + this.resolution < extGeo.yMin) continue
|
|
100
|
+
if (+cell.y - this.resolution > extGeo.yMax) continue
|
|
101
|
+
this.cellsViewCache.push(cell)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|