gridviz 2.0.28 → 2.0.31

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.
Files changed (57) hide show
  1. package/dist/gridviz.js +34368 -0
  2. package/dist/gridviz.min.js +1 -1
  3. package/package.json +10 -6
  4. package/src/App.js +654 -0
  5. package/src/BackgroundLayer.js +182 -0
  6. package/src/BackgroundLayerWMS.js +131 -0
  7. package/src/Dataset.js +76 -0
  8. package/src/DatasetComponent.js +103 -0
  9. package/src/GeoCanvas.js +419 -0
  10. package/src/LabelLayer.js +190 -0
  11. package/src/Layer.js +92 -0
  12. package/src/Legend.js +86 -0
  13. package/src/LineLayer.js +150 -0
  14. package/src/Style.js +114 -0
  15. package/src/Tooltip.js +150 -0
  16. package/src/dataset/COGeo.js +24 -0
  17. package/src/dataset/CSVGrid.js +104 -0
  18. package/src/dataset/GeoTIFF.js +127 -0
  19. package/src/dataset/GridTile.js +41 -0
  20. package/src/dataset/ParquetGrid.js +122 -0
  21. package/src/dataset/TiledGrid.js +296 -0
  22. package/src/index.js +177 -0
  23. package/src/legend/ColorCategoryLegend.js +124 -0
  24. package/src/legend/ColorDiscreteLegend.js +105 -0
  25. package/src/legend/ColorLegend.js +125 -0
  26. package/src/legend/SegmentOrientationLegend.js +87 -0
  27. package/src/legend/SegmentWidthLegend.js +97 -0
  28. package/src/legend/SizeLegend.js +141 -0
  29. package/src/style/CompositionStyle.js +300 -0
  30. package/src/style/ContourStyle.js +54 -0
  31. package/src/style/DotDensityStyle.js +156 -0
  32. package/src/style/JoyPlotStyle.js +135 -0
  33. package/src/style/KernelSmoothingOldStyle.js +221 -0
  34. package/src/style/KernelSmoothingStyle.js +115 -0
  35. package/src/style/KernelSmoothingWGLStyle.js +130 -0
  36. package/src/style/LegoStyle.js +112 -0
  37. package/src/style/MosaicStyle.js +107 -0
  38. package/src/style/NinjaStarStyle.js +124 -0
  39. package/src/style/PillarStyle.js +230 -0
  40. package/src/style/SegmentStyle.js +142 -0
  41. package/src/style/ShapeColorSizeStyle.js +126 -0
  42. package/src/style/SideCatStyle.js +172 -0
  43. package/src/style/SideStyle.js +218 -0
  44. package/src/style/SquareColorCatWGLStyle.js +120 -0
  45. package/src/style/SquareColorWGLStyle.js +146 -0
  46. package/src/style/StrokeStyle.js +131 -0
  47. package/src/style/TanakaStyle.js +160 -0
  48. package/src/style/TextStyle.js +132 -0
  49. package/src/utils/Utils.js +35 -0
  50. package/src/utils/WebGLRectangleColoring.js +106 -0
  51. package/src/utils/WebGLSquareColoring.js +118 -0
  52. package/src/utils/WebGLSquareColoringAdvanced.js +230 -0
  53. package/src/utils/WebGLSquareColoringCatAdvanced.js +133 -0
  54. package/src/utils/WebGLSquareColoringKS.js +153 -0
  55. package/src/utils/stretching.js +131 -0
  56. package/src/utils/webGLUtils.js +71 -0
  57. package/src/utils/zoomUtils.js +142 -0
@@ -0,0 +1,142 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { Style } from '../Style.js'
5
+
6
+ /**
7
+ * A style where each cell is represented by a segment whose length, width, color and orientation can vary according to statistical values.
8
+ *
9
+ * @author Julien Gaffuri
10
+ */
11
+ export class SegmentStyle extends Style {
12
+ /** @param {object} opts */
13
+ constructor(opts) {
14
+ super(opts)
15
+ opts = opts || {}
16
+
17
+ /** A function returning the orientation (in degrees) of the segment representing a cell.
18
+ * @type {function(import("../Dataset").Cell):number} */
19
+ this.orientation = opts.orientation || (() => 0)
20
+
21
+ /**
22
+ * @type {string} */
23
+ this.colorCol = opts.colorCol
24
+
25
+ /** A function returning the color of the cell segment.
26
+ * @type {function(number,number,import("../Style").Stat|undefined):string} */
27
+ this.color = opts.color || (() => '#EA6BAC')
28
+
29
+ /**
30
+ * @type {string} */
31
+ this.lengthCol = opts.lengthCol
32
+
33
+ /** A function returning the length of the segment representing a cell, in geo unit
34
+ * @type {function(number,number,import("../Style").Stat|undefined,number):number} */
35
+ this.length = opts.length
36
+
37
+ /**
38
+ * @type {string} */
39
+ this.widthCol = opts.widthCol
40
+
41
+ /** A function returning the width of the segment representing a cell, in geo unit
42
+ * @type {function(number,number,import("../Style").Stat|undefined,number):number} */
43
+ this.width = opts.width
44
+ }
45
+
46
+ /**
47
+ * Draw cells as segments.
48
+ *
49
+ * @param {Array.<import("../Dataset").Cell>} cells
50
+ * @param {number} r
51
+ * @param {import("../GeoCanvas").GeoCanvas} cg
52
+ */
53
+ draw(cells, r, cg) {
54
+ //filter
55
+ if (this.filter) cells = cells.filter(this.filter)
56
+
57
+ //zoom factor
58
+ const zf = cg.getZf()
59
+
60
+ let statColor
61
+ if (this.colorCol) {
62
+ //compute color variable statistics
63
+ statColor = Style.getStatistics(cells, (c) => c[this.colorCol], true)
64
+ }
65
+
66
+ let statLength
67
+ if (this.lengthCol) {
68
+ //if length is used, sort cells by length so that the longests are drawn first
69
+ cells.sort((c1, c2) => c2[this.lengthCol] - c1[this.lengthCol])
70
+ //and compute size variable statistics
71
+ statLength = Style.getStatistics(cells, (c) => c[this.lengthCol], true)
72
+ }
73
+
74
+ let statWidth
75
+ if (this.widthCol) {
76
+ //and compute size variable statistics
77
+ statWidth = Style.getStatistics(cells, (c) => c[this.widthCol], true)
78
+ }
79
+
80
+ //
81
+ cg.ctx.lineCap = 'butt'
82
+
83
+ //conversion factor degree -> radian
84
+ const f = Math.PI / 180
85
+
86
+ //draw in geo coordinates
87
+ cg.setCanvasTransform()
88
+
89
+ for (let c of cells) {
90
+ //color
91
+ /** @type {string|undefined} */
92
+ const col = this.color ? this.color(c[this.colorCol], r, statColor) : undefined
93
+ if (!col) continue
94
+
95
+ //width
96
+ /** @type {number|undefined} */
97
+ const wG = this.width ? this.width(c[this.widthCol], r, statWidth, zf) : undefined
98
+ if (!wG || wG < 0) continue
99
+
100
+ //length
101
+ /** @type {number|undefined} */
102
+ const lG = this.length ? this.length(c[this.lengthCol], r, statLength, zf) : undefined
103
+ if (!lG || lG < 0) continue
104
+
105
+ //orientation (in radian)
106
+ /** @type {number} */
107
+ const or = this.orientation(c) * f
108
+ if (or === undefined || isNaN(or)) continue
109
+
110
+ //get offset
111
+ const offset = this.offset(c, r, zf)
112
+
113
+ //set color and width
114
+ cg.ctx.strokeStyle = col
115
+ cg.ctx.lineWidth = wG
116
+
117
+ //compute segment centre postition
118
+ const cx = c.x + r / 2 + offset.dx
119
+ const cy = c.y + r / 2 + offset.dy
120
+
121
+ //compute segment direction
122
+ const dx = 0.5 * Math.cos(or) * lG
123
+ const dy = 0.5 * Math.sin(or) * lG
124
+
125
+ //draw segment
126
+ cg.ctx.beginPath()
127
+ cg.ctx.moveTo(cx - dx, cy - dy)
128
+ cg.ctx.lineTo(cx + dx, cy + dy)
129
+ cg.ctx.stroke()
130
+ }
131
+
132
+ //update legend, if any
133
+ this.updateLegends({
134
+ style: this,
135
+ r: r,
136
+ zf: zf,
137
+ sColor: statColor,
138
+ sLength: statLength,
139
+ sWidth: statWidth,
140
+ })
141
+ }
142
+ }
@@ -0,0 +1,126 @@
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
+ /** The name of the column/attribute of the tabular data where to retrieve the variable for color.
19
+ * @type {string} */
20
+ this.colorCol = opts.colorCol
21
+
22
+ /** A function returning the color of the cell.
23
+ * @type {function(number,number,import("../Style").Stat|undefined,number):string} */
24
+ this.color = opts.color || (() => '#EA6BAC') //(v,r,s,zf) => {}
25
+
26
+ /** The name of the column/attribute of the tabular data where to retrieve the variable for size.
27
+ * @type {string} */
28
+ this.sizeCol = opts.sizeCol
29
+
30
+ /** A function returning the size of a cell in geographical unit.
31
+ * @type {function(number,number,import("../Style").Stat|undefined,number):number} */
32
+ this.size = opts.size
33
+
34
+ /** A function returning the shape of a cell.
35
+ * @type {function(import("../Dataset").Cell):import("../Style").Shape} */
36
+ this.shape = opts.shape || (() => 'square')
37
+ }
38
+
39
+ /**
40
+ * Draw cells as squares, with various colors and size.
41
+ *
42
+ * @param {Array.<import("../Dataset").Cell>} cells
43
+ * @param {number} r
44
+ * @param {import("../GeoCanvas").GeoCanvas} cg
45
+ */
46
+ draw(cells, r, cg) {
47
+ //filter
48
+ if (this.filter) cells = cells.filter(this.filter)
49
+
50
+ //zoom factor
51
+ const zf = cg.getZf()
52
+
53
+ let statSize
54
+ if (this.sizeCol) {
55
+ //if size is used, sort cells by size so that the biggest are drawn first
56
+ cells.sort((c1, c2) => c2[this.sizeCol] - c1[this.sizeCol])
57
+ //and compute size variable statistics
58
+ statSize = Style.getStatistics(cells, (c) => c[this.sizeCol], true)
59
+ }
60
+
61
+ let statColor
62
+ if (this.colorCol) {
63
+ //compute color variable statistics
64
+ statColor = Style.getStatistics(cells, (c) => c[this.colorCol], true)
65
+ }
66
+
67
+ //draw with HTML canvas
68
+ //in geo coordinates
69
+ cg.setCanvasTransform()
70
+
71
+ const r2 = r * 0.5
72
+ for (let cell of cells) {
73
+ //color
74
+ const col = this.color ? this.color(cell[this.colorCol], r, statColor, zf) : undefined
75
+ if (!col || col === 'none') continue
76
+ cg.ctx.fillStyle = col
77
+
78
+ //shape
79
+ const shape = this.shape ? this.shape(cell) : 'square'
80
+ if (shape === 'none') continue
81
+
82
+ //size
83
+ /** @type {function(number,number,import("../Style").Stat|undefined,number):number} */
84
+ let s_ = this.size || (() => r)
85
+ //size - in geo unit
86
+ const sG = s_(cell[this.sizeCol], r, statSize, zf)
87
+
88
+ //get offset
89
+ const offset = this.offset(cell, r, zf)
90
+
91
+ if (shape === 'square') {
92
+ //draw square
93
+ const d = r * (1 - sG / r) * 0.5
94
+ cg.ctx.fillRect(cell.x + d + offset.dx, cell.y + d + offset.dy, sG, sG)
95
+ } else if (shape === 'circle') {
96
+ //draw circle
97
+ cg.ctx.beginPath()
98
+ cg.ctx.arc(cell.x + r2 + offset.dx, cell.y + r2 + offset.dy, sG * 0.5, 0, 2 * Math.PI, false)
99
+ cg.ctx.fill()
100
+ } else if (shape === 'donut') {
101
+ //draw donut
102
+ const xc = cell.x + r2 + offset.dx,
103
+ yc = cell.y + r2 + offset.dy
104
+ cg.ctx.beginPath()
105
+ cg.ctx.moveTo(xc, yc)
106
+ cg.ctx.arc(xc, yc, r2, 0, 2 * Math.PI)
107
+ cg.ctx.arc(xc, yc, (1 - sG / r) * r2, 0, 2 * Math.PI, true)
108
+ cg.ctx.closePath()
109
+ cg.ctx.fill()
110
+ } else if (shape === 'diamond') {
111
+ const s2 = sG * 0.5
112
+ cg.ctx.beginPath()
113
+ cg.ctx.moveTo(cell.x + r2 - s2, cell.y + r2)
114
+ cg.ctx.lineTo(cell.x + r2, cell.y + r2 + s2)
115
+ cg.ctx.lineTo(cell.x + r2 + s2, cell.y + r2)
116
+ cg.ctx.lineTo(cell.x + r2, cell.y + r2 - s2)
117
+ cg.ctx.fill()
118
+ } else {
119
+ throw new Error('Unexpected shape:' + shape)
120
+ }
121
+ }
122
+
123
+ //update legends
124
+ this.updateLegends({ style: this, r: r, zf: zf, sSize: statSize, sColor: statColor })
125
+ }
126
+ }
@@ -0,0 +1,172 @@
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
+ }
@@ -0,0 +1,218 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { Style } from '../Style.js'
5
+
6
+ /** @typedef {{x:number,y:number,or:"v"|"h",value:number}} Side */
7
+
8
+ /**
9
+ *
10
+ * @author Julien Gaffuri
11
+ */
12
+ export class SideStyle extends Style {
13
+ /** @param {object} opts */
14
+ constructor(opts) {
15
+ super(opts)
16
+ opts = opts || {}
17
+
18
+ /** The name of the column/attribute of the tabular data where to retrieve the variable for the cell values.
19
+ * @type {string} */
20
+ this.valueCol = opts.valueCol
21
+
22
+ /** A function returning the value of a cell side. This value is computed from the two adjacent cell values.
23
+ * For horizontal sides, v1 is the value of the cell below and v2 the value of the cell above.
24
+ * For vertical sides, v1 is the value of the cell left and v2 the value of the cell right.
25
+ * @type {function(number|undefined,number|undefined,number,import("../Style").Stat|undefined,number):number} */
26
+ this.value = opts.value || ((v1, v2, r, s, zf) => 1)
27
+
28
+ /** A function returning the color of a cell side.
29
+ * @type {function(Side,number,import("../Style").Stat|undefined,number):string} */
30
+ this.color = opts.color || (() => '#EA6BAC')
31
+
32
+ /** A function returning the width of a cell side, in geo unit
33
+ * @type {function(Side,number,import("../Style").Stat|undefined,number):number} */
34
+ this.width = opts.width || ((side, r, s, zf) => (r * side.value) / 5)
35
+
36
+ /** orientation. Set to 90 to show sides as slope lines for example.
37
+ * @type {number} */
38
+ this.orientation = opts.orientation || 0
39
+
40
+ /** A fill color for the cells.
41
+ * @type {function(import("../Dataset").Cell):string} */
42
+ this.fillColor = opts.fillColor
43
+ }
44
+
45
+ /**
46
+ * @param {Array.<import("../Dataset").Cell>} cells
47
+ * @param {number} r
48
+ * @param {import("../GeoCanvas").GeoCanvas} cg
49
+ */
50
+ draw(cells, r, cg) {
51
+ //filter
52
+ if (this.filter) cells = cells.filter(this.filter)
53
+
54
+ //zoom factor
55
+ const zf = cg.getZf()
56
+
57
+ //compute stats on cell values
58
+ let statValue
59
+ if (this.valueCol) {
60
+ //compute color variable statistics
61
+ statValue = Style.getStatistics(cells, (c) => c[this.valueCol], true)
62
+ }
63
+
64
+ /** @type {Array.<Side>} */
65
+ const sides = []
66
+
67
+ const epsilon = r * 0.001
68
+
69
+ //make horizontal sides
70
+ //sort cells by x and y
71
+ cells.sort((c1, c2) => (c2.x == c1.x ? c1.y - c2.y : c1.x - c2.x))
72
+ let c1 = cells[0]
73
+ for (let i = 1; i < cells.length; i++) {
74
+ let c2 = cells[i]
75
+
76
+ if (Math.abs(c1.y + r - c2.y) <= epsilon && Math.abs(c1.x - c2.x) <= epsilon)
77
+ //cells in same column and touch along horizontal side
78
+ //make shared side
79
+ sides.push({
80
+ x: c1.x,
81
+ y: c2.y,
82
+ or: 'h',
83
+ value: this.value(c1[this.valueCol], c2[this.valueCol], r, statValue, zf),
84
+ })
85
+ else {
86
+ //cells do not touch along horizontal side
87
+ //make two sides: top one for c1, bottom for c2
88
+ sides.push({
89
+ x: c1.x,
90
+ y: c1.y + r,
91
+ or: 'h',
92
+ value: this.value(c1[this.valueCol], undefined, r, statValue, zf),
93
+ })
94
+ sides.push({
95
+ x: c2.x,
96
+ y: c2.y,
97
+ or: 'h',
98
+ value: this.value(undefined, c2[this.valueCol], r, statValue, zf),
99
+ })
100
+ }
101
+
102
+ c1 = c2
103
+ }
104
+
105
+ //make vertical sides
106
+ //sort cells by y and x
107
+ cells.sort((c1, c2) => (c2.y == c1.y ? c1.x - c2.x : c1.y - c2.y))
108
+ c1 = cells[0]
109
+ for (let i = 1; i < cells.length; i++) {
110
+ let c2 = cells[i]
111
+
112
+ if (Math.abs(c1.x + r - c2.x) <= epsilon && Math.abs(c1.y - c2.y) <= epsilon)
113
+ //cells in same row and touch along vertical side
114
+ //make shared side
115
+ sides.push({
116
+ x: c1.x + r,
117
+ y: c1.y,
118
+ or: 'v',
119
+ value: this.value(c1[this.valueCol], c2[this.valueCol], r, statValue, zf),
120
+ })
121
+ else {
122
+ //cells do not touch along vertical side
123
+ //make two sides: right one for c1, left for c2
124
+ sides.push({
125
+ x: c1.x + r,
126
+ y: c1.y,
127
+ or: 'v',
128
+ value: this.value(c1[this.valueCol], undefined, r, statValue, zf),
129
+ })
130
+ sides.push({
131
+ x: c2.x,
132
+ y: c2.y,
133
+ or: 'v',
134
+ value: this.value(undefined, c2[this.valueCol], r, statValue, zf),
135
+ })
136
+ }
137
+
138
+ c1 = c2
139
+ }
140
+
141
+ //
142
+ if (sides.length == 0) return
143
+
144
+ //compute stats on sides
145
+ const statSides = SideStyle.getSideStatistics(sides, true)
146
+
147
+ //draw in geo coordinates
148
+ cg.setCanvasTransform()
149
+
150
+ //draw cells, if fillColor specified
151
+ if (this.fillColor)
152
+ for (let c of cells) {
153
+ const fc = this.fillColor(c)
154
+ if (!fc || fc == 'none') continue
155
+ cg.ctx.fillStyle = fc
156
+ cg.ctx.fillRect(c.x, c.y, r, r)
157
+ }
158
+
159
+ //draw sides
160
+ cg.ctx.lineCap = 'butt'
161
+ const r2 = r / 2
162
+ for (let s of sides) {
163
+ //color
164
+ /** @type {string|undefined} */
165
+ const col = this.color ? this.color(s, r, statSides, zf) : undefined
166
+ if (!col || col == 'none') continue
167
+
168
+ //width
169
+ /** @type {number|undefined} */
170
+ const wG = this.width ? this.width(s, r, statSides, zf) : undefined
171
+ if (!wG || wG <= 0) continue
172
+
173
+ //set color and width
174
+ cg.ctx.strokeStyle = col
175
+ cg.ctx.lineWidth = wG
176
+
177
+ //draw segment with correct orientation
178
+ cg.ctx.beginPath()
179
+ if (this.orientation == 90) {
180
+ cg.ctx.moveTo(s.x + r2, s.y + r2)
181
+ if (s.or === 'h') cg.ctx.lineTo(s.x + r2, s.y - r2)
182
+ else cg.ctx.lineTo(s.x - r2, s.y + r2)
183
+ } else {
184
+ cg.ctx.moveTo(s.x, s.y)
185
+ cg.ctx.lineTo(s.x + (s.or === 'h' ? r : 0), s.y + (s.or === 'v' ? r : 0))
186
+ }
187
+ cg.ctx.stroke()
188
+ }
189
+
190
+ //update legends
191
+ this.updateLegends({ style: this, r: r, zf: zf })
192
+ }
193
+
194
+ /**
195
+ * Compute some statistics on a value of some sides.
196
+ * This is used to define how to draw specifically the sides within the view.
197
+ *
198
+ * @param {Array.<Side>} sides
199
+ * @param {boolean} ignoreZeros
200
+ * @returns {import("../Style").Stat | undefined}
201
+ */
202
+ static getSideStatistics(sides, ignoreZeros) {
203
+ if (!sides || sides.length == 0) return undefined
204
+ let min = Infinity
205
+ let max = -Infinity
206
+ //let sum = 0
207
+ //let nb = 0
208
+ for (const s of sides) {
209
+ const v = s.value
210
+ if (ignoreZeros && !v) continue
211
+ if (v < min) min = v
212
+ if (v > max) max = v
213
+ //sum += v
214
+ //nb++
215
+ }
216
+ return { min: min, max: max }
217
+ }
218
+ }