gridviz 2.1.17 → 3.0.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.
- package/README.md +2 -3
- package/dist/gridviz.js +8659 -9294
- package/dist/gridviz.min.js +1 -1
- package/package.json +3 -2
- package/src/button/Button.js +14 -15
- package/src/button/FullscreenButton.js +14 -14
- package/src/button/ZoomButtons.js +2 -2
- package/src/{DatasetComponent.js → core/Dataset.js} +33 -13
- package/src/core/Drawable.js +60 -0
- package/src/{GeoCanvas.js → core/GeoCanvas.js} +65 -155
- package/src/core/Layer.js +24 -0
- package/src/{Legend.js → core/Legend.js} +24 -1
- package/src/core/Map.js +474 -0
- package/src/core/MultiResolutionDataset.js +96 -0
- package/src/core/Style.js +84 -0
- package/src/dataset/COGeo.js +5 -5
- package/src/dataset/CSVGrid.js +43 -41
- package/src/dataset/GeoTIFF.js +9 -14
- package/src/dataset/{LGrid.js → JSGrid.js} +9 -11
- package/src/dataset/TiledGrid.js +68 -45
- package/src/index.js +22 -29
- package/src/{BackgroundLayer.js → layer/BackgroundLayer.js} +27 -52
- package/src/{BackgroundLayerWMS.js → layer/BackgroundLayerWMS.js} +21 -45
- package/src/{LineLayer.js → layer/GeoJSONLayer.js} +22 -23
- package/src/layer/GridLayer.js +160 -0
- package/src/{LabelLayer.js → layer/LabelLayer.js} +25 -23
- package/src/legend/ColorCategoryLegend.js +9 -24
- package/src/legend/ColorDiscreteLegend.js +20 -24
- package/src/legend/ColorLegend.js +21 -33
- package/src/legend/OrientationLegend.js +90 -0
- package/src/legend/SizeLegend.js +153 -72
- package/src/style/CompositionStyle.js +71 -84
- package/src/style/ContourStyle.js +2 -36
- package/src/style/DotDensityStyle.js +50 -73
- package/src/style/IsoFenceStyle.js +59 -70
- package/src/style/JoyPlotStyle.js +41 -48
- package/src/style/LegoStyle.js +4 -4
- package/src/style/MosaicStyle.js +30 -39
- package/src/style/NinjaStarStyle.js +45 -68
- package/src/style/PillarStyle.js +70 -93
- package/src/style/SegmentStyle.js +37 -75
- package/src/style/ShapeColorSizeStyle.js +49 -98
- package/src/style/SideCategoryStyle.js +112 -0
- package/src/style/SideStyle.js +99 -133
- package/src/style/{SquareColorCatWGLStyle.js → SquareColorCategoryWebGLStyle.js} +28 -41
- package/src/style/{SquareColorWGLStyle.js → SquareColorWebGLStyle.js} +28 -49
- package/src/style/StrokeStyle.js +43 -70
- package/src/style/TanakaStyle.js +34 -132
- package/src/style/TextStyle.js +43 -64
- package/src/style/TimeSeriesStyle.js +44 -75
- package/src/utils/WebGLSquareColoringAdvanced.js +4 -4
- package/src/utils/scale.js +217 -0
- package/src/utils/stretching.js +86 -129
- package/src/utils/{Utils.js → utils.js} +15 -0
- package/src/utils/webGLUtils.js +4 -5
- package/src/utils/zoomUtils.js +30 -30
- package/src/App.js +0 -760
- package/src/Dataset.js +0 -76
- package/src/Layer.js +0 -103
- package/src/Style.js +0 -131
- package/src/dataset/GridTile.js +0 -41
- package/src/legend/SegmentOrientationLegend.js +0 -87
- package/src/legend/SegmentWidthLegend.js +0 -101
- package/src/style/CompositionStyle_.js +0 -293
- package/src/style/ShapeColorSizeStyle_.js +0 -108
- package/src/style/SideCatStyle.js +0 -172
- /package/src/{Tooltip.js → core/Tooltip.js} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gridviz",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Visualization tool for gridded statistics",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"statistics",
|
|
@@ -49,10 +49,11 @@
|
|
|
49
49
|
"src/**/*.js"
|
|
50
50
|
],
|
|
51
51
|
"dependencies": {
|
|
52
|
+
"d3-array": "^3.2.4",
|
|
52
53
|
"d3-color": "^3.1.0",
|
|
53
54
|
"d3-fetch": "^3.0.1",
|
|
54
|
-
"d3-format": "^3.1.0",
|
|
55
55
|
"d3-random": "^3.0.1",
|
|
56
|
+
"d3-scale": "^4.0.2",
|
|
56
57
|
"d3-selection": "^3.0.0",
|
|
57
58
|
"d3-zoom": "^3.0.0"
|
|
58
59
|
},
|
package/src/button/Button.js
CHANGED
|
@@ -16,11 +16,10 @@ export class Button {
|
|
|
16
16
|
* opts.x
|
|
17
17
|
* opts.y
|
|
18
18
|
*/
|
|
19
|
-
constructor(opts) {
|
|
20
|
-
opts = opts || {}
|
|
19
|
+
constructor(opts = {}) {
|
|
21
20
|
|
|
22
|
-
this.
|
|
23
|
-
this.parentNode = opts.parentNode || opts.
|
|
21
|
+
this.map = opts.map
|
|
22
|
+
this.parentNode = opts.parentNode || opts.map.container
|
|
24
23
|
|
|
25
24
|
// the div element
|
|
26
25
|
if (this.id) this.div = select('#' + this.id)
|
|
@@ -30,22 +29,22 @@ export class Button {
|
|
|
30
29
|
if (this.id) this.div.attr('id', this.id)
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
if (opts.title) this.div.attr('title',opts.title)
|
|
34
|
-
if (opts.class) this.div.attr('class',opts.class)
|
|
32
|
+
if (opts.title) this.div.attr('title', opts.title)
|
|
33
|
+
if (opts.class) this.div.attr('class', opts.class)
|
|
35
34
|
|
|
36
35
|
// add events
|
|
37
36
|
if (opts.onClickFunction) this.div.on('click', opts.onClickFunction)
|
|
38
37
|
|
|
39
38
|
//set styles
|
|
40
|
-
this.style('box-shadow','0 7px 8px rgba(0,47,103,.08), 0 0 22px rgba(0,47,103,.04), 0 12px 17px rgba(0,47,103,.04), 0 -4px 4px rgba(0,47,103,.04)') //.ecl-u-shadow-3
|
|
41
|
-
this.style('background-color','#ffffff')
|
|
42
|
-
this.style('position'
|
|
43
|
-
this.style('cursor'
|
|
44
|
-
this.style('display'
|
|
45
|
-
this.style('justify-content'
|
|
46
|
-
this.style('align-items'
|
|
47
|
-
this.style('width'
|
|
48
|
-
this.style('height'
|
|
39
|
+
this.style('box-shadow', '0 7px 8px rgba(0,47,103,.08), 0 0 22px rgba(0,47,103,.04), 0 12px 17px rgba(0,47,103,.04), 0 -4px 4px rgba(0,47,103,.04)') //.ecl-u-shadow-3
|
|
40
|
+
this.style('background-color', '#ffffff')
|
|
41
|
+
this.style('position', 'absolute')
|
|
42
|
+
this.style('cursor', 'pointer')
|
|
43
|
+
this.style('display', 'flex')
|
|
44
|
+
this.style('justify-content', 'center')
|
|
45
|
+
this.style('align-items', 'center')
|
|
46
|
+
this.style('width', '30px')
|
|
47
|
+
this.style('height', '30px')
|
|
49
48
|
// this.style(padding , '4px'
|
|
50
49
|
|
|
51
50
|
|
|
@@ -43,9 +43,9 @@ export class FullscreenButton extends Button {
|
|
|
43
43
|
</svg>
|
|
44
44
|
`
|
|
45
45
|
|
|
46
|
-
//save initial
|
|
47
|
-
this.defaultHeight = this.
|
|
48
|
-
this.defaultWidth = this.
|
|
46
|
+
//save initial map dimensions
|
|
47
|
+
this.defaultHeight = this.map.h
|
|
48
|
+
this.defaultWidth = this.map.w
|
|
49
49
|
|
|
50
50
|
// event handler
|
|
51
51
|
this.div.on('click', (e) => {
|
|
@@ -73,21 +73,21 @@ export class FullscreenButton extends Button {
|
|
|
73
73
|
|
|
74
74
|
onClickFunction(e) {
|
|
75
75
|
if (this.isFullscreen) {
|
|
76
|
-
this.closeFullscreen(this.
|
|
76
|
+
this.closeFullscreen(this.map.container)
|
|
77
77
|
//resize canvas to default
|
|
78
|
-
this.
|
|
79
|
-
this.
|
|
80
|
-
this.
|
|
81
|
-
this.
|
|
82
|
-
this.
|
|
83
|
-
this.
|
|
84
|
-
this.
|
|
78
|
+
this.map.h = this.defaultHeight
|
|
79
|
+
this.map.w = this.defaultWidth
|
|
80
|
+
this.map.geoCanvas.h = this.defaultHeight
|
|
81
|
+
this.map.geoCanvas.w = this.defaultWidth
|
|
82
|
+
this.map.geoCanvas.canvas.setAttribute('width', '' + this.defaultWidth)
|
|
83
|
+
this.map.geoCanvas.canvas.setAttribute('height', '' + this.defaultHeight)
|
|
84
|
+
this.map.redraw()
|
|
85
85
|
this.isFullscreen = false
|
|
86
86
|
} else {
|
|
87
|
-
this.openFullscreen(this.
|
|
87
|
+
this.openFullscreen(this.map.container)
|
|
88
88
|
//resize canvas to fullscreen
|
|
89
|
-
this.
|
|
90
|
-
this.
|
|
89
|
+
this.map.h = window.screen.height
|
|
90
|
+
this.map.w = window.screen.width
|
|
91
91
|
this.isFullscreen = true
|
|
92
92
|
}
|
|
93
93
|
}
|
|
@@ -83,13 +83,13 @@ export class ZoomButtons extends Button {
|
|
|
83
83
|
|
|
84
84
|
/* Zoom in */
|
|
85
85
|
zoomIn(e) {
|
|
86
|
-
this.
|
|
86
|
+
this.map.setZoom(this.map.getZoom() * (1 - this.delta)).redraw()
|
|
87
87
|
if (this.onZoom) this.onZoom(e)
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/* Zoom out */
|
|
91
91
|
zoomOut(e) {
|
|
92
|
-
this.
|
|
92
|
+
this.map.setZoom(this.map.getZoom() * (1 + this.delta)).redraw()
|
|
93
93
|
if (this.onZoom) this.onZoom(e)
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
//@ts-check
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* A grid cell.
|
|
6
|
+
* @typedef {{x: number, y: number}} Cell */
|
|
7
|
+
|
|
4
8
|
/**
|
|
5
9
|
* A dataset component, of grid cells.
|
|
6
10
|
* @abstract
|
|
7
11
|
*
|
|
8
12
|
* @author Joseph Davies, Julien Gaffuri
|
|
9
13
|
*/
|
|
10
|
-
export class
|
|
14
|
+
export class Dataset {
|
|
11
15
|
/**
|
|
16
|
+
* @param {import("./Map.js").Map} map The map.
|
|
12
17
|
* @param {string} url The URL of the dataset.
|
|
13
18
|
* @param {number} resolution The dataset resolution, in the CRS geographical unit.
|
|
14
|
-
* @param {{preprocess?:function(
|
|
19
|
+
* @param {{preprocess?:function(Cell):boolean}} opts
|
|
15
20
|
* @abstract
|
|
16
21
|
*/
|
|
17
|
-
constructor(url, resolution, opts = {}) {
|
|
18
|
-
|
|
22
|
+
constructor(map, url, resolution, opts = {}) {
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The map.
|
|
26
|
+
* @protected
|
|
27
|
+
* @type {import("./Map.js").Map} */
|
|
28
|
+
this.map = map
|
|
19
29
|
|
|
20
30
|
/**
|
|
21
31
|
* The url of the dataset.
|
|
@@ -31,12 +41,12 @@ export class DatasetComponent {
|
|
|
31
41
|
|
|
32
42
|
/**
|
|
33
43
|
* A preprocess to run on each cell after loading. It can be used to apply some specific treatment before or compute a new column. And also to determine which cells to keep after loading.
|
|
34
|
-
* @type {(function(
|
|
44
|
+
* @type {(function(Cell):boolean )| undefined } */
|
|
35
45
|
this.preprocess = opts.preprocess || undefined
|
|
36
46
|
|
|
37
47
|
/** The cells within the view
|
|
38
48
|
* @protected
|
|
39
|
-
* @type {Array.<
|
|
49
|
+
* @type {Array.<Cell>} */
|
|
40
50
|
this.cellsViewCache = []
|
|
41
51
|
}
|
|
42
52
|
|
|
@@ -44,18 +54,17 @@ export class DatasetComponent {
|
|
|
44
54
|
* Request data within a geographic envelope.
|
|
45
55
|
*
|
|
46
56
|
* @abstract
|
|
47
|
-
* @param {import("./
|
|
48
|
-
* @param {function():void} callback
|
|
57
|
+
* @param {import("./GeoCanvas").Envelope|undefined} extGeo
|
|
49
58
|
* @returns {this}
|
|
50
59
|
*/
|
|
51
|
-
getData(extGeo
|
|
60
|
+
getData(extGeo = undefined) {
|
|
52
61
|
throw new Error('Method getData not implemented.')
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
/**
|
|
56
65
|
* Fill the view cache with all cells which are within a geographical envelope.
|
|
57
66
|
* @abstract
|
|
58
|
-
* @param {import("./
|
|
67
|
+
* @param {import("./GeoCanvas").Envelope} extGeo The view geographical envelope.
|
|
59
68
|
* @returns {void}
|
|
60
69
|
*/
|
|
61
70
|
updateViewCache(extGeo) {
|
|
@@ -66,8 +75,8 @@ export class DatasetComponent {
|
|
|
66
75
|
* Get a cell under a given position, if any.
|
|
67
76
|
*
|
|
68
77
|
* @param {{x:number,y:number}} posGeo
|
|
69
|
-
* @param {Array.<
|
|
70
|
-
* @returns {
|
|
78
|
+
* @param {Array.<Cell>} cells Some cells from the dataset (a subset if necessary, usually the view cache).
|
|
79
|
+
* @returns {Cell|undefined}
|
|
71
80
|
*/
|
|
72
81
|
getCellFromPosition(posGeo, cells) {
|
|
73
82
|
//compute candidate cell position
|
|
@@ -106,8 +115,19 @@ export class DatasetComponent {
|
|
|
106
115
|
return this.resolution
|
|
107
116
|
}
|
|
108
117
|
|
|
109
|
-
/** @returns {Array.<
|
|
118
|
+
/** @returns {Array.<Cell>} */
|
|
110
119
|
getViewCache() {
|
|
111
120
|
return this.cellsViewCache
|
|
112
121
|
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Return the relevant dataset for a specified zoom.
|
|
125
|
+
* @param {number} z
|
|
126
|
+
* @param {number} minPixelsPerCell
|
|
127
|
+
* @returns {Dataset|undefined}
|
|
128
|
+
* */
|
|
129
|
+
getDataset(z, minPixelsPerCell) {
|
|
130
|
+
return this
|
|
131
|
+
}
|
|
132
|
+
|
|
113
133
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This is an abstract class used to group elements shared between Layer and Style classes.
|
|
6
|
+
*
|
|
7
|
+
* @abstract
|
|
8
|
+
* @author Joseph Davies, Julien Gaffuri
|
|
9
|
+
*/
|
|
10
|
+
export class Drawable {
|
|
11
|
+
/**
|
|
12
|
+
* @param {object} opts
|
|
13
|
+
*/
|
|
14
|
+
constructor(opts) {
|
|
15
|
+
opts = opts || {}
|
|
16
|
+
|
|
17
|
+
/** A function specifying if the element should be visible or not.
|
|
18
|
+
* The function parameter is the zoom level.
|
|
19
|
+
* @type {function(number):boolean} */
|
|
20
|
+
this.visible = opts.visible
|
|
21
|
+
|
|
22
|
+
/** A function returning the alpha (transparency/opacity), between 0.0 (fully transparent) and 1.0 (fully opaque).
|
|
23
|
+
* The function parameter is the zoom level.
|
|
24
|
+
* (see CanvasRenderingContext2D: globalAlpha property)
|
|
25
|
+
* @type {(function(number):number)|undefined} */
|
|
26
|
+
this.alpha = opts.alpha
|
|
27
|
+
|
|
28
|
+
/** A function returning the blend operation.
|
|
29
|
+
* The function parameter is the zoom level.
|
|
30
|
+
* (see CanvasRenderingContext2D: globalCompositeOperation property)
|
|
31
|
+
* @type {function(number):GlobalCompositeOperation} */
|
|
32
|
+
this.blendOperation = opts.blendOperation || (z => "source-over")
|
|
33
|
+
|
|
34
|
+
/** @type {(function(number):string)|undefined} */
|
|
35
|
+
this.filterColor = opts.filterColor // (z) => "#eee7"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Draw layer filter.
|
|
40
|
+
*
|
|
41
|
+
* @param {import("./GeoCanvas.js").GeoCanvas} geoCanvas The canvas where to draw the layer.
|
|
42
|
+
* @returns {void}
|
|
43
|
+
* @abstract
|
|
44
|
+
*/
|
|
45
|
+
drawFilter(geoCanvas) {
|
|
46
|
+
//no filter: return
|
|
47
|
+
if (!this.filterColor) return
|
|
48
|
+
|
|
49
|
+
//get filter
|
|
50
|
+
const fc = this.filterColor(geoCanvas.view.z)
|
|
51
|
+
|
|
52
|
+
//no filter: return
|
|
53
|
+
if (!fc || fc == 'none') return
|
|
54
|
+
|
|
55
|
+
//draw filter
|
|
56
|
+
geoCanvas.ctx.fillStyle = fc
|
|
57
|
+
geoCanvas.ctx.fillRect(0, 0, geoCanvas.w, geoCanvas.h)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
}
|
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
/** @typedef { {xMin: number, xMax: number, yMin: number, yMax: number} } Envelope */
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* A viewshed.
|
|
8
|
+
* @typedef {{x: number, y: number, z: number}} View */
|
|
9
|
+
|
|
6
10
|
import { select } from 'd3-selection'
|
|
7
|
-
import { zoom, zoomIdentity } from 'd3-zoom'
|
|
11
|
+
import { zoom as d3zoom, zoomIdentity } from 'd3-zoom'
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
14
|
* A HTML canvas for geo data display, enhanced with zoom and pan capabilities.
|
|
@@ -15,11 +19,12 @@ export class GeoCanvas {
|
|
|
15
19
|
/**
|
|
16
20
|
* @constructor
|
|
17
21
|
* @param {HTMLCanvasElement} canvas
|
|
18
|
-
* @param {
|
|
19
|
-
* @param {number}
|
|
22
|
+
* @param {number} x The x coordinate of the view
|
|
23
|
+
* @param {number} y The y coordinate of the view
|
|
24
|
+
* @param {number} z The zoom level of the view (pixel size, in ground m)
|
|
20
25
|
* @param {object} opts
|
|
21
26
|
*/
|
|
22
|
-
constructor(canvas,
|
|
27
|
+
constructor(canvas, x = 0, y = 0, z = 0, opts = undefined) {
|
|
23
28
|
this.opts = opts || {}
|
|
24
29
|
|
|
25
30
|
/** @type {HTMLCanvasElement} */
|
|
@@ -38,24 +43,22 @@ export class GeoCanvas {
|
|
|
38
43
|
/**@type {CanvasRenderingContext2D} */
|
|
39
44
|
this.ctx = ctx
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
/** @type {number} */
|
|
46
|
-
this.zf = zf
|
|
46
|
+
/**
|
|
47
|
+
* z: pixel size, in m/pix
|
|
48
|
+
* @type {View} */
|
|
49
|
+
this.view = { x: x, y: y, z: z }
|
|
47
50
|
|
|
48
51
|
/** Background color.
|
|
49
52
|
* @type {string} */
|
|
50
53
|
this.backgroundColor = opts.backgroundColor || 'white'
|
|
51
54
|
|
|
52
|
-
/** @type {function():void} */
|
|
55
|
+
/** @type {function(object|undefined):void} */
|
|
53
56
|
this.onZoomStartFun = opts.onZoomStartFun
|
|
54
57
|
|
|
55
|
-
/** @type {function():void} */
|
|
58
|
+
/** @type {function(object|undefined):void} */
|
|
56
59
|
this.onZoomEndFun = opts.onZoomEndFun
|
|
57
60
|
|
|
58
|
-
/** @type {function():void} */
|
|
61
|
+
/** @type {function(object|undefined):void} */
|
|
59
62
|
this.onZoomFun = opts.onZoomFun
|
|
60
63
|
|
|
61
64
|
//current extent
|
|
@@ -66,7 +69,7 @@ export class GeoCanvas {
|
|
|
66
69
|
//rely on d3 zoom for pan/zoom
|
|
67
70
|
if (!opts.disableZoom) {
|
|
68
71
|
let tP = zoomIdentity
|
|
69
|
-
const z =
|
|
72
|
+
const z = d3zoom()
|
|
70
73
|
//to make the zooming a bit faster
|
|
71
74
|
.wheelDelta((e) => -e.deltaY * (e.deltaMode === 1 ? 0.07 : e.deltaMode ? 1 : 0.004))
|
|
72
75
|
.on('zoom', (e) => {
|
|
@@ -76,7 +79,7 @@ export class GeoCanvas {
|
|
|
76
79
|
//pan
|
|
77
80
|
const dx = tP.x - t.x
|
|
78
81
|
const dy = tP.y - t.y
|
|
79
|
-
this.pan(dx * this.
|
|
82
|
+
this.pan(dx * this.view.z, -dy * this.view.z)
|
|
80
83
|
} else {
|
|
81
84
|
const se = e.sourceEvent
|
|
82
85
|
if (se instanceof WheelEvent) {
|
|
@@ -106,17 +109,17 @@ export class GeoCanvas {
|
|
|
106
109
|
})
|
|
107
110
|
.on('start', (e) => {
|
|
108
111
|
this.canvasSave.c = document.createElement('canvas')
|
|
109
|
-
this.canvasSave.c.setAttribute('width', '' + this.w)
|
|
110
|
-
this.canvasSave.c.setAttribute('height', '' + this.h)
|
|
111
|
-
this.canvasSave.c.getContext('2d')
|
|
112
|
-
this.canvasSave.dx = 0
|
|
113
|
-
this.canvasSave.dy = 0
|
|
114
|
-
this.canvasSave.f = 1
|
|
112
|
+
this.canvasSave.c.setAttribute('width', '' + this.w);
|
|
113
|
+
this.canvasSave.c.setAttribute('height', '' + this.h);
|
|
114
|
+
this.canvasSave.c.getContext('2d')?.drawImage(this.canvas, 0, 0);
|
|
115
|
+
this.canvasSave.dx = 0;
|
|
116
|
+
this.canvasSave.dy = 0;
|
|
117
|
+
this.canvasSave.f = 1;
|
|
115
118
|
|
|
116
119
|
if (this.onZoomStartFun) this.onZoomStartFun(e)
|
|
117
120
|
})
|
|
118
121
|
.on('end', (e) => {
|
|
119
|
-
this.redraw(
|
|
122
|
+
this.redraw()
|
|
120
123
|
this.canvasSave = { c: null, dx: 0, dy: 0, f: 1 }
|
|
121
124
|
|
|
122
125
|
if (this.onZoomEndFun) this.onZoomEndFun(e)
|
|
@@ -127,39 +130,24 @@ export class GeoCanvas {
|
|
|
127
130
|
|
|
128
131
|
/** Zoom extent, to limit zoom in and out
|
|
129
132
|
* @type {Array.<number>} */
|
|
130
|
-
this.
|
|
133
|
+
this.zoomExtent = opts.zoomExtent || [0, Infinity]
|
|
131
134
|
|
|
132
135
|
/** Canvas state, to be used to avoid unnecessary redraws on zoom/pan
|
|
133
136
|
* @type {{c:HTMLCanvasElement|null,dx:number,dy:number,f:number}} */
|
|
134
137
|
this.canvasSave = { c: null, dx: 0, dy: 0, f: 1 }
|
|
135
138
|
}
|
|
136
139
|
|
|
137
|
-
/** @
|
|
138
|
-
|
|
139
|
-
this.center = v
|
|
140
|
-
}
|
|
141
|
-
/** @returns {{x:number,y:number}} Geographical coordinates of the center */
|
|
142
|
-
getCenter() {
|
|
143
|
-
return this.center
|
|
144
|
-
}
|
|
140
|
+
/** @returns {View} */
|
|
141
|
+
getView() { return this.view }
|
|
145
142
|
|
|
146
|
-
/** @param {number} v The zoom factor (pixel size, in ground m) */
|
|
147
|
-
setZf(v) {
|
|
148
|
-
this.zf = v
|
|
149
|
-
if (this.slider) this.slider.attr('value', +this.zf)
|
|
150
|
-
}
|
|
151
|
-
/** @returns {number} The zoom factor (pixel size, in ground m) */
|
|
152
|
-
getZf() {
|
|
153
|
-
return this.zf
|
|
154
|
-
}
|
|
155
143
|
|
|
156
144
|
/** @param {Array.<number>} v */
|
|
157
|
-
|
|
158
|
-
this.
|
|
145
|
+
setZoomExtent(v) {
|
|
146
|
+
this.zoomExtent = v
|
|
159
147
|
}
|
|
160
148
|
/** @returns {Array.<number>} */
|
|
161
|
-
|
|
162
|
-
return this.
|
|
149
|
+
getZoomExtent() {
|
|
150
|
+
return this.zoomExtent
|
|
163
151
|
}
|
|
164
152
|
|
|
165
153
|
/** Initialise canvas transform with identity transformation. */
|
|
@@ -169,22 +157,21 @@ export class GeoCanvas {
|
|
|
169
157
|
|
|
170
158
|
/** Initialise canvas transform with geo to screen transformation, so that geo objects can be drawn directly in geo coordinates. */
|
|
171
159
|
setCanvasTransform() {
|
|
172
|
-
const k = 1 / this.
|
|
173
|
-
const tx = -this.
|
|
174
|
-
const ty = this.
|
|
160
|
+
const k = 1 / this.view.z
|
|
161
|
+
const tx = -this.view.x / this.view.z + this.w * 0.5
|
|
162
|
+
const ty = this.view.y / this.view.z + this.h * 0.5
|
|
175
163
|
this.ctx.setTransform(k, 0, 0, -k, tx, ty)
|
|
176
164
|
}
|
|
177
165
|
|
|
178
166
|
/** Get the transformation matrix to webGL screen coordinates, within [-1,1]*[-1,1] */
|
|
179
167
|
getWebGLTransform() {
|
|
180
|
-
const kx = 2.0 / (this.w * this.
|
|
181
|
-
const ky = 2.0 / (this.h * this.
|
|
182
|
-
return [kx, 0.0, 0.0, 0.0, ky, 0.0, -kx * this.
|
|
168
|
+
const kx = 2.0 / (this.w * this.view.z)
|
|
169
|
+
const ky = 2.0 / (this.h * this.view.z)
|
|
170
|
+
return [kx, 0.0, 0.0, 0.0, ky, 0.0, -kx * this.view.x, -ky * this.view.y, 1.0]
|
|
183
171
|
}
|
|
184
172
|
|
|
185
|
-
/** The function specifying how to draw the map.
|
|
186
|
-
|
|
187
|
-
redraw(strong = true) {
|
|
173
|
+
/** The function specifying how to draw the map. */
|
|
174
|
+
redraw() {
|
|
188
175
|
throw new Error('Method redraw not implemented.')
|
|
189
176
|
}
|
|
190
177
|
|
|
@@ -207,13 +194,13 @@ export class GeoCanvas {
|
|
|
207
194
|
*/
|
|
208
195
|
pan(dxGeo = 0, dyGeo = 0) {
|
|
209
196
|
//TODO force extend to remain
|
|
210
|
-
this.
|
|
211
|
-
this.
|
|
197
|
+
this.view.x += dxGeo
|
|
198
|
+
this.view.y += dyGeo
|
|
212
199
|
this.updateExtentGeo()
|
|
213
200
|
|
|
214
201
|
if (this.canvasSave.c) {
|
|
215
|
-
this.canvasSave.dx -= dxGeo / this.
|
|
216
|
-
this.canvasSave.dy += dyGeo / this.
|
|
202
|
+
this.canvasSave.dx -= dxGeo / this.view.z
|
|
203
|
+
this.canvasSave.dy += dyGeo / this.view.z
|
|
217
204
|
this.clear(this.backgroundColor)
|
|
218
205
|
// this doesnt work on mobile https://github.com/eurostat/gridviz/issues/98
|
|
219
206
|
this.ctx.drawImage(this.canvasSave.c, this.canvasSave.dx, this.canvasSave.dy)
|
|
@@ -222,31 +209,29 @@ export class GeoCanvas {
|
|
|
222
209
|
|
|
223
210
|
/**
|
|
224
211
|
* Zoom.
|
|
225
|
-
* @param {number} f The
|
|
212
|
+
* @param {number} f The , within ]0, Infinity]. 1 is for no change. <1 to zoom-in, >1 to zoom-out.
|
|
226
213
|
* @param {number} xGeo The x geo position fixed in the screen.
|
|
227
214
|
* @param {number} yGeo The y geo position fixed in the screen.
|
|
228
215
|
*/
|
|
229
|
-
zoom(f = 1, xGeo = this.
|
|
216
|
+
zoom(f = 1, xGeo = this.view.x, yGeo = this.view.y) {
|
|
230
217
|
//TODO force geo extend to remain
|
|
231
218
|
|
|
232
219
|
//trying to zoom in/out beyond limit
|
|
233
|
-
if (this.
|
|
234
|
-
if (this.
|
|
220
|
+
if (this.zoomExtent[0] == this.view.z && f <= 1) return
|
|
221
|
+
if (this.zoomExtent[1] == this.view.z && f >= 1) return
|
|
235
222
|
|
|
236
223
|
//ensure zoom extent preserved
|
|
237
|
-
const newZf = f * this.
|
|
238
|
-
if (newZf < this.
|
|
239
|
-
if (newZf > this.
|
|
240
|
-
|
|
241
|
-
this.
|
|
242
|
-
const dxGeo = (xGeo - this.
|
|
243
|
-
this.
|
|
244
|
-
const dyGeo = (yGeo - this.
|
|
245
|
-
this.
|
|
224
|
+
const newZf = f * this.view.z
|
|
225
|
+
if (newZf < this.zoomExtent[0]) f = this.zoomExtent[0] / this.view.z
|
|
226
|
+
if (newZf > this.zoomExtent[1]) f = this.zoomExtent[1] / this.view.z
|
|
227
|
+
|
|
228
|
+
this.view.z *= f
|
|
229
|
+
const dxGeo = (xGeo - this.view.x) * (1 - f)
|
|
230
|
+
this.view.x += dxGeo
|
|
231
|
+
const dyGeo = (yGeo - this.view.y) * (1 - f)
|
|
232
|
+
this.view.y += dyGeo
|
|
246
233
|
this.updateExtentGeo()
|
|
247
234
|
|
|
248
|
-
//TODO
|
|
249
|
-
//this.redraw(false)
|
|
250
235
|
if (this.canvasSave.c) {
|
|
251
236
|
this.clear(this.backgroundColor)
|
|
252
237
|
this.canvasSave.f /= f
|
|
@@ -296,113 +281,38 @@ export class GeoCanvas {
|
|
|
296
281
|
* @returns {number} Screen x coordinate, in pix.
|
|
297
282
|
*/
|
|
298
283
|
geoToPixX(xGeo) {
|
|
299
|
-
return (xGeo - this.
|
|
284
|
+
return (xGeo - this.view.x) / this.view.z + this.w * 0.5
|
|
300
285
|
}
|
|
301
286
|
/**
|
|
302
287
|
* @param {number} yGeo Geo y coordinate, in m.
|
|
303
288
|
* @returns {number} Screen y coordinate, in pix.
|
|
304
289
|
*/
|
|
305
290
|
geoToPixY(yGeo) {
|
|
306
|
-
return -(yGeo - this.
|
|
291
|
+
return -(yGeo - this.view.y) / this.view.z + this.h * 0.5
|
|
307
292
|
}
|
|
308
293
|
/**
|
|
309
294
|
* @param {number} x Screen x coordinate, in pix.
|
|
310
295
|
* @returns {number} Geo x coordinate, in m.
|
|
311
296
|
*/
|
|
312
297
|
pixToGeoX(x) {
|
|
313
|
-
return (x - this.w * 0.5) * this.
|
|
298
|
+
return (x - this.w * 0.5) * this.view.z + this.view.x
|
|
314
299
|
}
|
|
315
300
|
/**
|
|
316
301
|
* @param {number} y Screen y coordinate, in pix.
|
|
317
302
|
* @returns {number} Geo y coordinate, in m.
|
|
318
303
|
*/
|
|
319
304
|
pixToGeoY(y) {
|
|
320
|
-
return -(y - this.h * 0.5) * this.
|
|
305
|
+
return -(y - this.h * 0.5) * this.view.z + this.view.y
|
|
321
306
|
}
|
|
322
307
|
|
|
323
|
-
/** Get x,y,z elements from URL and assign them to the view
|
|
308
|
+
/** Get x,y,z elements from URL and assign them to the view. */
|
|
324
309
|
setViewFromURL() {
|
|
325
310
|
const x = GeoCanvas.getParameterByName('x'),
|
|
326
311
|
y = GeoCanvas.getParameterByName('y'),
|
|
327
312
|
z = GeoCanvas.getParameterByName('z')
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
if (
|
|
331
|
-
if (z != null && z != undefined && !isNaN(+z)) this.setZf(+z)
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
*
|
|
336
|
-
* @param {string} id
|
|
337
|
-
* @param {object} opts
|
|
338
|
-
* @returns {this}
|
|
339
|
-
*/
|
|
340
|
-
addZoomSlider(id, opts) {
|
|
341
|
-
opts = opts || {}
|
|
342
|
-
opts.width = opts.width || '30px'
|
|
343
|
-
opts.height = opts.height || '300px'
|
|
344
|
-
|
|
345
|
-
//the div element
|
|
346
|
-
const div = select('#' + id)
|
|
347
|
-
if (div.empty()) {
|
|
348
|
-
console.error('Could not find div element to build zoom slider. Id: ' + id)
|
|
349
|
-
return this
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const th = this
|
|
353
|
-
/** */
|
|
354
|
-
this.slider = div
|
|
355
|
-
.append('input')
|
|
356
|
-
.attr('type', 'range')
|
|
357
|
-
.attr('min', this.getZfExtent()[0])
|
|
358
|
-
.attr('max', this.getZfExtent()[1])
|
|
359
|
-
.attr('value', this.getZf())
|
|
360
|
-
.on('input', function (d) {
|
|
361
|
-
if (!this || !this.value) return
|
|
362
|
-
const v = +this.value
|
|
363
|
-
select(this).attr('value', v)
|
|
364
|
-
th.setZf(v)
|
|
365
|
-
//redraw
|
|
366
|
-
th.redraw()
|
|
367
|
-
})
|
|
368
|
-
.style('width', opts.width)
|
|
369
|
-
.style('height', opts.height)
|
|
370
|
-
.style('opacity', 0.7)
|
|
371
|
-
.on('mouseover', function (d) {
|
|
372
|
-
select(this).style('opacity', 1)
|
|
373
|
-
})
|
|
374
|
-
.on('mouseout', function (d) {
|
|
375
|
-
select(this).style('opacity', 0.7)
|
|
376
|
-
})
|
|
377
|
-
.style('-webkit-appearance', 'slider-vertical') //for chrome
|
|
378
|
-
.style('writing-mode', 'bt-lr') //for IE/edge
|
|
379
|
-
.attr('orient', 'vertical') //for firefox
|
|
380
|
-
.style('background', 'lightgray')
|
|
381
|
-
.style('outline', 'none')
|
|
382
|
-
.style('-webkit-transition', '.2s')
|
|
383
|
-
.style('transition', 'opacity .2s')
|
|
384
|
-
|
|
385
|
-
//TODO
|
|
386
|
-
/*select(".slider::-webkit-slider-thumb")
|
|
387
|
-
.style("-webkit-appearance", "none")
|
|
388
|
-
.style("appearance", "none")
|
|
389
|
-
.style("width", "30px")
|
|
390
|
-
.style("height", "40px")
|
|
391
|
-
.style("background", "black")
|
|
392
|
-
.style("cursor", "pointer")*/
|
|
393
|
-
|
|
394
|
-
/*select("slider::-moz-range-thumb")
|
|
395
|
-
.style("-webkit-appearance", "none")
|
|
396
|
-
.style("width", "30px")
|
|
397
|
-
.style("height", "40px")
|
|
398
|
-
.style("background", "#04AA6D")
|
|
399
|
-
.style("cursor", "pointer")*/
|
|
400
|
-
/*
|
|
401
|
-
.slider::ms-thumb,
|
|
402
|
-
.slider::-moz-range-thumb {
|
|
403
|
-
}*/
|
|
404
|
-
|
|
405
|
-
return this
|
|
313
|
+
if (x != null && x != undefined && !isNaN(+x)) this.view.x = +x
|
|
314
|
+
if (y != null && y != undefined && !isNaN(+y)) this.view.y = +y
|
|
315
|
+
if (z != null && z != undefined && !isNaN(+z)) this.view.z = +z
|
|
406
316
|
}
|
|
407
317
|
|
|
408
318
|
/**
|