gridviz 2.1.13 → 2.1.14
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 +124 -125
- package/dist/gridviz.js +8341 -8339
- package/dist/gridviz.min.js +1 -1
- package/package.json +77 -77
- package/src/App.js +758 -758
- package/src/BackgroundLayer.js +182 -182
- package/src/BackgroundLayerWMS.js +131 -131
- package/src/Dataset.js +76 -76
- package/src/DatasetComponent.js +113 -113
- package/src/GeoCanvas.js +420 -420
- package/src/LabelLayer.js +190 -190
- package/src/Layer.js +103 -103
- package/src/Legend.js +86 -86
- package/src/LineLayer.js +150 -150
- package/src/Style.js +131 -129
- package/src/Tooltip.js +180 -180
- package/src/button/Button.js +54 -54
- package/src/button/FullscreenButton.js +98 -98
- package/src/button/ZoomButtons.js +72 -72
- 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/LGrid.js +54 -54
- package/src/dataset/TiledGrid.js +264 -264
- package/src/index.js +71 -71
- package/src/legend/ColorCategoryLegend.js +124 -124
- package/src/legend/ColorDiscreteLegend.js +103 -103
- package/src/legend/ColorLegend.js +128 -128
- package/src/legend/SegmentOrientationLegend.js +87 -87
- package/src/legend/SegmentWidthLegend.js +101 -101
- 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/IsoFenceStyle.js +262 -262
- 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 +125 -125
- package/src/style/PillarStyle.js +230 -230
- package/src/style/SegmentStyle.js +142 -142
- package/src/style/ShapeColorSizeStyle.js +152 -152
- package/src/style/SideCatStyle.js +172 -172
- package/src/style/SideStyle.js +216 -216
- 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/style/TimeSeriesStyle.js +259 -259
- 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,262 +1,262 @@
|
|
|
1
|
-
//@ts-check
|
|
2
|
-
'use strict'
|
|
3
|
-
|
|
4
|
-
import { Style } from '../Style.js'
|
|
5
|
-
|
|
6
|
-
/** @typedef {{x:number,y:number,or:"v"|"h",c1:import('../Dataset.js').Cell|undefined,c2:import('../Dataset.js').Cell|undefined}} Side */
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @author Julien Gaffuri
|
|
10
|
-
*/
|
|
11
|
-
export class IsoFenceStyle extends Style {
|
|
12
|
-
|
|
13
|
-
/** @param {object} opts */
|
|
14
|
-
constructor(opts) {
|
|
15
|
-
super(opts)
|
|
16
|
-
opts = opts || {}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* The dictionary (string -> color) which give the color of each category.
|
|
20
|
-
* @type {object} */
|
|
21
|
-
this.color = opts.color
|
|
22
|
-
|
|
23
|
-
/** The column where to get the height values.
|
|
24
|
-
* @type {string} */
|
|
25
|
-
this.heightCol = opts.heightCol
|
|
26
|
-
|
|
27
|
-
/** A function returning the height of a cell.
|
|
28
|
-
* @type {function(number,number,import("../Style").Stat|undefined,number):number} */
|
|
29
|
-
this.height = opts.height || ((v, r, s, zf) => r * 0.4)
|
|
30
|
-
|
|
31
|
-
/** The perspective angle, in degree, within [-180,180], from [O,x] axis.
|
|
32
|
-
* @type {number} */
|
|
33
|
-
this.angle = opts.angle != undefined ? opts.angle : 50
|
|
34
|
-
|
|
35
|
-
/** A function returning the corner line stroke style.
|
|
36
|
-
* @type {function(import('../Dataset.js').Cell,number,number,number):string} */
|
|
37
|
-
this.cornerLineStrokeColor = opts.cornerLineStrokeColor || ((c, r, zf, angle) => "#999")
|
|
38
|
-
|
|
39
|
-
/** A function returning the corner line width.
|
|
40
|
-
* @type {function(import('../Dataset.js').Cell,number,number,number):number} */
|
|
41
|
-
this.cornerLineWidth = opts.cornerLineWidth || ((c, r, zf, angle) => (angle % 90 == 0 ? 0 : 0.8 * zf))
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Show vertical cross-sections.
|
|
45
|
-
* @type {boolean} */
|
|
46
|
-
this.sVert = opts.sVert != undefined ? opts.sVert : true
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Show horizontal cross-sections.
|
|
50
|
-
* @type {boolean} */
|
|
51
|
-
this.sHor = opts.sHor != undefined ? opts.sHor : true
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Draw cells as segments.
|
|
56
|
-
*
|
|
57
|
-
* @param {Array.<import("../Dataset.js").Cell>} cells
|
|
58
|
-
* @param {number} r
|
|
59
|
-
* @param {import("../GeoCanvas.js").GeoCanvas} cg
|
|
60
|
-
*/
|
|
61
|
-
draw(cells, r, cg) {
|
|
62
|
-
//filter
|
|
63
|
-
if (this.filter) cells = cells.filter(this.filter)
|
|
64
|
-
|
|
65
|
-
//zoom factor
|
|
66
|
-
const zf = cg.getZf()
|
|
67
|
-
|
|
68
|
-
let stat
|
|
69
|
-
if (this.heightCol) {
|
|
70
|
-
//if size is used, sort cells by size so that the biggest are drawn first
|
|
71
|
-
cells.sort((c1, c2) => c2[this.heightCol] - c1[this.heightCol])
|
|
72
|
-
//and compute statistics
|
|
73
|
-
stat = Style.getStatistics(cells, (c) => c[this.heightCol], true)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
//nb categories - used for radar and agepyramid
|
|
77
|
-
const cats = Object.keys(this.color)
|
|
78
|
-
|
|
79
|
-
//half resolution
|
|
80
|
-
const r2 = r / 2
|
|
81
|
-
|
|
82
|
-
//get offset
|
|
83
|
-
// @ts-ignore
|
|
84
|
-
const offset = this.offset(undefined, r, zf), dx = offset.dx, dy = offset.dy
|
|
85
|
-
|
|
86
|
-
//height
|
|
87
|
-
/** @type {function(number,number,import("../Style").Stat|undefined,number):number} */
|
|
88
|
-
let h_ = this.height
|
|
89
|
-
|
|
90
|
-
//make sides
|
|
91
|
-
/** @type {Array.<Side>} */
|
|
92
|
-
const sides = []
|
|
93
|
-
|
|
94
|
-
//make horizontal sides - except when angle%180=0
|
|
95
|
-
//sort cells by x and y
|
|
96
|
-
if (this.angle % 180 != 90 && this.sVert) {
|
|
97
|
-
cells.sort((c1, c2) => (c2.x == c1.x ? c1.y - c2.y : c1.x - c2.x))
|
|
98
|
-
let c1 = cells[0]
|
|
99
|
-
for (let i = 1; i < cells.length; i++) {
|
|
100
|
-
let c2 = cells[i]
|
|
101
|
-
|
|
102
|
-
if ((c1.y + r == c2.y) && (c1.x == c2.x))
|
|
103
|
-
//cells in same column and touch along horizontal side
|
|
104
|
-
//make shared side
|
|
105
|
-
sides.push({ x: c1.x + r2, y: c2.y, or: 'h', c1: c1, c2: c2 })
|
|
106
|
-
else {
|
|
107
|
-
//cells do not touch along horizontal side
|
|
108
|
-
//make two sides: top one for c1, bottom for c2
|
|
109
|
-
sides.push({ x: c1.x + r2, y: c1.y + r, or: 'h', c1: c1, c2: undefined })
|
|
110
|
-
sides.push({ x: c2.x + r2, y: c2.y, or: 'h', c1: undefined, c2: c2 })
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
c1 = c2
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
//make vertical sides - except when angle%180=90
|
|
118
|
-
//sort cells by y and x
|
|
119
|
-
if (this.angle % 180 != 0 && this.sHor) {
|
|
120
|
-
cells.sort((c1, c2) => (c2.y == c1.y ? c1.x - c2.x : c1.y - c2.y))
|
|
121
|
-
let c1 = cells[0]
|
|
122
|
-
for (let i = 1; i < cells.length; i++) {
|
|
123
|
-
let c2 = cells[i]
|
|
124
|
-
|
|
125
|
-
if ((c1.x + r == c2.x) && (c1.y == c2.y))
|
|
126
|
-
//cells in same row and touch along vertical side
|
|
127
|
-
//make shared side
|
|
128
|
-
sides.push({ x: c2.x, y: c1.y + r2, or: 'v', c1: c1, c2: c2 })
|
|
129
|
-
else {
|
|
130
|
-
//cells do not touch along vertical side
|
|
131
|
-
//make two sides: right one for c1, left for c2
|
|
132
|
-
sides.push({ x: c1.x + r, y: c1.y + r2, or: 'v', c1: c1, c2: undefined })
|
|
133
|
-
sides.push({ x: c2.x, y: c2.y + r2, or: 'v', c1: undefined, c2: c2 })
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
c1 = c2
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
//
|
|
141
|
-
if (sides.length == 0) return
|
|
142
|
-
|
|
143
|
-
//angle in radians
|
|
144
|
-
const aRad = this.angle * Math.PI / 180, cos = Math.cos(aRad), sin = Math.sin(aRad)
|
|
145
|
-
|
|
146
|
-
//sort sides so that the back ones are drawn first. This depends on the angle.
|
|
147
|
-
//depending on distance to the reference corner point
|
|
148
|
-
const xCorner = Math.abs(this.angle) < 90 ? cg.extGeo.xMin : cg.extGeo.xMax
|
|
149
|
-
const yCorner = this.angle < 0 ? cg.extGeo.yMax : cg.extGeo.yMin
|
|
150
|
-
sides.sort((s1, s2) => (Math.hypot(s2.x - xCorner, s2.y - yCorner) - Math.hypot(s1.x - xCorner, s1.y - yCorner)))
|
|
151
|
-
|
|
152
|
-
//prepare function to draw corner line for a cell *c*
|
|
153
|
-
const drawCornerLine = (c) => {
|
|
154
|
-
|
|
155
|
-
if (!c) return
|
|
156
|
-
//line style
|
|
157
|
-
const lw = this.cornerLineWidth ? this.cornerLineWidth(c, r, zf, this.angle) : 0.8 * zf
|
|
158
|
-
if (lw == 0) return
|
|
159
|
-
cg.ctx.strokeStyle = this.cornerLineStrokeColor ? this.cornerLineStrokeColor(c, r, zf, this.angle) : "#333"
|
|
160
|
-
cg.ctx.lineWidth = lw
|
|
161
|
-
|
|
162
|
-
//height - in geo
|
|
163
|
-
const hG = h_(c[this.heightCol], r, stat, zf)
|
|
164
|
-
|
|
165
|
-
//draw line
|
|
166
|
-
cg.ctx.beginPath()
|
|
167
|
-
cg.ctx.moveTo(c.x + r2 + dx, c.y + r2 + dy)
|
|
168
|
-
cg.ctx.lineTo(c.x + r2 + hG * cos + dx, c.y + r2 + hG * sin + dy)
|
|
169
|
-
cg.ctx.stroke()
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
//draw in geo coordinates
|
|
173
|
-
cg.setCanvasTransform()
|
|
174
|
-
|
|
175
|
-
//draw sides
|
|
176
|
-
cg.ctx.lineCap = "round";
|
|
177
|
-
for (let s of sides) {
|
|
178
|
-
|
|
179
|
-
//heights - in geo
|
|
180
|
-
const hG1 = s.c1 ? h_(s.c1[this.heightCol], r, stat, zf) : 0,
|
|
181
|
-
hG2 = s.c2 ? h_(s.c2[this.heightCol], r, stat, zf) : 0
|
|
182
|
-
|
|
183
|
-
//compute totals for both cells
|
|
184
|
-
const total1 = computeTotal(s.c1, cats),
|
|
185
|
-
total2 = computeTotal(s.c2, cats)
|
|
186
|
-
if (total1 == 0 && total2 == 0) continue
|
|
187
|
-
|
|
188
|
-
let cumul1 = 0, cumul2 = 0
|
|
189
|
-
for (let [column, color] of Object.entries(this.color)) {
|
|
190
|
-
//draw stripe of side s and category column
|
|
191
|
-
|
|
192
|
-
//get values for both cells
|
|
193
|
-
let v1 = s.c1 ? +s.c1[column] : 0
|
|
194
|
-
let v2 = s.c2 ? +s.c2[column] : 0
|
|
195
|
-
if (v1 == 0 && v2 == 0) continue
|
|
196
|
-
|
|
197
|
-
//compute heights
|
|
198
|
-
const h1 = hG1 * cumul1 / total1 || 0
|
|
199
|
-
const h1n = hG1 * (cumul1 + v1) / total1 || 0
|
|
200
|
-
const h2 = hG2 * cumul2 / total2 || 0
|
|
201
|
-
const h2n = hG2 * (cumul2 + v2) / total2 || 0
|
|
202
|
-
|
|
203
|
-
//make path
|
|
204
|
-
cg.ctx.beginPath()
|
|
205
|
-
if (s.or == "h") {
|
|
206
|
-
//horizontal side - vertical section
|
|
207
|
-
//bottom left
|
|
208
|
-
cg.ctx.moveTo(s.x + h1 * cos + dx, s.y - r2 + h1 * sin + dy)
|
|
209
|
-
//top left
|
|
210
|
-
cg.ctx.lineTo(s.x + h2 * cos + dx, s.y + r2 + h2 * sin + dy)
|
|
211
|
-
//top right
|
|
212
|
-
cg.ctx.lineTo(s.x + h2n * cos + dx, s.y + r2 + h2n * sin + dy)
|
|
213
|
-
//bottom right
|
|
214
|
-
cg.ctx.lineTo(s.x + h1n * cos + dx, s.y - r2 + h1n * sin + dy)
|
|
215
|
-
} else {
|
|
216
|
-
//vertical side - horizontal section
|
|
217
|
-
//bottom left
|
|
218
|
-
cg.ctx.moveTo(s.x - r2 + h1 * cos + dx, s.y + h1 * sin + dy)
|
|
219
|
-
//bottom right
|
|
220
|
-
cg.ctx.lineTo(s.x + r2 + h2 * cos + dx, s.y + h2 * sin + dy)
|
|
221
|
-
//top right
|
|
222
|
-
cg.ctx.lineTo(s.x + r2 + h2n * cos + dx, s.y + h2n * sin + dy)
|
|
223
|
-
//top left
|
|
224
|
-
cg.ctx.lineTo(s.x - r2 + h1n * cos + dx, s.y + h1n * sin + dy)
|
|
225
|
-
}
|
|
226
|
-
//cg.ctx.closePath()
|
|
227
|
-
|
|
228
|
-
//fill
|
|
229
|
-
cg.ctx.fillStyle = color
|
|
230
|
-
cg.ctx.fill()
|
|
231
|
-
|
|
232
|
-
cumul1 += v1
|
|
233
|
-
cumul2 += v2
|
|
234
|
-
|
|
235
|
-
//TODO draw only one line
|
|
236
|
-
//draw corner line
|
|
237
|
-
//if (s.or == "h") {
|
|
238
|
-
drawCornerLine(s.c1)
|
|
239
|
-
drawCornerLine(s.c2)
|
|
240
|
-
//if (this.angle > 0 && s.or == "h") drawCornerLine(s.c2)
|
|
241
|
-
//else drawCornerLine(s.c2)
|
|
242
|
-
//}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
//update legends
|
|
247
|
-
this.updateLegends({ style: this, r: r, zf: zf, sSize: stat })
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const computeTotal = (cell, categories) => {
|
|
254
|
-
if (!cell) return 0
|
|
255
|
-
let total = 0
|
|
256
|
-
for (let column of categories) {
|
|
257
|
-
const v = cell[column]
|
|
258
|
-
if (!v) continue
|
|
259
|
-
total += +v
|
|
260
|
-
}
|
|
261
|
-
return total || 0
|
|
262
|
-
}
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
import { Style } from '../Style.js'
|
|
5
|
+
|
|
6
|
+
/** @typedef {{x:number,y:number,or:"v"|"h",c1:import('../Dataset.js').Cell|undefined,c2:import('../Dataset.js').Cell|undefined}} Side */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @author Julien Gaffuri
|
|
10
|
+
*/
|
|
11
|
+
export class IsoFenceStyle extends Style {
|
|
12
|
+
|
|
13
|
+
/** @param {object} opts */
|
|
14
|
+
constructor(opts) {
|
|
15
|
+
super(opts)
|
|
16
|
+
opts = opts || {}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The dictionary (string -> color) which give the color of each category.
|
|
20
|
+
* @type {object} */
|
|
21
|
+
this.color = opts.color
|
|
22
|
+
|
|
23
|
+
/** The column where to get the height values.
|
|
24
|
+
* @type {string} */
|
|
25
|
+
this.heightCol = opts.heightCol
|
|
26
|
+
|
|
27
|
+
/** A function returning the height of a cell.
|
|
28
|
+
* @type {function(number,number,import("../Style").Stat|undefined,number):number} */
|
|
29
|
+
this.height = opts.height || ((v, r, s, zf) => r * 0.4)
|
|
30
|
+
|
|
31
|
+
/** The perspective angle, in degree, within [-180,180], from [O,x] axis.
|
|
32
|
+
* @type {number} */
|
|
33
|
+
this.angle = opts.angle != undefined ? opts.angle : 50
|
|
34
|
+
|
|
35
|
+
/** A function returning the corner line stroke style.
|
|
36
|
+
* @type {function(import('../Dataset.js').Cell,number,number,number):string} */
|
|
37
|
+
this.cornerLineStrokeColor = opts.cornerLineStrokeColor || ((c, r, zf, angle) => "#999")
|
|
38
|
+
|
|
39
|
+
/** A function returning the corner line width.
|
|
40
|
+
* @type {function(import('../Dataset.js').Cell,number,number,number):number} */
|
|
41
|
+
this.cornerLineWidth = opts.cornerLineWidth || ((c, r, zf, angle) => (angle % 90 == 0 ? 0 : 0.8 * zf))
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Show vertical cross-sections.
|
|
45
|
+
* @type {boolean} */
|
|
46
|
+
this.sVert = opts.sVert != undefined ? opts.sVert : true
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Show horizontal cross-sections.
|
|
50
|
+
* @type {boolean} */
|
|
51
|
+
this.sHor = opts.sHor != undefined ? opts.sHor : true
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Draw cells as segments.
|
|
56
|
+
*
|
|
57
|
+
* @param {Array.<import("../Dataset.js").Cell>} cells
|
|
58
|
+
* @param {number} r
|
|
59
|
+
* @param {import("../GeoCanvas.js").GeoCanvas} cg
|
|
60
|
+
*/
|
|
61
|
+
draw(cells, r, cg) {
|
|
62
|
+
//filter
|
|
63
|
+
if (this.filter) cells = cells.filter(this.filter)
|
|
64
|
+
|
|
65
|
+
//zoom factor
|
|
66
|
+
const zf = cg.getZf()
|
|
67
|
+
|
|
68
|
+
let stat
|
|
69
|
+
if (this.heightCol) {
|
|
70
|
+
//if size is used, sort cells by size so that the biggest are drawn first
|
|
71
|
+
cells.sort((c1, c2) => c2[this.heightCol] - c1[this.heightCol])
|
|
72
|
+
//and compute statistics
|
|
73
|
+
stat = Style.getStatistics(cells, (c) => c[this.heightCol], true)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//nb categories - used for radar and agepyramid
|
|
77
|
+
const cats = Object.keys(this.color)
|
|
78
|
+
|
|
79
|
+
//half resolution
|
|
80
|
+
const r2 = r / 2
|
|
81
|
+
|
|
82
|
+
//get offset
|
|
83
|
+
// @ts-ignore
|
|
84
|
+
const offset = this.offset(undefined, r, zf), dx = offset.dx, dy = offset.dy
|
|
85
|
+
|
|
86
|
+
//height
|
|
87
|
+
/** @type {function(number,number,import("../Style").Stat|undefined,number):number} */
|
|
88
|
+
let h_ = this.height
|
|
89
|
+
|
|
90
|
+
//make sides
|
|
91
|
+
/** @type {Array.<Side>} */
|
|
92
|
+
const sides = []
|
|
93
|
+
|
|
94
|
+
//make horizontal sides - except when angle%180=0
|
|
95
|
+
//sort cells by x and y
|
|
96
|
+
if (this.angle % 180 != 90 && this.sVert) {
|
|
97
|
+
cells.sort((c1, c2) => (c2.x == c1.x ? c1.y - c2.y : c1.x - c2.x))
|
|
98
|
+
let c1 = cells[0]
|
|
99
|
+
for (let i = 1; i < cells.length; i++) {
|
|
100
|
+
let c2 = cells[i]
|
|
101
|
+
|
|
102
|
+
if ((c1.y + r == c2.y) && (c1.x == c2.x))
|
|
103
|
+
//cells in same column and touch along horizontal side
|
|
104
|
+
//make shared side
|
|
105
|
+
sides.push({ x: c1.x + r2, y: c2.y, or: 'h', c1: c1, c2: c2 })
|
|
106
|
+
else {
|
|
107
|
+
//cells do not touch along horizontal side
|
|
108
|
+
//make two sides: top one for c1, bottom for c2
|
|
109
|
+
sides.push({ x: c1.x + r2, y: c1.y + r, or: 'h', c1: c1, c2: undefined })
|
|
110
|
+
sides.push({ x: c2.x + r2, y: c2.y, or: 'h', c1: undefined, c2: c2 })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
c1 = c2
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//make vertical sides - except when angle%180=90
|
|
118
|
+
//sort cells by y and x
|
|
119
|
+
if (this.angle % 180 != 0 && this.sHor) {
|
|
120
|
+
cells.sort((c1, c2) => (c2.y == c1.y ? c1.x - c2.x : c1.y - c2.y))
|
|
121
|
+
let c1 = cells[0]
|
|
122
|
+
for (let i = 1; i < cells.length; i++) {
|
|
123
|
+
let c2 = cells[i]
|
|
124
|
+
|
|
125
|
+
if ((c1.x + r == c2.x) && (c1.y == c2.y))
|
|
126
|
+
//cells in same row and touch along vertical side
|
|
127
|
+
//make shared side
|
|
128
|
+
sides.push({ x: c2.x, y: c1.y + r2, or: 'v', c1: c1, c2: c2 })
|
|
129
|
+
else {
|
|
130
|
+
//cells do not touch along vertical side
|
|
131
|
+
//make two sides: right one for c1, left for c2
|
|
132
|
+
sides.push({ x: c1.x + r, y: c1.y + r2, or: 'v', c1: c1, c2: undefined })
|
|
133
|
+
sides.push({ x: c2.x, y: c2.y + r2, or: 'v', c1: undefined, c2: c2 })
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
c1 = c2
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
//
|
|
141
|
+
if (sides.length == 0) return
|
|
142
|
+
|
|
143
|
+
//angle in radians
|
|
144
|
+
const aRad = this.angle * Math.PI / 180, cos = Math.cos(aRad), sin = Math.sin(aRad)
|
|
145
|
+
|
|
146
|
+
//sort sides so that the back ones are drawn first. This depends on the angle.
|
|
147
|
+
//depending on distance to the reference corner point
|
|
148
|
+
const xCorner = Math.abs(this.angle) < 90 ? cg.extGeo.xMin : cg.extGeo.xMax
|
|
149
|
+
const yCorner = this.angle < 0 ? cg.extGeo.yMax : cg.extGeo.yMin
|
|
150
|
+
sides.sort((s1, s2) => (Math.hypot(s2.x - xCorner, s2.y - yCorner) - Math.hypot(s1.x - xCorner, s1.y - yCorner)))
|
|
151
|
+
|
|
152
|
+
//prepare function to draw corner line for a cell *c*
|
|
153
|
+
const drawCornerLine = (c) => {
|
|
154
|
+
|
|
155
|
+
if (!c) return
|
|
156
|
+
//line style
|
|
157
|
+
const lw = this.cornerLineWidth ? this.cornerLineWidth(c, r, zf, this.angle) : 0.8 * zf
|
|
158
|
+
if (lw == 0) return
|
|
159
|
+
cg.ctx.strokeStyle = this.cornerLineStrokeColor ? this.cornerLineStrokeColor(c, r, zf, this.angle) : "#333"
|
|
160
|
+
cg.ctx.lineWidth = lw
|
|
161
|
+
|
|
162
|
+
//height - in geo
|
|
163
|
+
const hG = h_(c[this.heightCol], r, stat, zf)
|
|
164
|
+
|
|
165
|
+
//draw line
|
|
166
|
+
cg.ctx.beginPath()
|
|
167
|
+
cg.ctx.moveTo(c.x + r2 + dx, c.y + r2 + dy)
|
|
168
|
+
cg.ctx.lineTo(c.x + r2 + hG * cos + dx, c.y + r2 + hG * sin + dy)
|
|
169
|
+
cg.ctx.stroke()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
//draw in geo coordinates
|
|
173
|
+
cg.setCanvasTransform()
|
|
174
|
+
|
|
175
|
+
//draw sides
|
|
176
|
+
cg.ctx.lineCap = "round";
|
|
177
|
+
for (let s of sides) {
|
|
178
|
+
|
|
179
|
+
//heights - in geo
|
|
180
|
+
const hG1 = s.c1 ? h_(s.c1[this.heightCol], r, stat, zf) : 0,
|
|
181
|
+
hG2 = s.c2 ? h_(s.c2[this.heightCol], r, stat, zf) : 0
|
|
182
|
+
|
|
183
|
+
//compute totals for both cells
|
|
184
|
+
const total1 = computeTotal(s.c1, cats),
|
|
185
|
+
total2 = computeTotal(s.c2, cats)
|
|
186
|
+
if (total1 == 0 && total2 == 0) continue
|
|
187
|
+
|
|
188
|
+
let cumul1 = 0, cumul2 = 0
|
|
189
|
+
for (let [column, color] of Object.entries(this.color)) {
|
|
190
|
+
//draw stripe of side s and category column
|
|
191
|
+
|
|
192
|
+
//get values for both cells
|
|
193
|
+
let v1 = s.c1 ? +s.c1[column] : 0
|
|
194
|
+
let v2 = s.c2 ? +s.c2[column] : 0
|
|
195
|
+
if (v1 == 0 && v2 == 0) continue
|
|
196
|
+
|
|
197
|
+
//compute heights
|
|
198
|
+
const h1 = hG1 * cumul1 / total1 || 0
|
|
199
|
+
const h1n = hG1 * (cumul1 + v1) / total1 || 0
|
|
200
|
+
const h2 = hG2 * cumul2 / total2 || 0
|
|
201
|
+
const h2n = hG2 * (cumul2 + v2) / total2 || 0
|
|
202
|
+
|
|
203
|
+
//make path
|
|
204
|
+
cg.ctx.beginPath()
|
|
205
|
+
if (s.or == "h") {
|
|
206
|
+
//horizontal side - vertical section
|
|
207
|
+
//bottom left
|
|
208
|
+
cg.ctx.moveTo(s.x + h1 * cos + dx, s.y - r2 + h1 * sin + dy)
|
|
209
|
+
//top left
|
|
210
|
+
cg.ctx.lineTo(s.x + h2 * cos + dx, s.y + r2 + h2 * sin + dy)
|
|
211
|
+
//top right
|
|
212
|
+
cg.ctx.lineTo(s.x + h2n * cos + dx, s.y + r2 + h2n * sin + dy)
|
|
213
|
+
//bottom right
|
|
214
|
+
cg.ctx.lineTo(s.x + h1n * cos + dx, s.y - r2 + h1n * sin + dy)
|
|
215
|
+
} else {
|
|
216
|
+
//vertical side - horizontal section
|
|
217
|
+
//bottom left
|
|
218
|
+
cg.ctx.moveTo(s.x - r2 + h1 * cos + dx, s.y + h1 * sin + dy)
|
|
219
|
+
//bottom right
|
|
220
|
+
cg.ctx.lineTo(s.x + r2 + h2 * cos + dx, s.y + h2 * sin + dy)
|
|
221
|
+
//top right
|
|
222
|
+
cg.ctx.lineTo(s.x + r2 + h2n * cos + dx, s.y + h2n * sin + dy)
|
|
223
|
+
//top left
|
|
224
|
+
cg.ctx.lineTo(s.x - r2 + h1n * cos + dx, s.y + h1n * sin + dy)
|
|
225
|
+
}
|
|
226
|
+
//cg.ctx.closePath()
|
|
227
|
+
|
|
228
|
+
//fill
|
|
229
|
+
cg.ctx.fillStyle = color
|
|
230
|
+
cg.ctx.fill()
|
|
231
|
+
|
|
232
|
+
cumul1 += v1
|
|
233
|
+
cumul2 += v2
|
|
234
|
+
|
|
235
|
+
//TODO draw only one line
|
|
236
|
+
//draw corner line
|
|
237
|
+
//if (s.or == "h") {
|
|
238
|
+
drawCornerLine(s.c1)
|
|
239
|
+
drawCornerLine(s.c2)
|
|
240
|
+
//if (this.angle > 0 && s.or == "h") drawCornerLine(s.c2)
|
|
241
|
+
//else drawCornerLine(s.c2)
|
|
242
|
+
//}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
//update legends
|
|
247
|
+
this.updateLegends({ style: this, r: r, zf: zf, sSize: stat })
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
const computeTotal = (cell, categories) => {
|
|
254
|
+
if (!cell) return 0
|
|
255
|
+
let total = 0
|
|
256
|
+
for (let column of categories) {
|
|
257
|
+
const v = cell[column]
|
|
258
|
+
if (!v) continue
|
|
259
|
+
total += +v
|
|
260
|
+
}
|
|
261
|
+
return total || 0
|
|
262
|
+
}
|