gridviz 3.1.0 → 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.
- package/README.md +18 -15
- package/dist/gridviz.js +3829 -2623
- package/dist/gridviz.min.js +1 -1
- package/package.json +5 -3
- package/src/button/Button.js +77 -68
- package/src/button/FullscreenButton.js +123 -123
- package/src/button/ZoomButtons.js +109 -104
- package/src/core/GeoCanvas.js +15 -0
- package/src/core/Legend.js +267 -245
- package/src/core/Map.js +819 -670
- package/src/core/Tooltip.js +191 -185
- package/src/index.js +82 -79
- package/src/layer/GridLayer.js +4 -3
- package/src/layer/LabelLayer.js +7 -4
- package/src/legend/ColorCategoryLegend.js +127 -125
- package/src/legend/ColorDiscreteLegend.js +116 -114
- package/src/legend/ColorLegend.js +33 -11
- package/src/legend/OrientationLegend.js +94 -92
- package/src/legend/SizeLegend.js +8 -1
- package/src/legend/TernaryLegend.js +317 -314
- package/src/legend/VennLegend.js +202 -0
- package/src/style/CircleColorSizeWebGLStyle.js +249 -0
- package/src/style/CompositionStyle.js +1 -1
- package/src/style/DotDensityStyle.js +8 -4
- package/src/style/ImageStyle.js +1 -1
- package/src/style/Interpolator.js +283 -259
- package/src/style/IsoFenceStyle.js +1 -1
- package/src/style/LegoStyle.js +6 -5
- package/src/style/MosaicStyle.js +1 -1
- package/src/style/NinjaStarStyle.js +1 -1
- package/src/style/PillarStyle.js +1 -1
- package/src/style/RandomWalkInterpolator.js +213 -0
- package/src/style/ShadingRayStyle.js +16 -2
- package/src/style/ShadingStyle.js +80 -72
- package/src/style/ShapeColorSizeStyle.js +18 -2
- package/src/style/SideCategoryStyle.js +1 -1
- package/src/style/SideStyle.js +1 -1
- package/src/style/SideTanakaStyle.js +1 -1
- package/src/style/SquareColorCategoryWebGLStyle.js +71 -20
- package/src/style/SquareColorWebGLStyle.js +7 -4
- package/src/style/StrokeStyle.js +1 -1
- package/src/style/TextStyle.js +1 -1
- package/src/style/TimeSeriesStyle.js +14 -7
- package/src/utils/WebGLSquareColoringAdvanced.js +47 -9
- package/src/utils/scale.js +20 -0
- package/src/utils/webGLUtils.js +52 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
import { Interpolator } from './Interpolator.js'
|
|
5
|
+
import { cellsToMatrix } from '../utils/utils.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A style which makes a finer grid from a grid of categories (a land cover for example), and draws it with other styles.
|
|
9
|
+
* It does not draw anything by itself.
|
|
10
|
+
*
|
|
11
|
+
* A category has no value between categories to interpolate, as a number has. This style uses the random walk interpolator
|
|
12
|
+
* described in "Spatial interpolators in Observable Plot" (VISU 2023): each cell of the finer grid is given the category
|
|
13
|
+
* of the first cell of the original grid which a random walk, starting from it, reaches. The cells of the original grid are
|
|
14
|
+
* taken as points at their centre. A cell of the finer grid is then most often given the category of its closest neighbours,
|
|
15
|
+
* and the limits between the categories become irregular, with fine details, instead of squares.
|
|
16
|
+
*
|
|
17
|
+
* The random walks are drawn from the position of the cell, so that the result does not change when the map is panned,
|
|
18
|
+
* or drawn again.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const interpolator = new gridviz.RandomWalkInterpolator({ value: (c) => c.clc, targetResolution: (r, z) => r / 5, interpolatedProperty: 'clc' })
|
|
22
|
+
* interpolator.styles.push(new gridviz.SquareColorCategoryWebGLStyle({ code: (c) => c.clc, color: colors }))
|
|
23
|
+
*
|
|
24
|
+
* @module style
|
|
25
|
+
*/
|
|
26
|
+
export class RandomWalkInterpolator extends Interpolator {
|
|
27
|
+
/** @param {object} opts The options of the style (see Style for the others: filter, visible...).
|
|
28
|
+
* @param {function(import('../core/Dataset.js').Cell):(string|number|undefined)} opts.value The category of a cell. A cell without category (undefined) is not interpolated.
|
|
29
|
+
* @param {function(number, number):number} [opts.targetResolution] (resolution, z) => the resolution of the interpolated cells. The ratio between the resolution and this one is rounded to an integer. Default: resolution / 5.
|
|
30
|
+
* @param {string} [opts.interpolatedProperty] The property of the interpolated cells which holds their category. Default: 'value'.
|
|
31
|
+
* @param {function(number):number} [opts.maxSteps] (factor) => the maximum number of steps of a random walk, from the ratio between the resolutions. A walk which has not reached a cell of the original grid then gives its cell the category of the cell it is in. Default: 6 * factor * factor.
|
|
32
|
+
* @param {number} [opts.seed] A number to change the random walks, and thus the result. Default: 0.
|
|
33
|
+
* @param {Array.<import('../core/Style.js').Style>} [opts.styles] The styles which draw the interpolated cells.
|
|
34
|
+
*/
|
|
35
|
+
constructor(opts) {
|
|
36
|
+
super(opts)
|
|
37
|
+
opts = opts || {}
|
|
38
|
+
|
|
39
|
+
/** The category of a cell.
|
|
40
|
+
* @type {function(import('../core/Dataset.js').Cell):(string|number|undefined)} */
|
|
41
|
+
this.value = opts.value
|
|
42
|
+
|
|
43
|
+
/** The maximum number of steps of a random walk, as a function of the ratio between the resolutions.
|
|
44
|
+
* @type {function(number):number} */
|
|
45
|
+
this.maxSteps = opts.maxSteps || ((factor) => 6 * factor * factor)
|
|
46
|
+
|
|
47
|
+
/** A number to change the random walks.
|
|
48
|
+
* @type {number} */
|
|
49
|
+
this.seed = opts.seed || 0
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {Array.<import("../core/Dataset.js").Cell>} cells
|
|
54
|
+
* @param {import("../core/GeoCanvas.js").GeoCanvas} geoCanvas
|
|
55
|
+
* @param {number} resolution
|
|
56
|
+
* @override
|
|
57
|
+
*/
|
|
58
|
+
draw(cells, geoCanvas, resolution) {
|
|
59
|
+
//filter cells
|
|
60
|
+
if (this.filter) cells = cells.filter(this.filter)
|
|
61
|
+
if (cells.length == 0) return
|
|
62
|
+
|
|
63
|
+
const z = geoCanvas.view.z
|
|
64
|
+
|
|
65
|
+
//get target resolution and scale factor
|
|
66
|
+
let targetResolution = this.targetResolution(resolution, z)
|
|
67
|
+
const factor = Math.max(1, Math.round(resolution / targetResolution))
|
|
68
|
+
targetResolution = resolution / factor
|
|
69
|
+
|
|
70
|
+
//the categories of the cells, in a matrix: the lines go from the top to the bottom
|
|
71
|
+
const m = cellsToMatrix(cells, resolution, (c) => this.value(c))
|
|
72
|
+
const x0 = m.x0,
|
|
73
|
+
y0 = m.y0
|
|
74
|
+
|
|
75
|
+
//the categories as numbers, for speed
|
|
76
|
+
/** @type {Array.<string|number>} */
|
|
77
|
+
const categories = []
|
|
78
|
+
const index = new Map()
|
|
79
|
+
const rows = m.length,
|
|
80
|
+
cols = rows ? m[0].length : 0
|
|
81
|
+
const codes = new Int32Array(rows * cols).fill(-1)
|
|
82
|
+
for (let i = 0; i < rows; i++)
|
|
83
|
+
for (let j = 0; j < cols; j++) {
|
|
84
|
+
const v = m[i][j]
|
|
85
|
+
if (v === undefined || v === null) continue
|
|
86
|
+
let k = index.get(v)
|
|
87
|
+
if (k === undefined) {
|
|
88
|
+
k = categories.length
|
|
89
|
+
categories.push(v)
|
|
90
|
+
index.set(v, k)
|
|
91
|
+
}
|
|
92
|
+
codes[i * cols + j] = k
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
//the position of the first fine cell in the grid of all the fine cells of the map: the random walks depend on it only
|
|
96
|
+
const offsetI = -Math.round(y0 / targetResolution) - factor
|
|
97
|
+
const offsetJ = Math.round(x0 / targetResolution)
|
|
98
|
+
|
|
99
|
+
const fine = randomWalkInterpolate(codes, rows, cols, factor, {
|
|
100
|
+
maxSteps: this.maxSteps(factor),
|
|
101
|
+
seed: this.seed,
|
|
102
|
+
offsetI: offsetI,
|
|
103
|
+
offsetJ: offsetJ,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
//make the cells
|
|
107
|
+
const out = []
|
|
108
|
+
const fineCols = cols * factor
|
|
109
|
+
const r = targetResolution
|
|
110
|
+
for (let I = 0; I < rows * factor; I++) {
|
|
111
|
+
const y = y0 + resolution - (I + 1) * r
|
|
112
|
+
for (let J = 0; J < fineCols; J++) {
|
|
113
|
+
const k = fine[I * fineCols + J]
|
|
114
|
+
if (k < 0) continue
|
|
115
|
+
const c = { x: x0 + J * r, y: y }
|
|
116
|
+
c[this.interpolatedProperty] = categories[k]
|
|
117
|
+
out.push(c)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//draw the interpolated cells with the styles
|
|
122
|
+
this.drawStyles(out, geoCanvas, targetResolution)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The random walk interpolation of a grid of categories to a grid `factor` times finer.
|
|
128
|
+
*
|
|
129
|
+
* The cell (i,j) of the grid is a point at the centre of its factor x factor fine cells. The category of a fine cell
|
|
130
|
+
* is the one of the first point reached by a random walk which starts on it and moves to a neighbour fine cell
|
|
131
|
+
* (up, down, left or right) at each step. A walk which has not reached a point after maxSteps steps, or which leaves the
|
|
132
|
+
* grid, is ended: the fine cell gets the category of the cell of the grid it is in. The fine cells of the cells without
|
|
133
|
+
* category get no category.
|
|
134
|
+
*
|
|
135
|
+
* The walks are deterministic: they come from the position of the fine cell in the grid of the map, and a seed.
|
|
136
|
+
*
|
|
137
|
+
* @param {Int32Array|Array.<number>} codes The categories of the cells of the grid, as numbers (0, 1, 2...), -1 for no category. Line by line (rows * cols values).
|
|
138
|
+
* @param {number} rows The number of lines
|
|
139
|
+
* @param {number} cols The number of columns
|
|
140
|
+
* @param {number} factor The number of fine cells for a cell, in each direction.
|
|
141
|
+
* @param {{maxSteps?:number, seed?:number, offsetI?:number, offsetJ?:number}} [opts] maxSteps: the maximum number of steps of a walk (default: 6*factor*factor). seed. offsetI and offsetJ: the position of the first fine cell in a larger grid, so that the same fine cell has the same walk in different extents (default: 0).
|
|
142
|
+
* @returns {Int32Array} The categories of the fine cells, line by line (rows * factor lines of cols * factor values), -1 for no category.
|
|
143
|
+
*/
|
|
144
|
+
export function randomWalkInterpolate(codes, rows, cols, factor, opts = {}) {
|
|
145
|
+
const maxSteps = opts.maxSteps === undefined ? 6 * factor * factor : opts.maxSteps
|
|
146
|
+
const seed = (opts.seed || 0) | 0
|
|
147
|
+
const offsetI = opts.offsetI || 0
|
|
148
|
+
const offsetJ = opts.offsetJ || 0
|
|
149
|
+
|
|
150
|
+
const fineRows = rows * factor,
|
|
151
|
+
fineCols = cols * factor
|
|
152
|
+
const out = new Int32Array(fineRows * fineCols).fill(-1)
|
|
153
|
+
|
|
154
|
+
// the point of a cell is the fine cell h cells from its top left corner
|
|
155
|
+
const h = Math.floor(factor / 2)
|
|
156
|
+
|
|
157
|
+
for (let I = 0; I < fineRows; I++) {
|
|
158
|
+
const i = Math.floor(I / factor)
|
|
159
|
+
for (let J = 0; J < fineCols; J++) {
|
|
160
|
+
const j = Math.floor(J / factor)
|
|
161
|
+
const own = codes[i * cols + j]
|
|
162
|
+
//no category here
|
|
163
|
+
if (own < 0) continue
|
|
164
|
+
|
|
165
|
+
//a random generator for this fine cell
|
|
166
|
+
let rnd = hash(I + offsetI, J + offsetJ, seed)
|
|
167
|
+
|
|
168
|
+
let a = I,
|
|
169
|
+
b = J,
|
|
170
|
+
found = -1
|
|
171
|
+
for (let step = 0; ; step++) {
|
|
172
|
+
//the fine cell is the point of a cell of the grid?
|
|
173
|
+
if (a % factor === h && b % factor === h) {
|
|
174
|
+
const c = codes[Math.floor(a / factor) * cols + Math.floor(b / factor)]
|
|
175
|
+
if (c >= 0) {
|
|
176
|
+
found = c
|
|
177
|
+
break
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (step >= maxSteps) break
|
|
181
|
+
//next step, to a neighbour: xorshift32
|
|
182
|
+
rnd ^= rnd << 13
|
|
183
|
+
rnd ^= rnd >>> 17
|
|
184
|
+
rnd ^= rnd << 5
|
|
185
|
+
const dir = (rnd >>> 0) & 3
|
|
186
|
+
if (dir === 0) a--
|
|
187
|
+
else if (dir === 1) a++
|
|
188
|
+
else if (dir === 2) b--
|
|
189
|
+
else b++
|
|
190
|
+
//out of the grid: the walk is ended
|
|
191
|
+
if (a < 0 || a >= fineRows || b < 0 || b >= fineCols) break
|
|
192
|
+
}
|
|
193
|
+
out[I * fineCols + J] = found >= 0 ? found : own
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return out
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* A 32 bits number from two integers and a seed, never 0.
|
|
201
|
+
* @param {number} a
|
|
202
|
+
* @param {number} b
|
|
203
|
+
* @param {number} seed
|
|
204
|
+
* @returns {number}
|
|
205
|
+
*/
|
|
206
|
+
function hash(a, b, seed) {
|
|
207
|
+
let h = (Math.imul(a | 0, 73856093) ^ Math.imul(b | 0, 19349663) ^ Math.imul(seed, 83492791)) | 0
|
|
208
|
+
//mix the bits
|
|
209
|
+
h = Math.imul(h ^ (h >>> 16), 2246822507)
|
|
210
|
+
h = Math.imul(h ^ (h >>> 13), 3266489909)
|
|
211
|
+
h ^= h >>> 16
|
|
212
|
+
return h === 0 ? 1 : h
|
|
213
|
+
}
|
|
@@ -14,11 +14,23 @@ import { extent } from 'd3-array'
|
|
|
14
14
|
// style with SquareColorWebGLStyle, improved version !
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
+
* A style to draw the shadows cast by the relief, for a position of the sun.
|
|
18
|
+
* For each cell, a ray is cast towards the sun across the elevation grid. The cells in the shadow are drawn as squares of a single color.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* new gridviz.ShadingRayStyle({ elevation: (c) => c.v, sunAzimuth: () => 2.356, sunAltitude: () => 0.2, alpha: () => 0.4 })
|
|
22
|
+
*
|
|
17
23
|
* @module style
|
|
18
24
|
* @author Julien Gaffuri
|
|
19
25
|
*/
|
|
20
26
|
export class ShadingRayStyle extends Style {
|
|
21
|
-
/** @param {object} opts
|
|
27
|
+
/** @param {object} opts The options of the style (see Style for the others: filter, alpha, blendOperation, visible...).
|
|
28
|
+
* @param {function(import('../core/Dataset.js').Cell):number} opts.elevation The elevation of a cell.
|
|
29
|
+
* @param {function(number, number, object):number} [opts.sunAzimuth] (resolution, z, viewScale) => the direction of the sun, in radians, clockwise from the north. Default: 2.356 (south-east).
|
|
30
|
+
* @param {function(number, number, object):number} [opts.sunAltitude] (resolution, z, viewScale) => the height of the sun above the horizon, in radians. Default: 0.2.
|
|
31
|
+
* @param {function(number, number, object):string} [opts.color] (resolution, z, viewScale) => the color of the shadows. Default: black.
|
|
32
|
+
* @param {number} [opts.undefinedValue] The elevation to use for the cells without elevation. Default: 0.
|
|
33
|
+
*/
|
|
22
34
|
constructor(opts) {
|
|
23
35
|
super(opts)
|
|
24
36
|
opts = opts || {}
|
|
@@ -48,6 +60,8 @@ export class ShadingRayStyle extends Style {
|
|
|
48
60
|
//filter
|
|
49
61
|
if (this.filter) cells = cells.filter(this.filter)
|
|
50
62
|
if (cells.length == 0) return;
|
|
63
|
+
//the cells of the grid, for the legends: the cells below are the ones of the shading
|
|
64
|
+
const gridCells = cells
|
|
51
65
|
|
|
52
66
|
//
|
|
53
67
|
const z = geoCanvas.view.z
|
|
@@ -124,7 +138,7 @@ export class ShadingRayStyle extends Style {
|
|
|
124
138
|
//if (s.filterColor) s.drawFilter(geoCanvas)
|
|
125
139
|
|
|
126
140
|
//update legends
|
|
127
|
-
this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
|
|
141
|
+
this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale, cells: gridCells })
|
|
128
142
|
}
|
|
129
143
|
}
|
|
130
144
|
|
|
@@ -1,72 +1,80 @@
|
|
|
1
|
-
//@ts-check
|
|
2
|
-
'use strict'
|
|
3
|
-
|
|
4
|
-
import { SideStyle } from './SideStyle.js'
|
|
5
|
-
import { max } from 'd3-array'
|
|
6
|
-
|
|
7
|
-
/** @typedef {{ x:number, y:number, or:"v"|"h", c1:(import('../core/Dataset.js').Cell)|undefined, c2:(import('../core/Dataset.js').Cell)|undefined }} Side */
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
import { SideStyle } from './SideStyle.js'
|
|
5
|
+
import { max } from 'd3-array'
|
|
6
|
+
|
|
7
|
+
/** @typedef {{ x:number, y:number, or:"v"|"h", c1:(import('../core/Dataset.js').Cell)|undefined, c2:(import('../core/Dataset.js').Cell)|undefined }} Side */
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A style to show the relief of an elevation grid as hillshading.
|
|
12
|
+
* For each side between two adjacent cells, a segment is drawn: light when the side faces the top left, dark when it faces the bottom right,
|
|
13
|
+
* with an opacity proportional to the elevation difference between the two cells (relative to the biggest one in the view).
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* new gridviz.ShadingStyle({ elevation: 'v', colorTopLeft: '255,155,155', colorBottomRight: '0,0,100' })
|
|
17
|
+
*
|
|
18
|
+
* @module style
|
|
19
|
+
* @author Julien Gaffuri
|
|
20
|
+
*/
|
|
21
|
+
export class ShadingStyle extends SideStyle {
|
|
22
|
+
|
|
23
|
+
/** @param {object} opts The options of the style (see SideStyle and Style for the others: filter, alpha, blendOperation, visible...).
|
|
24
|
+
* @param {string} opts.elevation The name of the cell property with the elevation.
|
|
25
|
+
* @param {string} [opts.colorTopLeft] The color of the sides which face the top left, as 'r,g,b'. Default: '255,255,255'.
|
|
26
|
+
* @param {string} [opts.colorBottomRight] The color of the sides which face the bottom right, as 'r,g,b'. Default: '0,0,0'.
|
|
27
|
+
* @param {boolean} [opts.revert] Swaps the two colors, to show depressions as hills. Default: false.
|
|
28
|
+
* @param {function(number):number} [opts.scale] Stretches the opacity, from a value within [0,1] to a value within [0,1]. Default: linear.
|
|
29
|
+
* @param {number} [opts.width] The width of the segments, in geographical unit. Default: 2 pixels, and at most a third of the resolution.
|
|
30
|
+
* @param {boolean} [opts.diamond] Draws the sides as diamonds.
|
|
31
|
+
*/
|
|
32
|
+
constructor(opts = {}) {
|
|
33
|
+
super(opts)
|
|
34
|
+
|
|
35
|
+
/** The cell elevation field name
|
|
36
|
+
* @type {string} */
|
|
37
|
+
const elevation = opts.elevation
|
|
38
|
+
|
|
39
|
+
// compute side value as elevation difference and attach to side
|
|
40
|
+
const sideValue = (/** @type {Side} */ side) => {
|
|
41
|
+
if (!side.c1) side.v = 0
|
|
42
|
+
else if (!side.c2) side.v = 0
|
|
43
|
+
else if (!side.c1[elevation]) side.v = 0
|
|
44
|
+
else if (!side.c2[elevation]) side.v = 0
|
|
45
|
+
else side.v = +side.c2[elevation] - side.c1[elevation]
|
|
46
|
+
return side.v
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// compute maximum side value for normalization
|
|
50
|
+
this.viewScale = sides => max(sides, s => sideValue(s))
|
|
51
|
+
|
|
52
|
+
// get colors
|
|
53
|
+
let colorTopLeft = opts.colorTopLeft || '255,255,255'
|
|
54
|
+
let colorBottomRight = opts.colorBottomRight || '0,0,0'
|
|
55
|
+
|
|
56
|
+
// revert colors (to revert the relief, show depressions as hills)
|
|
57
|
+
const revert = opts.revert == undefined? false : opts.revert
|
|
58
|
+
if(revert) {
|
|
59
|
+
let a = colorTopLeft
|
|
60
|
+
colorTopLeft = colorBottomRight
|
|
61
|
+
colorBottomRight = a
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//
|
|
65
|
+
const scale = opts.scale
|
|
66
|
+
|
|
67
|
+
this.color = (side, resolution, z, max) => {
|
|
68
|
+
if (side.v == 0) return
|
|
69
|
+
let coeff = Math.abs(side.v / max)
|
|
70
|
+
if(scale) coeff = scale(coeff)
|
|
71
|
+
if ((side.v < 0 && side.or === 'h') || (side.v > 0 && side.or === 'v'))
|
|
72
|
+
return 'rgba(' + colorTopLeft + ',' + coeff + ')'
|
|
73
|
+
return 'rgba(' + colorBottomRight + ',' + coeff + ')'
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 2 pixels, and at most a third of the resolution (a bitwise OR here truncated the width to an integer number of geographical units)
|
|
77
|
+
this.width = (_, r, z) => opts.width || Math.min(2 * z, r / 3)
|
|
78
|
+
this.diamond = opts.diamond
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -23,6 +23,13 @@ export class ShapeColorSizeStyle extends Style {
|
|
|
23
23
|
|
|
24
24
|
/** @type {(function(import("../core/Dataset.js").Cell,number, number,object):import("../core/Style.js").Shape) | string} */
|
|
25
25
|
this.shape = opts.shape || 'square'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* For the donut shape, the outer diameter of the donut, in geographical unit (the resolution by default, so that the donut fills the cell).
|
|
29
|
+
* The size of the donut is then the part of this diameter it fills: the donut is a full disk when its size equals its outer diameter, and a thin ring when its size is small.
|
|
30
|
+
* With a smaller outer diameter, a donut can be a ring around another symbol, a pie chart for example, or a part of a symbol with several rings.
|
|
31
|
+
* @type {(function(import('../core/Dataset.js').Cell, number, number, object):number) | number | undefined} */
|
|
32
|
+
this.donutOuterSize = opts.donutOuterSize
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
/**
|
|
@@ -98,10 +105,19 @@ export class ShapeColorSizeStyle extends Style {
|
|
|
98
105
|
//draw donut
|
|
99
106
|
const xc = c.x + r2 + offset.dx,
|
|
100
107
|
yc = c.y + r2 + offset.dy
|
|
108
|
+
// outer diameter: the resolution, unless specified
|
|
109
|
+
const outer =
|
|
110
|
+
this.donutOuterSize === undefined
|
|
111
|
+
? resolution
|
|
112
|
+
: typeof this.donutOuterSize === 'function'
|
|
113
|
+
? this.donutOuterSize(c, resolution, z, viewScale)
|
|
114
|
+
: this.donutOuterSize
|
|
115
|
+
if (!outer) continue
|
|
116
|
+
const R = outer * 0.5
|
|
101
117
|
ctx.beginPath()
|
|
102
118
|
ctx.moveTo(xc, yc)
|
|
103
|
-
ctx.arc(xc, yc,
|
|
104
|
-
ctx.arc(xc, yc, (1 - size /
|
|
119
|
+
ctx.arc(xc, yc, R, 0, 2 * Math.PI)
|
|
120
|
+
ctx.arc(xc, yc, Math.max(0, 1 - size / outer) * R, 0, 2 * Math.PI, true)
|
|
105
121
|
ctx.closePath()
|
|
106
122
|
ctx.fill()
|
|
107
123
|
} else if (shape === 'diamond') {
|
|
@@ -107,6 +107,6 @@ export class SideCategoryStyle extends SideStyle {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
//update legends
|
|
110
|
-
this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
|
|
110
|
+
this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale, cells: cells })
|
|
111
111
|
}
|
|
112
112
|
}
|
package/src/style/SideStyle.js
CHANGED
|
@@ -112,7 +112,7 @@ export class SideStyle extends Style {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
//update legends
|
|
115
|
-
this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
|
|
115
|
+
this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale, cells: cells })
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
/**
|
|
@@ -148,7 +148,7 @@ export class SideTanakaStyle extends Style {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
//update legends
|
|
151
|
-
this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
|
|
151
|
+
this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale, cells: cells })
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
}
|