gridviz 2.1.16 → 3.0.0
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 +4971 -5654
- 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 -12
- 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 +460 -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 -10
- 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 +140 -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 +202 -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
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
//@ts-check
|
|
2
|
-
'use strict'
|
|
3
|
-
|
|
4
|
-
import { Style } from '../Style.js'
|
|
5
|
-
|
|
6
|
-
/** @typedef {"flag"|"piechart"|"ring"|"segment"|"radar"|"agepyramid"|"halftone"} CompositionType */
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A style showing the composition of a total in different categories, with different color hues.
|
|
10
|
-
* It consists of a symbol with different parts, whose size reflect the proportion of the corresponding category.
|
|
11
|
-
* For a list of supported symbols, @see CompositionType
|
|
12
|
-
* The symbol can be scaled depending on the cell importance.
|
|
13
|
-
*
|
|
14
|
-
* @author Julien Gaffuri
|
|
15
|
-
*/
|
|
16
|
-
export class CompositionStyle_ extends Style {
|
|
17
|
-
/** @param {object} opts */
|
|
18
|
-
constructor(opts) {
|
|
19
|
-
super(opts)
|
|
20
|
-
opts = opts || {}
|
|
21
|
-
|
|
22
|
-
/** A function returning the view scale.
|
|
23
|
-
* @type {function(Array.<import('../Dataset.js').Cell>,number, number):object} */
|
|
24
|
-
this.viewScale = opts.viewScale
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The dictionary (string -> color) which give the color of each category.
|
|
28
|
-
* @type {object} */
|
|
29
|
-
this.color = opts.color
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* A function returning the type of decomposition symbol of a cell, @see CompositionType
|
|
33
|
-
* @type {function(import("../Dataset.js").Cell,number, number,object):CompositionType} */
|
|
34
|
-
this.type = opts.type
|
|
35
|
-
|
|
36
|
-
/** A function returning the size of a cell in geographical unit.
|
|
37
|
-
* @type {function(import('../Dataset.js').Cell,number, number,object):number} */
|
|
38
|
-
this.size = opts.size
|
|
39
|
-
|
|
40
|
-
/** For style types with stripes (flag, segment), the orientation of the stripes (0 for horizontal, other for vertical).
|
|
41
|
-
* @type {function(import("../Dataset.js").Cell,number,number,object):number} */
|
|
42
|
-
this.stripesOrientation = opts.stripesOrientation || (() => 0) //(c,r,zf,vc) => ...
|
|
43
|
-
|
|
44
|
-
/** The function specifying an offset angle for a radar, halftone or pie chart style.
|
|
45
|
-
* The angle is specified in degree. The rotation is anti-clockwise.
|
|
46
|
-
* @type {function(import("../Dataset.js").Cell,number,number,object):number} */
|
|
47
|
-
this.offsetAngle = opts.offsetAngle || (() => 0) //(cell,r,zf,vc) => ...
|
|
48
|
-
|
|
49
|
-
/** The function specifying the height of the age pyramid, in geo unit.
|
|
50
|
-
* @type {function(import("../Dataset.js").Cell,number,number,object):number} */
|
|
51
|
-
this.agePyramidHeight = opts.agePyramidHeight || ((c, r, zf) => r) //(cell,r,zf,vc) => ...
|
|
52
|
-
|
|
53
|
-
/** For pie chart, this is parameter for internal radius, so that the pie chart looks like a donut.
|
|
54
|
-
* 0 for normal pie charts, 0.5 to empty half of the radius.
|
|
55
|
-
* @type {number} */
|
|
56
|
-
this.pieChartInternalRadiusFactor = opts.pieChartInternalRadiusFactor || 0
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Draw cells as squares depending on their value.
|
|
61
|
-
*
|
|
62
|
-
* @param {Array.<import("../Dataset.js").Cell>} cells
|
|
63
|
-
* @param {number} r
|
|
64
|
-
* @param {import("../GeoCanvas.js").GeoCanvas} cg
|
|
65
|
-
*/
|
|
66
|
-
draw(cells, r, cg) {
|
|
67
|
-
//filter
|
|
68
|
-
if (this.filter) cells = cells.filter(this.filter)
|
|
69
|
-
|
|
70
|
-
//zoom factor
|
|
71
|
-
const zf = cg.getZf()
|
|
72
|
-
|
|
73
|
-
//get view scale
|
|
74
|
-
const vc = this.viewScale ? this.viewScale(cells, r, zf) : undefined
|
|
75
|
-
|
|
76
|
-
//nb categories - used for radar and agepyramid
|
|
77
|
-
const nbCat = Object.entries(this.color).length
|
|
78
|
-
|
|
79
|
-
//draw in geo coordinates
|
|
80
|
-
cg.setCanvasTransform()
|
|
81
|
-
|
|
82
|
-
//draw calls
|
|
83
|
-
for (let cell of cells) {
|
|
84
|
-
|
|
85
|
-
//size
|
|
86
|
-
const sG = this.size ? this.size(cell, r, zf, vc) : r
|
|
87
|
-
if (!sG) continue
|
|
88
|
-
|
|
89
|
-
//get offset
|
|
90
|
-
const offset = this.offset(cell, r, zf)
|
|
91
|
-
|
|
92
|
-
//get symbol type
|
|
93
|
-
const type_ = this.type ? this.type(cell, r, zf, vc) : 'flag'
|
|
94
|
-
|
|
95
|
-
//compute center position
|
|
96
|
-
const xc = cell.x + offset.dx + (type_ === 'agepyramid' ? 0 : r * 0.5)
|
|
97
|
-
const yc = cell.y + offset.dy + (type_ === 'agepyramid' ? 0 : r * 0.5)
|
|
98
|
-
|
|
99
|
-
//compute offset angle, when relevant
|
|
100
|
-
const offAng = this.offsetAngle ? (this.offsetAngle(cell, r, zf, vc) * Math.PI) / 180 : 0
|
|
101
|
-
|
|
102
|
-
if (type_ === 'agepyramid' || type_ === 'radar' || type_ === 'halftone') {
|
|
103
|
-
//get cell category max value
|
|
104
|
-
let maxVal = -Infinity
|
|
105
|
-
for (let key of Object.keys(this.color)) {
|
|
106
|
-
const v = +cell[key]
|
|
107
|
-
if (v > maxVal) maxVal = v
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
//cumul
|
|
111
|
-
let cumul = 0
|
|
112
|
-
if (type_ === 'agepyramid' && this.agePyramidHeight)
|
|
113
|
-
cumul = (r - this.agePyramidHeight(cell, r, zf, vc)) / 2
|
|
114
|
-
if (type_ === 'radar' || type_ === 'halftone') cumul = Math.PI / 2 + offAng
|
|
115
|
-
|
|
116
|
-
//compute the increment, which is the value to increment the cumul for each category
|
|
117
|
-
const incr =
|
|
118
|
-
type_ === 'agepyramid'
|
|
119
|
-
? (this.agePyramidHeight ? this.agePyramidHeight(cell, r, zf, vc) : r) / nbCat
|
|
120
|
-
: type_ === 'radar' || type_ === 'halftone'
|
|
121
|
-
? (2 * Math.PI) / nbCat
|
|
122
|
-
: undefined
|
|
123
|
-
if (incr === undefined) throw new Error('Unexpected symbol type:' + type_)
|
|
124
|
-
|
|
125
|
-
for (let [column, color] of Object.entries(this.color)) {
|
|
126
|
-
if (type_ === 'agepyramid') {
|
|
127
|
-
//set category color
|
|
128
|
-
cg.ctx.fillStyle = color
|
|
129
|
-
|
|
130
|
-
//get category value
|
|
131
|
-
const val = cell[column]
|
|
132
|
-
|
|
133
|
-
//compute category length - in geo
|
|
134
|
-
/** @type {number} */
|
|
135
|
-
const wG = (sG * val) / maxVal
|
|
136
|
-
|
|
137
|
-
//draw bar
|
|
138
|
-
cg.ctx.fillRect(xc + (r - wG) / 2, yc + cumul, wG, incr)
|
|
139
|
-
|
|
140
|
-
//next height
|
|
141
|
-
cumul += incr
|
|
142
|
-
} else if (type_ === 'radar') {
|
|
143
|
-
//set category color
|
|
144
|
-
cg.ctx.fillStyle = color
|
|
145
|
-
|
|
146
|
-
//get categroy value
|
|
147
|
-
const val = cell[column]
|
|
148
|
-
|
|
149
|
-
//compute category radius - in geo
|
|
150
|
-
/** @type {number} */
|
|
151
|
-
//const rG = this.radius(val, r, stat, cellStat, zf)
|
|
152
|
-
const rG = (sG / 2) * Math.sqrt(val / maxVal)
|
|
153
|
-
|
|
154
|
-
//draw angular sector
|
|
155
|
-
cg.ctx.beginPath()
|
|
156
|
-
cg.ctx.moveTo(xc, yc)
|
|
157
|
-
cg.ctx.arc(xc, yc, rG, cumul - incr, cumul)
|
|
158
|
-
cg.ctx.lineTo(xc, yc)
|
|
159
|
-
cg.ctx.fill()
|
|
160
|
-
|
|
161
|
-
//next angular sector
|
|
162
|
-
cumul += incr
|
|
163
|
-
} else if (type_ === 'halftone') {
|
|
164
|
-
//set category color
|
|
165
|
-
cg.ctx.fillStyle = color
|
|
166
|
-
|
|
167
|
-
//get categroy value
|
|
168
|
-
const val = cell[column]
|
|
169
|
-
|
|
170
|
-
//compute category radius - in geo
|
|
171
|
-
/** @type {number} */
|
|
172
|
-
const rG = sG * 0.333 * Math.sqrt(val / maxVal)
|
|
173
|
-
|
|
174
|
-
//draw circle
|
|
175
|
-
cg.ctx.beginPath()
|
|
176
|
-
cg.ctx.arc(
|
|
177
|
-
xc + r * 0.25 * Math.cos(cumul),
|
|
178
|
-
yc + r * 0.25 * Math.sin(cumul),
|
|
179
|
-
rG,
|
|
180
|
-
0,
|
|
181
|
-
2 * Math.PI
|
|
182
|
-
)
|
|
183
|
-
cg.ctx.fill()
|
|
184
|
-
|
|
185
|
-
//next angular sector
|
|
186
|
-
cumul += incr
|
|
187
|
-
} else {
|
|
188
|
-
throw new Error('Unexpected symbol type:' + type_)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
} else {
|
|
192
|
-
//compute total
|
|
193
|
-
let total = 0
|
|
194
|
-
for (let column of Object.keys(this.color)) {
|
|
195
|
-
const v = +cell[column]
|
|
196
|
-
if (!v) continue
|
|
197
|
-
total += v
|
|
198
|
-
}
|
|
199
|
-
if (!total || isNaN(total)) continue
|
|
200
|
-
|
|
201
|
-
//draw decomposition symbol
|
|
202
|
-
let cumul = 0
|
|
203
|
-
const d = r * (1 - sG / r) * 0.5
|
|
204
|
-
const ori = this.stripesOrientation(cell, r, zf, vc)
|
|
205
|
-
|
|
206
|
-
for (let [column, color] of Object.entries(this.color)) {
|
|
207
|
-
//get share
|
|
208
|
-
const share = cell[column] / total
|
|
209
|
-
if (!share || isNaN(share)) continue
|
|
210
|
-
|
|
211
|
-
//set color
|
|
212
|
-
cg.ctx.fillStyle = color
|
|
213
|
-
|
|
214
|
-
//draw symbol part
|
|
215
|
-
if (type_ === 'flag') {
|
|
216
|
-
//draw flag stripe
|
|
217
|
-
if (ori == 0) {
|
|
218
|
-
//horizontal
|
|
219
|
-
cg.ctx.fillRect(
|
|
220
|
-
cell.x + d + offset.dx,
|
|
221
|
-
cell.y + d + cumul * sG + offset.dy,
|
|
222
|
-
sG,
|
|
223
|
-
share * sG
|
|
224
|
-
)
|
|
225
|
-
} else {
|
|
226
|
-
//vertical
|
|
227
|
-
cg.ctx.fillRect(
|
|
228
|
-
cell.x + d + cumul * sG + offset.dx,
|
|
229
|
-
cell.y + d + offset.dy,
|
|
230
|
-
share * sG,
|
|
231
|
-
sG
|
|
232
|
-
)
|
|
233
|
-
}
|
|
234
|
-
} else if (type_ === 'piechart') {
|
|
235
|
-
//draw pie chart angular sector
|
|
236
|
-
|
|
237
|
-
//compute angles
|
|
238
|
-
const a1 = cumul * 2 * Math.PI
|
|
239
|
-
const a2 = (cumul + share) * 2 * Math.PI
|
|
240
|
-
|
|
241
|
-
//draw
|
|
242
|
-
cg.ctx.beginPath()
|
|
243
|
-
cg.ctx.moveTo(xc, yc)
|
|
244
|
-
cg.ctx.arc(xc, yc, sG * 0.5, a1 + offAng, a2 + offAng)
|
|
245
|
-
if (this.pieChartInternalRadiusFactor)
|
|
246
|
-
cg.ctx.arc(
|
|
247
|
-
xc,
|
|
248
|
-
yc,
|
|
249
|
-
sG * 0.5 * this.pieChartInternalRadiusFactor,
|
|
250
|
-
a1 + offAng,
|
|
251
|
-
a2 + offAng,
|
|
252
|
-
true
|
|
253
|
-
)
|
|
254
|
-
cg.ctx.closePath()
|
|
255
|
-
cg.ctx.fill()
|
|
256
|
-
} else if (type_ === 'ring') {
|
|
257
|
-
//draw ring
|
|
258
|
-
cg.ctx.beginPath()
|
|
259
|
-
cg.ctx.arc(xc, yc, Math.sqrt(1 - cumul) * sG * 0.5, 0, 2 * Math.PI)
|
|
260
|
-
cg.ctx.fill()
|
|
261
|
-
} else if (type_ === 'segment') {
|
|
262
|
-
//draw segment sections
|
|
263
|
-
const wG = (sG * sG) / r
|
|
264
|
-
if (ori == 0) {
|
|
265
|
-
//horizontal
|
|
266
|
-
cg.ctx.fillRect(
|
|
267
|
-
cell.x + offset.dx,
|
|
268
|
-
cell.y + (r - wG) / 2 + cumul * wG + offset.dy,
|
|
269
|
-
r,
|
|
270
|
-
share * wG
|
|
271
|
-
)
|
|
272
|
-
} else {
|
|
273
|
-
//vertical
|
|
274
|
-
cg.ctx.fillRect(
|
|
275
|
-
cell.x + cumul * r + offset.dx,
|
|
276
|
-
cell.y + (r - wG) / 2 + offset.dy,
|
|
277
|
-
share * r,
|
|
278
|
-
wG
|
|
279
|
-
)
|
|
280
|
-
}
|
|
281
|
-
} else {
|
|
282
|
-
throw new Error('Unexpected symbol type:' + type_)
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
cumul += share
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
//update legends
|
|
291
|
-
this.updateLegends({ style: this, r: r, zf: zf, viewScale: vc })
|
|
292
|
-
}
|
|
293
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
//@ts-check
|
|
2
|
-
'use strict'
|
|
3
|
-
|
|
4
|
-
import { Style } from '../Style.js'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* A very generic style that shows grid cells with specific color, size and shape.
|
|
8
|
-
* It can be used to show variables as cell colors, cell size, cell shape, or any combination of the three visual variables.
|
|
9
|
-
*
|
|
10
|
-
* @author Joseph Davies, Julien Gaffuri
|
|
11
|
-
*/
|
|
12
|
-
export class ShapeColorSizeStyle_ extends Style {
|
|
13
|
-
/** @param {object} opts */
|
|
14
|
-
constructor(opts) {
|
|
15
|
-
super(opts)
|
|
16
|
-
opts = opts || {}
|
|
17
|
-
|
|
18
|
-
/** A function returning the view scale.
|
|
19
|
-
* @type {function(Array.<import('../Dataset.js').Cell>,number, number):object} */
|
|
20
|
-
this.viewScale = opts.viewScale //(cells,r,z) => {}
|
|
21
|
-
|
|
22
|
-
/** A function returning the color of the cell.
|
|
23
|
-
* @type {function(import('../Dataset.js').Cell,number, number,object):string} */
|
|
24
|
-
this.color = opts.color //(c,r,z,o) => {}
|
|
25
|
-
|
|
26
|
-
/** A function returning the size of a cell in geographical unit.
|
|
27
|
-
* @type {function(import('../Dataset.js').Cell,number, number,object):number} */
|
|
28
|
-
this.size = opts.size //(c,r,z,o) => {}
|
|
29
|
-
|
|
30
|
-
/** A function returning the shape of a cell.
|
|
31
|
-
* @type {function(import("../Dataset.js").Cell,number, number,object):import("../Style.js").Shape} */
|
|
32
|
-
this.shape = opts.shape //(c,r,z,o) => {}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Draw cells as squares, with various colors and sizes.
|
|
37
|
-
*
|
|
38
|
-
* @param {Array.<import("../Dataset.js").Cell>} cells
|
|
39
|
-
* @param {number} r
|
|
40
|
-
* @param {import("../GeoCanvas.js").GeoCanvas} cg
|
|
41
|
-
*/
|
|
42
|
-
draw(cells, r, cg) {
|
|
43
|
-
//filter
|
|
44
|
-
if (this.filter) cells = cells.filter(this.filter)
|
|
45
|
-
|
|
46
|
-
//zoom factor
|
|
47
|
-
const zf = cg.getZf()
|
|
48
|
-
|
|
49
|
-
//get view scale
|
|
50
|
-
const vc = this.viewScale ? this.viewScale(cells, r, zf) : undefined
|
|
51
|
-
|
|
52
|
-
//draw with HTML canvas in geo coordinates
|
|
53
|
-
cg.setCanvasTransform()
|
|
54
|
-
|
|
55
|
-
const r2 = r * 0.5
|
|
56
|
-
for (let c of cells) {
|
|
57
|
-
//color
|
|
58
|
-
let col = this.color ? this.color(c, r, zf, vc) : "black"
|
|
59
|
-
if (!col || col === 'none') continue
|
|
60
|
-
|
|
61
|
-
//size
|
|
62
|
-
const size = this.size ? this.size(c, r, zf, vc) : r
|
|
63
|
-
if (!size) continue
|
|
64
|
-
|
|
65
|
-
//shape
|
|
66
|
-
const shape = this.shape ? this.shape(c, r, zf, vc) : 'square'
|
|
67
|
-
if (shape === 'none') continue
|
|
68
|
-
|
|
69
|
-
//get offset
|
|
70
|
-
const offset = this.offset(c, r, zf)
|
|
71
|
-
|
|
72
|
-
cg.ctx.fillStyle = col
|
|
73
|
-
if (shape === 'square') {
|
|
74
|
-
//draw square
|
|
75
|
-
const d = r * (1 - size / r) * 0.5
|
|
76
|
-
cg.ctx.fillRect(c.x + d + offset.dx, c.y + d + offset.dy, size, size)
|
|
77
|
-
} else if (shape === 'circle') {
|
|
78
|
-
//draw circle
|
|
79
|
-
cg.ctx.beginPath()
|
|
80
|
-
cg.ctx.arc(c.x + r2 + offset.dx, c.y + r2 + offset.dy, size * 0.5, 0, 2 * Math.PI, false)
|
|
81
|
-
cg.ctx.fill()
|
|
82
|
-
} else if (shape === 'donut') {
|
|
83
|
-
//draw donut
|
|
84
|
-
const xc = c.x + r2 + offset.dx,
|
|
85
|
-
yc = c.y + r2 + offset.dy
|
|
86
|
-
cg.ctx.beginPath()
|
|
87
|
-
cg.ctx.moveTo(xc, yc)
|
|
88
|
-
cg.ctx.arc(xc, yc, r2, 0, 2 * Math.PI)
|
|
89
|
-
cg.ctx.arc(xc, yc, (1 - size / r) * r2, 0, 2 * Math.PI, true)
|
|
90
|
-
cg.ctx.closePath()
|
|
91
|
-
cg.ctx.fill()
|
|
92
|
-
} else if (shape === 'diamond') {
|
|
93
|
-
const s2 = size * 0.5
|
|
94
|
-
cg.ctx.beginPath()
|
|
95
|
-
cg.ctx.moveTo(c.x + r2 - s2, c.y + r2)
|
|
96
|
-
cg.ctx.lineTo(c.x + r2, c.y + r2 + s2)
|
|
97
|
-
cg.ctx.lineTo(c.x + r2 + s2, c.y + r2)
|
|
98
|
-
cg.ctx.lineTo(c.x + r2, c.y + r2 - s2)
|
|
99
|
-
cg.ctx.fill()
|
|
100
|
-
} else {
|
|
101
|
-
throw new Error('Unexpected shape:' + shape)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
//update legends
|
|
106
|
-
this.updateLegends({ style: this, r: r, zf: zf, viewScale: vc })
|
|
107
|
-
}
|
|
108
|
-
}
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
//@ts-check
|
|
2
|
-
'use strict'
|
|
3
|
-
|
|
4
|
-
import { Style } from '../Style.js'
|
|
5
|
-
|
|
6
|
-
/** @typedef {{x:number,y:number,or:"v"|"h",v1:string|undefined,v2:string|undefined}} Side */
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A style to show the sides of grid cells based on their different categories.
|
|
10
|
-
*
|
|
11
|
-
* @author Julien Gaffuri
|
|
12
|
-
*/
|
|
13
|
-
export class SideCatStyle extends Style {
|
|
14
|
-
/** @param {object} opts */
|
|
15
|
-
constructor(opts) {
|
|
16
|
-
super(opts)
|
|
17
|
-
opts = opts || {}
|
|
18
|
-
|
|
19
|
-
/** The name of the column/attribute of the tabular data where to retrieve the categorical value.
|
|
20
|
-
* @type {string} */
|
|
21
|
-
this.col = opts.col
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The dictionary (string -> color) which give the color of each category.
|
|
25
|
-
* @type {object} */
|
|
26
|
-
this.color = opts.color
|
|
27
|
-
|
|
28
|
-
/** A function returning the width of a cell side line, in geo unit
|
|
29
|
-
* @type {function(Side,number,number):number} */
|
|
30
|
-
this.width = opts.width || ((side, r, z) => r * 0.2)
|
|
31
|
-
|
|
32
|
-
/** A fill color for the cells.
|
|
33
|
-
* @type {function(import("../Dataset").Cell):string} */
|
|
34
|
-
this.fillColor = opts.fillColor
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* @param {Array.<import("../Dataset").Cell>} cells
|
|
39
|
-
* @param {number} r
|
|
40
|
-
* @param {import("../GeoCanvas").GeoCanvas} cg
|
|
41
|
-
*/
|
|
42
|
-
draw(cells, r, cg) {
|
|
43
|
-
//filter
|
|
44
|
-
if (this.filter) cells = cells.filter(this.filter)
|
|
45
|
-
|
|
46
|
-
if (!cells || cells.length == 0) return
|
|
47
|
-
|
|
48
|
-
//zoom factor
|
|
49
|
-
const zf = cg.getZf()
|
|
50
|
-
|
|
51
|
-
/** @type {Array.<Side>} */
|
|
52
|
-
const sides = []
|
|
53
|
-
|
|
54
|
-
//make horizontal sides
|
|
55
|
-
//sort cells by x and y
|
|
56
|
-
cells.sort((c1, c2) => (c2.x == c1.x ? c1.y - c2.y : c1.x - c2.x))
|
|
57
|
-
let c1 = cells[0]
|
|
58
|
-
let v1 = c1[this.col]
|
|
59
|
-
for (let i = 1; i < cells.length; i++) {
|
|
60
|
-
let c2 = cells[i]
|
|
61
|
-
let v2 = c2[this.col]
|
|
62
|
-
|
|
63
|
-
if (c1.y + r == c2.y && c1.x == c2.x) {
|
|
64
|
-
//cells in same column and touch along horizontal side
|
|
65
|
-
//make shared side
|
|
66
|
-
if (v1 != v2) sides.push({ x: c1.x, y: c2.y, or: 'h', v1: v1, v2: v2 })
|
|
67
|
-
} else {
|
|
68
|
-
//cells do not touch along horizontal side
|
|
69
|
-
//make two sides: top one for c1, bottom for c2
|
|
70
|
-
sides.push({ x: c1.x, y: c1.y + r, or: 'h', v1: v1, v2: undefined })
|
|
71
|
-
sides.push({ x: c2.x, y: c2.y, or: 'h', v1: undefined, v2: v2 })
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
c1 = c2
|
|
75
|
-
v1 = v2
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
//make vertical sides
|
|
79
|
-
//sort cells by y and x
|
|
80
|
-
cells.sort((c1, c2) => (c2.y == c1.y ? c1.x - c2.x : c1.y - c2.y))
|
|
81
|
-
c1 = cells[0]
|
|
82
|
-
v1 = c1[this.col]
|
|
83
|
-
for (let i = 1; i < cells.length; i++) {
|
|
84
|
-
let c2 = cells[i]
|
|
85
|
-
let v2 = c2[this.col]
|
|
86
|
-
|
|
87
|
-
if (c1.x + r == c2.x && c1.y == c2.y) {
|
|
88
|
-
//cells in same row and touch along vertical side
|
|
89
|
-
//make shared side
|
|
90
|
-
if (v1 != v2) sides.push({ x: c1.x + r, y: c1.y, or: 'v', v1: v1, v2: v2 })
|
|
91
|
-
} else {
|
|
92
|
-
//cells do not touch along vertical side
|
|
93
|
-
//make two sides: right one for c1, left for c2
|
|
94
|
-
sides.push({ x: c1.x + r, y: c1.y, or: 'v', v1: v1, v2: undefined })
|
|
95
|
-
sides.push({ x: c2.x, y: c2.y, or: 'v', v1: undefined, v2: v2 })
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
c1 = c2
|
|
99
|
-
v1 = v2
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
if (sides.length == 0) return
|
|
104
|
-
|
|
105
|
-
//draw in geo coordinates
|
|
106
|
-
cg.setCanvasTransform()
|
|
107
|
-
|
|
108
|
-
//draw cells, if fillColor specified
|
|
109
|
-
if (this.fillColor)
|
|
110
|
-
for (let c of cells) {
|
|
111
|
-
const fc = this.fillColor(c)
|
|
112
|
-
if (!fc || fc == 'none') continue
|
|
113
|
-
cg.ctx.fillStyle = fc
|
|
114
|
-
cg.ctx.fillRect(c.x, c.y, r, r)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
//draw sides
|
|
118
|
-
cg.ctx.lineCap = 'butt'
|
|
119
|
-
for (let s of sides) {
|
|
120
|
-
//width
|
|
121
|
-
/** @type {number|undefined} */
|
|
122
|
-
const wG = this.width ? this.width(s, r, zf) : undefined
|
|
123
|
-
if (!wG || wG <= 0) continue
|
|
124
|
-
const w2 = wG * 0.5
|
|
125
|
-
|
|
126
|
-
//set color and width
|
|
127
|
-
cg.ctx.lineWidth = wG
|
|
128
|
-
|
|
129
|
-
//draw segment with correct orientation
|
|
130
|
-
if (s.or === 'h') {
|
|
131
|
-
//top line
|
|
132
|
-
if (s.v2) {
|
|
133
|
-
cg.ctx.beginPath()
|
|
134
|
-
cg.ctx.strokeStyle = this.color[s.v2]
|
|
135
|
-
cg.ctx.moveTo(s.x, s.y + w2)
|
|
136
|
-
cg.ctx.lineTo(s.x + r, s.y + w2)
|
|
137
|
-
cg.ctx.stroke()
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
//bottom line
|
|
141
|
-
if (s.v1) {
|
|
142
|
-
cg.ctx.beginPath()
|
|
143
|
-
cg.ctx.strokeStyle = this.color[s.v1]
|
|
144
|
-
cg.ctx.moveTo(s.x, s.y - w2)
|
|
145
|
-
cg.ctx.lineTo(s.x + r, s.y - w2)
|
|
146
|
-
cg.ctx.stroke()
|
|
147
|
-
}
|
|
148
|
-
} else {
|
|
149
|
-
//right line
|
|
150
|
-
if (s.v2) {
|
|
151
|
-
cg.ctx.beginPath()
|
|
152
|
-
cg.ctx.strokeStyle = this.color[s.v2]
|
|
153
|
-
cg.ctx.moveTo(s.x + w2, s.y)
|
|
154
|
-
cg.ctx.lineTo(s.x + w2, s.y + r)
|
|
155
|
-
cg.ctx.stroke()
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
//left line
|
|
159
|
-
if (s.v1) {
|
|
160
|
-
cg.ctx.beginPath()
|
|
161
|
-
cg.ctx.strokeStyle = this.color[s.v1]
|
|
162
|
-
cg.ctx.moveTo(s.x - w2, s.y)
|
|
163
|
-
cg.ctx.lineTo(s.x - w2, s.y + r)
|
|
164
|
-
cg.ctx.stroke()
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
//update legends
|
|
170
|
-
this.updateLegends({ style: this, r: r, zf: zf })
|
|
171
|
-
}
|
|
172
|
-
}
|
|
File without changes
|