gridviz 2.0.45 → 2.0.47
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 +119 -119
- package/dist/gridviz.js +7559 -7549
- package/dist/gridviz.min.js +1 -1
- package/package.json +78 -76
- package/src/App.js +668 -668
- package/src/BackgroundLayer.js +182 -182
- package/src/BackgroundLayerWMS.js +131 -131
- package/src/Dataset.js +76 -76
- package/src/DatasetComponent.js +101 -101
- package/src/GeoCanvas.js +420 -420
- package/src/LabelLayer.js +190 -190
- package/src/Layer.js +92 -92
- package/src/Legend.js +86 -86
- package/src/LineLayer.js +150 -150
- package/src/Style.js +114 -114
- package/src/Tooltip.js +172 -172
- package/src/dataset/COGeo.js +24 -24
- package/src/dataset/CSVGrid.js +104 -104
- package/src/dataset/GeoTIFF.js +127 -127
- package/src/dataset/GridTile.js +41 -41
- package/src/dataset/TiledGrid.js +264 -264
- package/src/index.js +177 -176
- package/src/legend/ColorCategoryLegend.js +124 -124
- package/src/legend/ColorDiscreteLegend.js +103 -103
- package/src/legend/ColorLegend.js +124 -124
- package/src/legend/SegmentOrientationLegend.js +87 -87
- package/src/legend/SegmentWidthLegend.js +97 -97
- package/src/legend/SizeLegend.js +141 -141
- package/src/style/CompositionStyle.js +300 -300
- package/src/style/ContourStyle.js +54 -54
- package/src/style/DotDensityStyle.js +156 -156
- package/src/style/JoyPlotStyle.js +135 -135
- package/src/style/LegoStyle.js +112 -112
- package/src/style/MosaicStyle.js +107 -107
- package/src/style/NinjaStarStyle.js +124 -124
- package/src/style/PillarStyle.js +230 -230
- package/src/style/SegmentStyle.js +142 -142
- package/src/style/ShapeColorSizeStyle.js +126 -126
- package/src/style/SideCatStyle.js +172 -172
- package/src/style/SideStyle.js +218 -218
- package/src/style/SquareColorCatWGLStyle.js +120 -120
- package/src/style/SquareColorWGLStyle.js +146 -146
- package/src/style/StrokeStyle.js +131 -131
- package/src/style/TanakaStyle.js +160 -160
- package/src/style/TextStyle.js +132 -132
- package/src/utils/Utils.js +35 -35
- package/src/utils/WebGLRectangleColoring.js +106 -106
- package/src/utils/WebGLSquareColoring.js +118 -118
- package/src/utils/WebGLSquareColoringAdvanced.js +230 -230
- package/src/utils/WebGLSquareColoringCatAdvanced.js +133 -133
- package/src/utils/WebGLSquareColoringKS.js +153 -153
- package/src/utils/stretching.js +131 -131
- package/src/utils/webGLUtils.js +71 -71
- package/src/utils/zoomUtils.js +142 -142
|
@@ -1,300 +1,300 @@
|
|
|
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
|
-
/**
|
|
23
|
-
* The dictionary (string -> color) which give the color of each category.
|
|
24
|
-
* @type {object} */
|
|
25
|
-
this.color = opts.color
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* A function returning the type of decomposition symbol of a cell, @see CompositionType
|
|
29
|
-
* @type {function(import("../Dataset").Cell):CompositionType} */
|
|
30
|
-
this.type = opts.type
|
|
31
|
-
|
|
32
|
-
/** The column where to get the size values.
|
|
33
|
-
* @type {string} */
|
|
34
|
-
this.sizeCol = opts.sizeCol
|
|
35
|
-
|
|
36
|
-
/** A function returning the size of a cell.
|
|
37
|
-
* @type {function(number,number,import("../Style").Stat|undefined,number):number} */
|
|
38
|
-
this.size = opts.size || ((v, r, s, zf) => r)
|
|
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").Cell,number,number):number} */
|
|
42
|
-
this.stripesOrientation = opts.stripesOrientation || (() => 0) //(c,r,zf) => ...
|
|
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").Cell,number,number):number} */
|
|
47
|
-
this.offsetAngle = opts.offsetAngle || (() => 0) //(cell,r,zf) => ...
|
|
48
|
-
|
|
49
|
-
/** The function specifying the height of the age pyramid, in geo unit.
|
|
50
|
-
* @type {function(import("../Dataset").Cell,number,number):number} */
|
|
51
|
-
this.agePyramidHeight = opts.agePyramidHeight || ((c, r, zf) => r) //(cell,r,zf) => ...
|
|
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").Cell>} cells
|
|
63
|
-
* @param {number} r
|
|
64
|
-
* @param {import("../GeoCanvas").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
|
-
let stat
|
|
74
|
-
if (this.sizeCol) {
|
|
75
|
-
//if size is used, sort cells by size so that the biggest are drawn first
|
|
76
|
-
cells.sort((c1, c2) => c2[this.sizeCol] - c1[this.sizeCol])
|
|
77
|
-
//and compute statistics
|
|
78
|
-
stat = Style.getStatistics(cells, (c) => c[this.sizeCol], true)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
//nb categories - used for radar and agepyramid
|
|
82
|
-
const nbCat = Object.entries(this.color).length
|
|
83
|
-
|
|
84
|
-
//draw in geo coordinates
|
|
85
|
-
cg.setCanvasTransform()
|
|
86
|
-
|
|
87
|
-
//draw calls
|
|
88
|
-
for (let cell of cells) {
|
|
89
|
-
//size
|
|
90
|
-
/** @type {function(number,number,import("../Style").Stat|undefined,number):number} */
|
|
91
|
-
let s_ = this.size || (() => r)
|
|
92
|
-
//size - in geo
|
|
93
|
-
/** @type {number} */
|
|
94
|
-
const sG = s_(cell[this.sizeCol], r, stat, zf)
|
|
95
|
-
|
|
96
|
-
//get offset
|
|
97
|
-
const offset = this.offset(cell, r, zf)
|
|
98
|
-
|
|
99
|
-
//get symbol type
|
|
100
|
-
const type_ = this.type ? this.type(cell) : 'flag'
|
|
101
|
-
|
|
102
|
-
//compute center position
|
|
103
|
-
const xc = cell.x + offset.dx + (type_ === 'agepyramid' ? 0 : r * 0.5)
|
|
104
|
-
const yc = cell.y + offset.dy + (type_ === 'agepyramid' ? 0 : r * 0.5)
|
|
105
|
-
|
|
106
|
-
//compute offset angle, when relevant
|
|
107
|
-
const offAng = this.offsetAngle ? (this.offsetAngle(cell, r, zf) * Math.PI) / 180 : 0
|
|
108
|
-
|
|
109
|
-
if (type_ === 'agepyramid' || type_ === 'radar' || type_ === 'halftone') {
|
|
110
|
-
//get cell category max value
|
|
111
|
-
let maxVal = -Infinity
|
|
112
|
-
for (let key of Object.keys(this.color)) {
|
|
113
|
-
const v = +cell[key]
|
|
114
|
-
if (v > maxVal) maxVal = v
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
//cumul
|
|
118
|
-
let cumul = 0
|
|
119
|
-
if (type_ === 'agepyramid' && this.agePyramidHeight)
|
|
120
|
-
cumul = (r - this.agePyramidHeight(cell, r, zf)) / 2
|
|
121
|
-
if (type_ === 'radar' || type_ === 'halftone') cumul = Math.PI / 2 + offAng
|
|
122
|
-
|
|
123
|
-
//compute the increment, which is the value to increment the cumul for each category
|
|
124
|
-
const incr =
|
|
125
|
-
type_ === 'agepyramid'
|
|
126
|
-
? (this.agePyramidHeight ? this.agePyramidHeight(cell, r, zf) : r) / nbCat
|
|
127
|
-
: type_ === 'radar' || type_ === 'halftone'
|
|
128
|
-
? (2 * Math.PI) / nbCat
|
|
129
|
-
: undefined
|
|
130
|
-
if (incr === undefined) throw new Error('Unexpected symbol type:' + type_)
|
|
131
|
-
|
|
132
|
-
for (let [column, color] of Object.entries(this.color)) {
|
|
133
|
-
if (type_ === 'agepyramid') {
|
|
134
|
-
//set category color
|
|
135
|
-
cg.ctx.fillStyle = color
|
|
136
|
-
|
|
137
|
-
//get category value
|
|
138
|
-
const val = cell[column]
|
|
139
|
-
|
|
140
|
-
//compute category length - in geo
|
|
141
|
-
/** @type {number} */
|
|
142
|
-
const wG = (sG * val) / maxVal
|
|
143
|
-
|
|
144
|
-
//draw bar
|
|
145
|
-
cg.ctx.fillRect(xc + (r - wG) / 2, yc + cumul, wG, incr)
|
|
146
|
-
|
|
147
|
-
//next height
|
|
148
|
-
cumul += incr
|
|
149
|
-
} else if (type_ === 'radar') {
|
|
150
|
-
//set category color
|
|
151
|
-
cg.ctx.fillStyle = color
|
|
152
|
-
|
|
153
|
-
//get categroy value
|
|
154
|
-
const val = cell[column]
|
|
155
|
-
|
|
156
|
-
//compute category radius - in geo
|
|
157
|
-
/** @type {number} */
|
|
158
|
-
//const rG = this.radius(val, r, stat, cellStat, zf)
|
|
159
|
-
const rG = (sG / 2) * Math.sqrt(val / maxVal)
|
|
160
|
-
|
|
161
|
-
//draw angular sector
|
|
162
|
-
cg.ctx.beginPath()
|
|
163
|
-
cg.ctx.moveTo(xc, yc)
|
|
164
|
-
cg.ctx.arc(xc, yc, rG, cumul - incr, cumul)
|
|
165
|
-
cg.ctx.lineTo(xc, yc)
|
|
166
|
-
cg.ctx.fill()
|
|
167
|
-
|
|
168
|
-
//next angular sector
|
|
169
|
-
cumul += incr
|
|
170
|
-
} else if (type_ === 'halftone') {
|
|
171
|
-
//set category color
|
|
172
|
-
cg.ctx.fillStyle = color
|
|
173
|
-
|
|
174
|
-
//get categroy value
|
|
175
|
-
const val = cell[column]
|
|
176
|
-
|
|
177
|
-
//compute category radius - in geo
|
|
178
|
-
/** @type {number} */
|
|
179
|
-
const rG = sG * 0.333 * Math.sqrt(val / maxVal)
|
|
180
|
-
|
|
181
|
-
//draw circle
|
|
182
|
-
cg.ctx.beginPath()
|
|
183
|
-
cg.ctx.arc(
|
|
184
|
-
xc + r * 0.25 * Math.cos(cumul),
|
|
185
|
-
yc + r * 0.25 * Math.sin(cumul),
|
|
186
|
-
rG,
|
|
187
|
-
0,
|
|
188
|
-
2 * Math.PI
|
|
189
|
-
)
|
|
190
|
-
cg.ctx.fill()
|
|
191
|
-
|
|
192
|
-
//next angular sector
|
|
193
|
-
cumul += incr
|
|
194
|
-
} else {
|
|
195
|
-
throw new Error('Unexpected symbol type:' + type_)
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
} else {
|
|
199
|
-
//compute total
|
|
200
|
-
let total = 0
|
|
201
|
-
for (let column of Object.keys(this.color)) {
|
|
202
|
-
const v = +cell[column]
|
|
203
|
-
if (!v) continue
|
|
204
|
-
total += v
|
|
205
|
-
}
|
|
206
|
-
if (!total || isNaN(total)) continue
|
|
207
|
-
|
|
208
|
-
//draw decomposition symbol
|
|
209
|
-
let cumul = 0
|
|
210
|
-
const d = r * (1 - sG / r) * 0.5
|
|
211
|
-
const ori = this.stripesOrientation(cell, r, zf)
|
|
212
|
-
|
|
213
|
-
for (let [column, color] of Object.entries(this.color)) {
|
|
214
|
-
//get share
|
|
215
|
-
const share = cell[column] / total
|
|
216
|
-
if (!share || isNaN(share)) continue
|
|
217
|
-
|
|
218
|
-
//set color
|
|
219
|
-
cg.ctx.fillStyle = color
|
|
220
|
-
|
|
221
|
-
//draw symbol part
|
|
222
|
-
if (type_ === 'flag') {
|
|
223
|
-
//draw flag stripe
|
|
224
|
-
if (ori == 0) {
|
|
225
|
-
//horizontal
|
|
226
|
-
cg.ctx.fillRect(
|
|
227
|
-
cell.x + d + offset.dx,
|
|
228
|
-
cell.y + d + cumul * sG + offset.dy,
|
|
229
|
-
sG,
|
|
230
|
-
share * sG
|
|
231
|
-
)
|
|
232
|
-
} else {
|
|
233
|
-
//vertical
|
|
234
|
-
cg.ctx.fillRect(
|
|
235
|
-
cell.x + d + cumul * sG + offset.dx,
|
|
236
|
-
cell.y + d + offset.dy,
|
|
237
|
-
share * sG,
|
|
238
|
-
sG
|
|
239
|
-
)
|
|
240
|
-
}
|
|
241
|
-
} else if (type_ === 'piechart') {
|
|
242
|
-
//draw pie chart angular sector
|
|
243
|
-
|
|
244
|
-
//compute angles
|
|
245
|
-
const a1 = cumul * 2 * Math.PI
|
|
246
|
-
const a2 = (cumul + share) * 2 * Math.PI
|
|
247
|
-
|
|
248
|
-
//draw
|
|
249
|
-
cg.ctx.beginPath()
|
|
250
|
-
cg.ctx.moveTo(xc, yc)
|
|
251
|
-
cg.ctx.arc(xc, yc, sG * 0.5, a1 + offAng, a2 + offAng)
|
|
252
|
-
if (this.pieChartInternalRadiusFactor)
|
|
253
|
-
cg.ctx.arc(
|
|
254
|
-
xc,
|
|
255
|
-
yc,
|
|
256
|
-
sG * 0.5 * this.pieChartInternalRadiusFactor,
|
|
257
|
-
a1 + offAng,
|
|
258
|
-
a2 + offAng,
|
|
259
|
-
true
|
|
260
|
-
)
|
|
261
|
-
cg.ctx.closePath()
|
|
262
|
-
cg.ctx.fill()
|
|
263
|
-
} else if (type_ === 'ring') {
|
|
264
|
-
//draw ring
|
|
265
|
-
cg.ctx.beginPath()
|
|
266
|
-
cg.ctx.arc(xc, yc, Math.sqrt(1 - cumul) * sG * 0.5, 0, 2 * Math.PI)
|
|
267
|
-
cg.ctx.fill()
|
|
268
|
-
} else if (type_ === 'segment') {
|
|
269
|
-
//draw segment sections
|
|
270
|
-
const wG = (sG * sG) / r
|
|
271
|
-
if (ori == 0) {
|
|
272
|
-
//horizontal
|
|
273
|
-
cg.ctx.fillRect(
|
|
274
|
-
cell.x + offset.dx,
|
|
275
|
-
cell.y + (r - wG) / 2 + cumul * wG + offset.dy,
|
|
276
|
-
r,
|
|
277
|
-
share * wG
|
|
278
|
-
)
|
|
279
|
-
} else {
|
|
280
|
-
//vertical
|
|
281
|
-
cg.ctx.fillRect(
|
|
282
|
-
cell.x + cumul * r + offset.dx,
|
|
283
|
-
cell.y + (r - wG) / 2 + offset.dy,
|
|
284
|
-
share * r,
|
|
285
|
-
wG
|
|
286
|
-
)
|
|
287
|
-
}
|
|
288
|
-
} else {
|
|
289
|
-
throw new Error('Unexpected symbol type:' + type_)
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
cumul += share
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
//update legends
|
|
298
|
-
this.updateLegends({ style: this, r: r, zf: zf, sSize: stat })
|
|
299
|
-
}
|
|
300
|
-
}
|
|
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
|
+
/**
|
|
23
|
+
* The dictionary (string -> color) which give the color of each category.
|
|
24
|
+
* @type {object} */
|
|
25
|
+
this.color = opts.color
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A function returning the type of decomposition symbol of a cell, @see CompositionType
|
|
29
|
+
* @type {function(import("../Dataset").Cell):CompositionType} */
|
|
30
|
+
this.type = opts.type
|
|
31
|
+
|
|
32
|
+
/** The column where to get the size values.
|
|
33
|
+
* @type {string} */
|
|
34
|
+
this.sizeCol = opts.sizeCol
|
|
35
|
+
|
|
36
|
+
/** A function returning the size of a cell.
|
|
37
|
+
* @type {function(number,number,import("../Style").Stat|undefined,number):number} */
|
|
38
|
+
this.size = opts.size || ((v, r, s, zf) => r)
|
|
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").Cell,number,number):number} */
|
|
42
|
+
this.stripesOrientation = opts.stripesOrientation || (() => 0) //(c,r,zf) => ...
|
|
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").Cell,number,number):number} */
|
|
47
|
+
this.offsetAngle = opts.offsetAngle || (() => 0) //(cell,r,zf) => ...
|
|
48
|
+
|
|
49
|
+
/** The function specifying the height of the age pyramid, in geo unit.
|
|
50
|
+
* @type {function(import("../Dataset").Cell,number,number):number} */
|
|
51
|
+
this.agePyramidHeight = opts.agePyramidHeight || ((c, r, zf) => r) //(cell,r,zf) => ...
|
|
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").Cell>} cells
|
|
63
|
+
* @param {number} r
|
|
64
|
+
* @param {import("../GeoCanvas").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
|
+
let stat
|
|
74
|
+
if (this.sizeCol) {
|
|
75
|
+
//if size is used, sort cells by size so that the biggest are drawn first
|
|
76
|
+
cells.sort((c1, c2) => c2[this.sizeCol] - c1[this.sizeCol])
|
|
77
|
+
//and compute statistics
|
|
78
|
+
stat = Style.getStatistics(cells, (c) => c[this.sizeCol], true)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
//nb categories - used for radar and agepyramid
|
|
82
|
+
const nbCat = Object.entries(this.color).length
|
|
83
|
+
|
|
84
|
+
//draw in geo coordinates
|
|
85
|
+
cg.setCanvasTransform()
|
|
86
|
+
|
|
87
|
+
//draw calls
|
|
88
|
+
for (let cell of cells) {
|
|
89
|
+
//size
|
|
90
|
+
/** @type {function(number,number,import("../Style").Stat|undefined,number):number} */
|
|
91
|
+
let s_ = this.size || (() => r)
|
|
92
|
+
//size - in geo
|
|
93
|
+
/** @type {number} */
|
|
94
|
+
const sG = s_(cell[this.sizeCol], r, stat, zf)
|
|
95
|
+
|
|
96
|
+
//get offset
|
|
97
|
+
const offset = this.offset(cell, r, zf)
|
|
98
|
+
|
|
99
|
+
//get symbol type
|
|
100
|
+
const type_ = this.type ? this.type(cell) : 'flag'
|
|
101
|
+
|
|
102
|
+
//compute center position
|
|
103
|
+
const xc = cell.x + offset.dx + (type_ === 'agepyramid' ? 0 : r * 0.5)
|
|
104
|
+
const yc = cell.y + offset.dy + (type_ === 'agepyramid' ? 0 : r * 0.5)
|
|
105
|
+
|
|
106
|
+
//compute offset angle, when relevant
|
|
107
|
+
const offAng = this.offsetAngle ? (this.offsetAngle(cell, r, zf) * Math.PI) / 180 : 0
|
|
108
|
+
|
|
109
|
+
if (type_ === 'agepyramid' || type_ === 'radar' || type_ === 'halftone') {
|
|
110
|
+
//get cell category max value
|
|
111
|
+
let maxVal = -Infinity
|
|
112
|
+
for (let key of Object.keys(this.color)) {
|
|
113
|
+
const v = +cell[key]
|
|
114
|
+
if (v > maxVal) maxVal = v
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//cumul
|
|
118
|
+
let cumul = 0
|
|
119
|
+
if (type_ === 'agepyramid' && this.agePyramidHeight)
|
|
120
|
+
cumul = (r - this.agePyramidHeight(cell, r, zf)) / 2
|
|
121
|
+
if (type_ === 'radar' || type_ === 'halftone') cumul = Math.PI / 2 + offAng
|
|
122
|
+
|
|
123
|
+
//compute the increment, which is the value to increment the cumul for each category
|
|
124
|
+
const incr =
|
|
125
|
+
type_ === 'agepyramid'
|
|
126
|
+
? (this.agePyramidHeight ? this.agePyramidHeight(cell, r, zf) : r) / nbCat
|
|
127
|
+
: type_ === 'radar' || type_ === 'halftone'
|
|
128
|
+
? (2 * Math.PI) / nbCat
|
|
129
|
+
: undefined
|
|
130
|
+
if (incr === undefined) throw new Error('Unexpected symbol type:' + type_)
|
|
131
|
+
|
|
132
|
+
for (let [column, color] of Object.entries(this.color)) {
|
|
133
|
+
if (type_ === 'agepyramid') {
|
|
134
|
+
//set category color
|
|
135
|
+
cg.ctx.fillStyle = color
|
|
136
|
+
|
|
137
|
+
//get category value
|
|
138
|
+
const val = cell[column]
|
|
139
|
+
|
|
140
|
+
//compute category length - in geo
|
|
141
|
+
/** @type {number} */
|
|
142
|
+
const wG = (sG * val) / maxVal
|
|
143
|
+
|
|
144
|
+
//draw bar
|
|
145
|
+
cg.ctx.fillRect(xc + (r - wG) / 2, yc + cumul, wG, incr)
|
|
146
|
+
|
|
147
|
+
//next height
|
|
148
|
+
cumul += incr
|
|
149
|
+
} else if (type_ === 'radar') {
|
|
150
|
+
//set category color
|
|
151
|
+
cg.ctx.fillStyle = color
|
|
152
|
+
|
|
153
|
+
//get categroy value
|
|
154
|
+
const val = cell[column]
|
|
155
|
+
|
|
156
|
+
//compute category radius - in geo
|
|
157
|
+
/** @type {number} */
|
|
158
|
+
//const rG = this.radius(val, r, stat, cellStat, zf)
|
|
159
|
+
const rG = (sG / 2) * Math.sqrt(val / maxVal)
|
|
160
|
+
|
|
161
|
+
//draw angular sector
|
|
162
|
+
cg.ctx.beginPath()
|
|
163
|
+
cg.ctx.moveTo(xc, yc)
|
|
164
|
+
cg.ctx.arc(xc, yc, rG, cumul - incr, cumul)
|
|
165
|
+
cg.ctx.lineTo(xc, yc)
|
|
166
|
+
cg.ctx.fill()
|
|
167
|
+
|
|
168
|
+
//next angular sector
|
|
169
|
+
cumul += incr
|
|
170
|
+
} else if (type_ === 'halftone') {
|
|
171
|
+
//set category color
|
|
172
|
+
cg.ctx.fillStyle = color
|
|
173
|
+
|
|
174
|
+
//get categroy value
|
|
175
|
+
const val = cell[column]
|
|
176
|
+
|
|
177
|
+
//compute category radius - in geo
|
|
178
|
+
/** @type {number} */
|
|
179
|
+
const rG = sG * 0.333 * Math.sqrt(val / maxVal)
|
|
180
|
+
|
|
181
|
+
//draw circle
|
|
182
|
+
cg.ctx.beginPath()
|
|
183
|
+
cg.ctx.arc(
|
|
184
|
+
xc + r * 0.25 * Math.cos(cumul),
|
|
185
|
+
yc + r * 0.25 * Math.sin(cumul),
|
|
186
|
+
rG,
|
|
187
|
+
0,
|
|
188
|
+
2 * Math.PI
|
|
189
|
+
)
|
|
190
|
+
cg.ctx.fill()
|
|
191
|
+
|
|
192
|
+
//next angular sector
|
|
193
|
+
cumul += incr
|
|
194
|
+
} else {
|
|
195
|
+
throw new Error('Unexpected symbol type:' + type_)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
//compute total
|
|
200
|
+
let total = 0
|
|
201
|
+
for (let column of Object.keys(this.color)) {
|
|
202
|
+
const v = +cell[column]
|
|
203
|
+
if (!v) continue
|
|
204
|
+
total += v
|
|
205
|
+
}
|
|
206
|
+
if (!total || isNaN(total)) continue
|
|
207
|
+
|
|
208
|
+
//draw decomposition symbol
|
|
209
|
+
let cumul = 0
|
|
210
|
+
const d = r * (1 - sG / r) * 0.5
|
|
211
|
+
const ori = this.stripesOrientation(cell, r, zf)
|
|
212
|
+
|
|
213
|
+
for (let [column, color] of Object.entries(this.color)) {
|
|
214
|
+
//get share
|
|
215
|
+
const share = cell[column] / total
|
|
216
|
+
if (!share || isNaN(share)) continue
|
|
217
|
+
|
|
218
|
+
//set color
|
|
219
|
+
cg.ctx.fillStyle = color
|
|
220
|
+
|
|
221
|
+
//draw symbol part
|
|
222
|
+
if (type_ === 'flag') {
|
|
223
|
+
//draw flag stripe
|
|
224
|
+
if (ori == 0) {
|
|
225
|
+
//horizontal
|
|
226
|
+
cg.ctx.fillRect(
|
|
227
|
+
cell.x + d + offset.dx,
|
|
228
|
+
cell.y + d + cumul * sG + offset.dy,
|
|
229
|
+
sG,
|
|
230
|
+
share * sG
|
|
231
|
+
)
|
|
232
|
+
} else {
|
|
233
|
+
//vertical
|
|
234
|
+
cg.ctx.fillRect(
|
|
235
|
+
cell.x + d + cumul * sG + offset.dx,
|
|
236
|
+
cell.y + d + offset.dy,
|
|
237
|
+
share * sG,
|
|
238
|
+
sG
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
} else if (type_ === 'piechart') {
|
|
242
|
+
//draw pie chart angular sector
|
|
243
|
+
|
|
244
|
+
//compute angles
|
|
245
|
+
const a1 = cumul * 2 * Math.PI
|
|
246
|
+
const a2 = (cumul + share) * 2 * Math.PI
|
|
247
|
+
|
|
248
|
+
//draw
|
|
249
|
+
cg.ctx.beginPath()
|
|
250
|
+
cg.ctx.moveTo(xc, yc)
|
|
251
|
+
cg.ctx.arc(xc, yc, sG * 0.5, a1 + offAng, a2 + offAng)
|
|
252
|
+
if (this.pieChartInternalRadiusFactor)
|
|
253
|
+
cg.ctx.arc(
|
|
254
|
+
xc,
|
|
255
|
+
yc,
|
|
256
|
+
sG * 0.5 * this.pieChartInternalRadiusFactor,
|
|
257
|
+
a1 + offAng,
|
|
258
|
+
a2 + offAng,
|
|
259
|
+
true
|
|
260
|
+
)
|
|
261
|
+
cg.ctx.closePath()
|
|
262
|
+
cg.ctx.fill()
|
|
263
|
+
} else if (type_ === 'ring') {
|
|
264
|
+
//draw ring
|
|
265
|
+
cg.ctx.beginPath()
|
|
266
|
+
cg.ctx.arc(xc, yc, Math.sqrt(1 - cumul) * sG * 0.5, 0, 2 * Math.PI)
|
|
267
|
+
cg.ctx.fill()
|
|
268
|
+
} else if (type_ === 'segment') {
|
|
269
|
+
//draw segment sections
|
|
270
|
+
const wG = (sG * sG) / r
|
|
271
|
+
if (ori == 0) {
|
|
272
|
+
//horizontal
|
|
273
|
+
cg.ctx.fillRect(
|
|
274
|
+
cell.x + offset.dx,
|
|
275
|
+
cell.y + (r - wG) / 2 + cumul * wG + offset.dy,
|
|
276
|
+
r,
|
|
277
|
+
share * wG
|
|
278
|
+
)
|
|
279
|
+
} else {
|
|
280
|
+
//vertical
|
|
281
|
+
cg.ctx.fillRect(
|
|
282
|
+
cell.x + cumul * r + offset.dx,
|
|
283
|
+
cell.y + (r - wG) / 2 + offset.dy,
|
|
284
|
+
share * r,
|
|
285
|
+
wG
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
throw new Error('Unexpected symbol type:' + type_)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
cumul += share
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
//update legends
|
|
298
|
+
this.updateLegends({ style: this, r: r, zf: zf, sSize: stat })
|
|
299
|
+
}
|
|
300
|
+
}
|