gridviz 2.0.28 → 2.0.29
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/dist/gridviz.js +34315 -0
- package/package.json +3 -2
- package/src/App.js +651 -0
- package/src/BackgroundLayer.js +182 -0
- package/src/BackgroundLayerWMS.js +131 -0
- package/src/Dataset.js +76 -0
- package/src/DatasetComponent.js +103 -0
- package/src/GeoCanvas.js +419 -0
- package/src/LabelLayer.js +190 -0
- package/src/Layer.js +92 -0
- package/src/Legend.js +86 -0
- package/src/LineLayer.js +150 -0
- package/src/Style.js +114 -0
- package/src/Tooltip.js +150 -0
- package/src/dataset/COGeo.js +24 -0
- package/src/dataset/CSVGrid.js +105 -0
- package/src/dataset/GeoTIFF.js +127 -0
- package/src/dataset/GridTile.js +41 -0
- package/src/dataset/ParquetGrid.js +123 -0
- package/src/dataset/TiledGrid.js +294 -0
- package/src/index.js +176 -0
- package/src/legend/ColorCategoryLegend.js +124 -0
- package/src/legend/ColorDiscreteLegend.js +105 -0
- package/src/legend/ColorLegend.js +125 -0
- package/src/legend/SegmentOrientationLegend.js +87 -0
- package/src/legend/SegmentWidthLegend.js +97 -0
- package/src/legend/SizeLegend.js +141 -0
- package/src/style/CompositionStyle.js +294 -0
- package/src/style/ContourStyle.js +54 -0
- package/src/style/DotDensityStyle.js +156 -0
- package/src/style/JoyPlotStyle.js +135 -0
- package/src/style/KernelSmoothingOldStyle.js +221 -0
- package/src/style/KernelSmoothingStyle.js +115 -0
- package/src/style/KernelSmoothingWGLStyle.js +130 -0
- package/src/style/LegoStyle.js +112 -0
- package/src/style/MosaicStyle.js +107 -0
- package/src/style/NinjaStarStyle.js +124 -0
- package/src/style/PillarStyle.js +230 -0
- package/src/style/SegmentStyle.js +142 -0
- package/src/style/ShapeColorSizeStyle.js +126 -0
- package/src/style/SideCatStyle.js +172 -0
- package/src/style/SideStyle.js +218 -0
- package/src/style/SquareColorCatWGLStyle.js +120 -0
- package/src/style/SquareColorWGLStyle.js +146 -0
- package/src/style/StrokeStyle.js +131 -0
- package/src/style/TanakaStyle.js +160 -0
- package/src/style/TextStyle.js +132 -0
- package/src/utils/Utils.js +35 -0
- package/src/utils/WebGLRectangleColoring.js +106 -0
- package/src/utils/WebGLSquareColoring.js +118 -0
- package/src/utils/WebGLSquareColoringAdvanced.js +230 -0
- package/src/utils/WebGLSquareColoringCatAdvanced.js +133 -0
- package/src/utils/WebGLSquareColoringKS.js +153 -0
- package/src/utils/stretching.js +131 -0
- package/src/utils/webGLUtils.js +71 -0
- package/src/utils/zoomUtils.js +142 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
import { Style } from '../Style'
|
|
5
|
+
import { makeWebGLCanvas } from '../utils/webGLUtils'
|
|
6
|
+
import { WebGLSquareColoringKS } from '../utils/WebGLSquareColoringKS'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @author Julien Gaffuri
|
|
10
|
+
*/
|
|
11
|
+
export class KernelSmoothingStyle extends Style {
|
|
12
|
+
//see
|
|
13
|
+
//https://stackoverflow.com/questions/8099979/creating-a-glsl-arrays-of-uniforms
|
|
14
|
+
//see https://gist.github.com/jasonkit/c5b4fd62e8cbfe2780cc
|
|
15
|
+
|
|
16
|
+
/** @param {object} opts */
|
|
17
|
+
constructor(opts) {
|
|
18
|
+
super(opts)
|
|
19
|
+
opts = opts || {}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The name of the column/attribute of the tabular data where to retrieve the variable for color.
|
|
23
|
+
* @type {string} */
|
|
24
|
+
this.colorCol = opts.colorCol
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A function returning the t value (within [0,1]) of the cell.
|
|
28
|
+
* @type {function(number,number,import("../Style").Stat):number} */
|
|
29
|
+
this.tFun = opts.tFun || ((v, r, s) => v / s.max)
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Distribution stretching method.
|
|
33
|
+
* The stretching is performed on GPU side (fragment shader).
|
|
34
|
+
* @type {{ fun:string, alpha:number }} */
|
|
35
|
+
this.stretching = opts.stretching
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The sample of the color ramp.
|
|
39
|
+
* The color is computed on GPU side (fragment shader) based on those values (linear interpolation).
|
|
40
|
+
* @type {Array.<string>} */
|
|
41
|
+
this.colors =
|
|
42
|
+
opts.colors ||
|
|
43
|
+
[
|
|
44
|
+
'rgb(158, 1, 66)',
|
|
45
|
+
'rgb(248, 142, 83)',
|
|
46
|
+
'rgb(251, 248, 176)',
|
|
47
|
+
'rgb(137, 207, 165)',
|
|
48
|
+
'rgb(94, 79, 162)',
|
|
49
|
+
].reverse()
|
|
50
|
+
if (opts.color)
|
|
51
|
+
this.colors = [
|
|
52
|
+
opts.color(0),
|
|
53
|
+
opts.color(0.2),
|
|
54
|
+
opts.color(0.4),
|
|
55
|
+
opts.color(0.6),
|
|
56
|
+
opts.color(0.8),
|
|
57
|
+
opts.color(1),
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A function returning the size of the cells, in geographical unit. All cells have the same size.
|
|
62
|
+
* @type {function(number,number):number} */
|
|
63
|
+
this.size = opts.size // (resolution, zf) => ...
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {Array.<import("../Dataset").Cell>} cells
|
|
68
|
+
* @param {number} r
|
|
69
|
+
* @param {import("../GeoCanvas").GeoCanvas} cg
|
|
70
|
+
*/
|
|
71
|
+
draw(cells, r, cg) {
|
|
72
|
+
//filter
|
|
73
|
+
if (this.filter) cells = cells.filter(this.filter)
|
|
74
|
+
|
|
75
|
+
//zoom factor
|
|
76
|
+
const zf = cg.getZf()
|
|
77
|
+
|
|
78
|
+
//compute color variable statistics
|
|
79
|
+
const statColor = Style.getStatistics(cells, (c) => c[this.colorCol], true)
|
|
80
|
+
|
|
81
|
+
if (!statColor) return
|
|
82
|
+
|
|
83
|
+
//create canvas and webgl renderer
|
|
84
|
+
const cvWGL = makeWebGLCanvas(cg.w + '', cg.h + '')
|
|
85
|
+
if (!cvWGL) {
|
|
86
|
+
console.error('No webGL')
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//add vertice and fragment data
|
|
91
|
+
const r2 = r / 2
|
|
92
|
+
let c,
|
|
93
|
+
nb = cells.length
|
|
94
|
+
const verticesBuffer = []
|
|
95
|
+
//const tBuffer = []
|
|
96
|
+
for (let i = 0; i < nb; i++) {
|
|
97
|
+
c = cells[i]
|
|
98
|
+
const t = this.tFun(c[this.colorCol], r, statColor)
|
|
99
|
+
if (t == null || t == undefined) continue
|
|
100
|
+
verticesBuffer.push(c.x + r2, c.y + r2)
|
|
101
|
+
//tBuffer.push(t > 1 ? 1 : t < 0 ? 0 : t)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const sizeGeo = this.size ? this.size(r, zf) : r + 0.2 * zf
|
|
105
|
+
const wgp = new WebGLSquareColoringKS(cvWGL.gl, this.colors, this.stretching, sizeGeo / zf)
|
|
106
|
+
|
|
107
|
+
//TODO - [i,j,t]
|
|
108
|
+
let data = []
|
|
109
|
+
for (const c of cells) {
|
|
110
|
+
const xGL = cg.geoToPixX(c.x + r2) //TODO within [-1,1] ?
|
|
111
|
+
data.push(xGL)
|
|
112
|
+
const yGL = cg.geoToPixY(c.y + r2) //TODO within [-1,1] ?
|
|
113
|
+
data.push(yGL)
|
|
114
|
+
const t = this.tFun(c[this.colorCol], r, statColor)
|
|
115
|
+
data.push(t)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
data = [0.0, 2.0, 1.0, 0.0, 0.0, 0.0]
|
|
119
|
+
|
|
120
|
+
//draw
|
|
121
|
+
wgp.draw(verticesBuffer, data, cg.getWebGLTransform())
|
|
122
|
+
|
|
123
|
+
//draw in canvas geo
|
|
124
|
+
cg.initCanvasTransform()
|
|
125
|
+
cg.ctx.drawImage(cvWGL.canvas, 0, 0)
|
|
126
|
+
|
|
127
|
+
//update legends
|
|
128
|
+
this.updateLegends({ style: this, r: r, zf: zf, sColor: statColor })
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
import { TanakaStyle } from './TanakaStyle'
|
|
5
|
+
import { StrokeStyle } from './StrokeStyle'
|
|
6
|
+
import { SquareColorCatWGLStyle } from './SquareColorCatWGLStyle'
|
|
7
|
+
import { Style } from '../Style'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @author Julien Gaffuri
|
|
11
|
+
*/
|
|
12
|
+
export class LegoStyle {
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} col
|
|
15
|
+
* @param {object} opts
|
|
16
|
+
* @returns {Array.<Style>}
|
|
17
|
+
*/
|
|
18
|
+
static get(col, opts) {
|
|
19
|
+
opts = opts || {}
|
|
20
|
+
|
|
21
|
+
//the colors
|
|
22
|
+
//http://www.jennyscrayoncollection.com/2021/06/all-current-lego-colors.html
|
|
23
|
+
//https://leonawicz.github.io/legocolors/reference/figures/README-plot-1.png
|
|
24
|
+
opts.colors = opts.colors || [
|
|
25
|
+
'#00852b', //darker green
|
|
26
|
+
'#afd246', //light green
|
|
27
|
+
'#fac80a', //dark yellow
|
|
28
|
+
'#bb805a', //brown
|
|
29
|
+
'#d67923', //mostard
|
|
30
|
+
'#cb4e29', //redish
|
|
31
|
+
'#b40000', //red
|
|
32
|
+
'#720012', //dark red
|
|
33
|
+
//"purple",
|
|
34
|
+
//"#eee" //whithe
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
opts.colDark = opts.colDark || '#333'
|
|
38
|
+
opts.colBright = opts.colBright || '#aaa'
|
|
39
|
+
opts.widthFactor = opts.widthFactor || 0.12
|
|
40
|
+
|
|
41
|
+
//reuse tanaka as basis
|
|
42
|
+
const ts = TanakaStyle.get(col, opts)
|
|
43
|
+
//style to show limits between pieces
|
|
44
|
+
const sst = new StrokeStyle({
|
|
45
|
+
strokeColor: () => '#666',
|
|
46
|
+
strokeWidth: (v, r, s, z) => 0.2 * z,
|
|
47
|
+
filter: opts.filter,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return [
|
|
51
|
+
ts[0],
|
|
52
|
+
sst,
|
|
53
|
+
ts[1],
|
|
54
|
+
new LegoTopStyle({ colDark: opts.colDark, colBright: opts.colBright, filter: opts.filter }),
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {function(string):string} col
|
|
60
|
+
* @param {object} opts
|
|
61
|
+
* @returns {Array.<Style>}
|
|
62
|
+
*/
|
|
63
|
+
static getCat(col, opts) {
|
|
64
|
+
opts = opts || {}
|
|
65
|
+
|
|
66
|
+
opts.colDark = opts.colDark || '#333'
|
|
67
|
+
opts.colBright = opts.colBright || '#aaa'
|
|
68
|
+
|
|
69
|
+
//
|
|
70
|
+
const s = new SquareColorCatWGLStyle({ colorCol: col, color: opts.color })
|
|
71
|
+
//style to show limits between pieces
|
|
72
|
+
const sst = new StrokeStyle({ strokeColor: () => '#666', strokeWidth: (v, r, s, z) => 0.2 * z })
|
|
73
|
+
|
|
74
|
+
return [s, sst, new LegoTopStyle({ colDark: opts.colDark, colBright: opts.colBright })]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* A style to draw top circle of lego bricks.
|
|
80
|
+
*/
|
|
81
|
+
class LegoTopStyle extends Style {
|
|
82
|
+
/** @param {object|undefined} opts */
|
|
83
|
+
constructor(opts) {
|
|
84
|
+
super(opts)
|
|
85
|
+
opts = opts || {}
|
|
86
|
+
this.colDark = opts.colDark || '#333'
|
|
87
|
+
this.colBright = opts.colBright || '#aaa'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
draw(cells, r, cg) {
|
|
91
|
+
//filter
|
|
92
|
+
if (this.filter) cells = cells.filter(this.filter)
|
|
93
|
+
|
|
94
|
+
cg.ctx.lineWidth = 0.6 * cg.getZf()
|
|
95
|
+
|
|
96
|
+
//dark part
|
|
97
|
+
cg.ctx.strokeStyle = this.colDark
|
|
98
|
+
for (let c of cells) {
|
|
99
|
+
cg.ctx.beginPath()
|
|
100
|
+
cg.ctx.arc(c.x + r * 0.5, c.y + r * 0.5, r * 0.55 * 0.5, Math.PI / 4, -Math.PI * (3 / 4), true)
|
|
101
|
+
cg.ctx.stroke()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
//bright part
|
|
105
|
+
cg.ctx.strokeStyle = this.colBright
|
|
106
|
+
for (let c of cells) {
|
|
107
|
+
cg.ctx.beginPath()
|
|
108
|
+
cg.ctx.arc(c.x + r * 0.5, c.y + r * 0.5, r * 0.55 * 0.5, Math.PI / 4, -Math.PI * (3 / 4), false)
|
|
109
|
+
cg.ctx.stroke()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
import { Style } from '../Style'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @author Julien Gaffuri
|
|
8
|
+
*/
|
|
9
|
+
export class MosaicStyle extends Style {
|
|
10
|
+
/** @param {object} opts */
|
|
11
|
+
constructor(opts) {
|
|
12
|
+
super(opts)
|
|
13
|
+
opts = opts || {}
|
|
14
|
+
|
|
15
|
+
/** The name of the column/attribute of the tabular data where to retrieve the variable for color.
|
|
16
|
+
* @type {string} */
|
|
17
|
+
this.colorCol = opts.colorCol
|
|
18
|
+
|
|
19
|
+
/** A function returning the color of the cell.
|
|
20
|
+
* @type {function(number,number,import("../Style").Stat|undefined):string} */
|
|
21
|
+
this.color = opts.color || (() => '#EA6BAC')
|
|
22
|
+
|
|
23
|
+
/** The mosaic factor, within [0,0.5]. Set to 0 for no mosaic effect. Set to 0.5 for strong mosaic effect.
|
|
24
|
+
* @type {number} */
|
|
25
|
+
this.mosaicFactor = opts.mosaicFactor || 0.15
|
|
26
|
+
|
|
27
|
+
/** The mosaic shadow factor, within [0,0.5]. Set to 0 for no mosaic shadow. Set to 0.5 for strong mosaic shadow.
|
|
28
|
+
* @type {number} */
|
|
29
|
+
this.shadowFactor = opts.shadowFactor || 0.2
|
|
30
|
+
|
|
31
|
+
/** The mosaic shadow color.
|
|
32
|
+
* @type {string} */
|
|
33
|
+
this.shadowColor = opts.shadowColor || '#555'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
*
|
|
38
|
+
* @param {Array.<import("../Dataset").Cell>} cells
|
|
39
|
+
* @param {number} resolution
|
|
40
|
+
* @param {import("../GeoCanvas").GeoCanvas} cg
|
|
41
|
+
*/
|
|
42
|
+
draw(cells, resolution, cg) {
|
|
43
|
+
//filter
|
|
44
|
+
if (this.filter) cells = cells.filter(this.filter)
|
|
45
|
+
|
|
46
|
+
//zoom factor
|
|
47
|
+
const zf = cg.getZf()
|
|
48
|
+
|
|
49
|
+
let statColor
|
|
50
|
+
if (this.colorCol) {
|
|
51
|
+
//compute color variable statistics
|
|
52
|
+
statColor = Style.getStatistics(cells, (c) => c[this.colorCol], true)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//set stroke style, for shadow
|
|
56
|
+
cg.ctx.strokeStyle = this.shadowColor
|
|
57
|
+
cg.ctx.lineWidth = this.shadowFactor * resolution
|
|
58
|
+
cg.ctx.lineJoin = 'round'
|
|
59
|
+
cg.ctx.lineCap = 'butt'
|
|
60
|
+
|
|
61
|
+
//function to compute position mosaic effect
|
|
62
|
+
const d = resolution * this.mosaicFactor
|
|
63
|
+
const mosaic = () => {
|
|
64
|
+
return { x: Math.random() * d, y: Math.random() * d }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//draw with HTML canvas in geo coordinates
|
|
68
|
+
cg.setCanvasTransform()
|
|
69
|
+
|
|
70
|
+
for (let cell of cells) {
|
|
71
|
+
//set fill color
|
|
72
|
+
const col = this.color ? this.color(cell[this.colorCol], resolution, statColor) : undefined
|
|
73
|
+
if (!col || col === 'none') continue
|
|
74
|
+
cg.ctx.fillStyle = col
|
|
75
|
+
|
|
76
|
+
//get offset
|
|
77
|
+
const offset = this.offset(cell, resolution, zf)
|
|
78
|
+
|
|
79
|
+
//compute position mosaic effect
|
|
80
|
+
const ll = mosaic(),
|
|
81
|
+
ul = mosaic(),
|
|
82
|
+
lr = mosaic(),
|
|
83
|
+
ur = mosaic()
|
|
84
|
+
|
|
85
|
+
//stroke
|
|
86
|
+
if (this.shadowFactor > 0) {
|
|
87
|
+
cg.ctx.beginPath()
|
|
88
|
+
cg.ctx.moveTo(cell.x + offset.dx + ll.x, cell.y + offset.dy + ll.y)
|
|
89
|
+
cg.ctx.lineTo(cell.x + offset.dx + resolution - lr.x, cell.y + offset.dy + lr.y)
|
|
90
|
+
cg.ctx.lineTo(cell.x + offset.dx + resolution - ur.x, cell.y + offset.dy + resolution - ur.y)
|
|
91
|
+
cg.ctx.stroke()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//fill
|
|
95
|
+
|
|
96
|
+
cg.ctx.beginPath()
|
|
97
|
+
cg.ctx.moveTo(cell.x + offset.dx + ll.x, cell.y + offset.dy + ll.y)
|
|
98
|
+
cg.ctx.lineTo(cell.x + offset.dx + resolution - lr.x, cell.y + offset.dy + lr.y)
|
|
99
|
+
cg.ctx.lineTo(cell.x + offset.dx + resolution - ur.x, cell.y + offset.dy + resolution - ur.y)
|
|
100
|
+
cg.ctx.lineTo(cell.x + offset.dx + ul.x, cell.y + offset.dy + resolution - ul.y)
|
|
101
|
+
cg.ctx.fill()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
//update legends
|
|
105
|
+
this.updateLegends({ style: this, r: resolution, zf: zf, sColor: statColor })
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
import { Style } from '../Style'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @author Joseph Davies, Julien Gaffuri
|
|
9
|
+
*/
|
|
10
|
+
export class NinjaStarStyle extends Style {
|
|
11
|
+
/** @param {object} opts */
|
|
12
|
+
constructor(opts) {
|
|
13
|
+
super(opts)
|
|
14
|
+
opts = opts || {}
|
|
15
|
+
|
|
16
|
+
/** The name of the column/attribute of the tabular data where to retrieve the variable for color.
|
|
17
|
+
* @type {string} */
|
|
18
|
+
this.colorCol = opts.colorCol
|
|
19
|
+
|
|
20
|
+
/** A function returning the color of the cell.
|
|
21
|
+
* @type {function(number,number,import("../Style").Stat|undefined,number):string} */
|
|
22
|
+
this.color = opts.color || (() => '#EA6BAC') //(v,r,s,zf) => {}
|
|
23
|
+
|
|
24
|
+
/** The name of the column/attribute of the tabular data where to retrieve the variable for size.
|
|
25
|
+
* @type {string} */
|
|
26
|
+
this.sizeCol = opts.sizeCol
|
|
27
|
+
|
|
28
|
+
/** A function returning the size of a cell, within [0,1]:
|
|
29
|
+
* - 0, nothing shown
|
|
30
|
+
* - 1, entire square
|
|
31
|
+
* @type {function(number,number,import("../Style").Stat|undefined,number):number} */
|
|
32
|
+
this.size = opts.size
|
|
33
|
+
|
|
34
|
+
/** A function returning the shape.
|
|
35
|
+
* @type {function(import("../Dataset").Cell):string} */
|
|
36
|
+
this.shape = opts.shape || (() => "o")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @param {Array.<import("../Dataset").Cell>} cells
|
|
42
|
+
* @param {number} r
|
|
43
|
+
* @param {import("../GeoCanvas").GeoCanvas} cg
|
|
44
|
+
*/
|
|
45
|
+
draw(cells, r, cg) {
|
|
46
|
+
//filter
|
|
47
|
+
if (this.filter) cells = cells.filter(this.filter)
|
|
48
|
+
|
|
49
|
+
//zoom factor
|
|
50
|
+
const zf = cg.getZf()
|
|
51
|
+
|
|
52
|
+
let statSize
|
|
53
|
+
if (this.sizeCol) {
|
|
54
|
+
//if size is used, sort cells by size so that the biggest are drawn first
|
|
55
|
+
cells.sort((c1, c2) => c2[this.sizeCol] - c1[this.sizeCol])
|
|
56
|
+
//and compute size variable statistics
|
|
57
|
+
statSize = Style.getStatistics(cells, (c) => c[this.sizeCol], true)
|
|
58
|
+
}
|
|
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
|
+
//draw with HTML canvas
|
|
67
|
+
//in geo coordinates
|
|
68
|
+
cg.setCanvasTransform()
|
|
69
|
+
|
|
70
|
+
const r2 = r * 0.5
|
|
71
|
+
for (let cell of cells) {
|
|
72
|
+
//color
|
|
73
|
+
const col = this.color ? this.color(cell[this.colorCol], r, statColor, zf) : undefined
|
|
74
|
+
if (!col || col === 'none') continue
|
|
75
|
+
cg.ctx.fillStyle = col
|
|
76
|
+
|
|
77
|
+
//shape
|
|
78
|
+
const shape = this.shape ? this.shape(cell) : 'o'
|
|
79
|
+
if (shape === 'none') continue
|
|
80
|
+
|
|
81
|
+
//size
|
|
82
|
+
/** @type {function(number,number,import("../Style").Stat|undefined,number):number} */
|
|
83
|
+
let s_ = this.size || (() => 0.5)
|
|
84
|
+
//size - in geo unit
|
|
85
|
+
const sG2 = s_(cell[this.sizeCol], r, statSize, zf) * r2
|
|
86
|
+
|
|
87
|
+
//get offset
|
|
88
|
+
const offset = this.offset(cell, r, zf)
|
|
89
|
+
|
|
90
|
+
//center position
|
|
91
|
+
const cx = cell.x + r2
|
|
92
|
+
const cy = cell.y + r2
|
|
93
|
+
|
|
94
|
+
if (shape === 'p') {
|
|
95
|
+
cg.ctx.beginPath()
|
|
96
|
+
cg.ctx.moveTo(cx, cy + r2)
|
|
97
|
+
cg.ctx.lineTo(cx + sG2, cy + sG2)
|
|
98
|
+
cg.ctx.lineTo(cx + r2, cy)
|
|
99
|
+
cg.ctx.lineTo(cx + sG2, cy - sG2)
|
|
100
|
+
cg.ctx.lineTo(cx, cy - r2)
|
|
101
|
+
cg.ctx.lineTo(cx - sG2, cy - sG2)
|
|
102
|
+
cg.ctx.lineTo(cx - r2, cy)
|
|
103
|
+
cg.ctx.lineTo(cx - sG2, cy + sG2)
|
|
104
|
+
cg.ctx.fill()
|
|
105
|
+
} else if (shape === 'o') {
|
|
106
|
+
cg.ctx.beginPath()
|
|
107
|
+
cg.ctx.moveTo(cx, cy + sG2)
|
|
108
|
+
cg.ctx.lineTo(cx + r2, cy + r2)
|
|
109
|
+
cg.ctx.lineTo(cx + sG2, cy)
|
|
110
|
+
cg.ctx.lineTo(cx + r2, cy - r2)
|
|
111
|
+
cg.ctx.lineTo(cx, cy - sG2)
|
|
112
|
+
cg.ctx.lineTo(cx - r2, cy - r2)
|
|
113
|
+
cg.ctx.lineTo(cx - sG2, cy)
|
|
114
|
+
cg.ctx.lineTo(cx - r2, cy + r2)
|
|
115
|
+
cg.ctx.fill()
|
|
116
|
+
} else {
|
|
117
|
+
throw new Error('Unexpected shape:' + shape)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//update legends
|
|
122
|
+
this.updateLegends({ style: this, r: r, zf: zf, sSize: statSize, sColor: statColor })
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
import { Style } from '../Style'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @author Julien Gaffuri
|
|
8
|
+
*/
|
|
9
|
+
export class PillarStyle extends Style {
|
|
10
|
+
//TODO make a webGL version ?
|
|
11
|
+
|
|
12
|
+
/** @param {object} opts */
|
|
13
|
+
constructor(opts) {
|
|
14
|
+
super(opts)
|
|
15
|
+
opts = opts || {}
|
|
16
|
+
|
|
17
|
+
/** @type {string} */
|
|
18
|
+
this.heightCol = opts.heightCol
|
|
19
|
+
|
|
20
|
+
/** A function returning the height of the line representing a cell, in geo unit
|
|
21
|
+
* @type {function(number,number,import("../Style").Stat|undefined,number):number} */
|
|
22
|
+
this.height = opts.height
|
|
23
|
+
|
|
24
|
+
/** @type {string} */
|
|
25
|
+
this.colorCol = opts.colorCol
|
|
26
|
+
|
|
27
|
+
/** A function returning the color of the line representing a cell.
|
|
28
|
+
* @type {function(number,number,import("../Style").Stat|undefined):string} */
|
|
29
|
+
this.color = opts.color || (() => '#c08c59') //bb
|
|
30
|
+
|
|
31
|
+
/** @type {string} */
|
|
32
|
+
this.widthCol = opts.widthCol
|
|
33
|
+
|
|
34
|
+
/** A function returning the width of the line representing a cell, in geo unit
|
|
35
|
+
* @type {function(number,number,import("../Style").Stat|undefined,number):number} */
|
|
36
|
+
this.width = opts.width || ((v, r) => 0.5 * r)
|
|
37
|
+
|
|
38
|
+
/** @type {boolean} */
|
|
39
|
+
this.simple = opts.simple != undefined
|
|
40
|
+
|
|
41
|
+
/** @type {number} */
|
|
42
|
+
this.viewHeightFactor = opts.viewHeightFactor || 1.5
|
|
43
|
+
//0,0 is the center
|
|
44
|
+
/** @type {number} */
|
|
45
|
+
this.viewSX = opts.viewSX == undefined ? 0 : opts.viewSX
|
|
46
|
+
/** @type {number} */
|
|
47
|
+
this.viewSY = opts.viewSY == undefined ? -0.5 : opts.viewSY
|
|
48
|
+
|
|
49
|
+
//TODO replace with sun location ?
|
|
50
|
+
/** @type {number} */
|
|
51
|
+
this.shadowDirection =
|
|
52
|
+
opts.shadowDirection == undefined ? (-40.3 * Math.PI) / 180.0 : opts.shadowDirection
|
|
53
|
+
/** @type {number} */
|
|
54
|
+
this.shadowFactor = opts.shadowFactor || 0.3
|
|
55
|
+
/** @type {string} */
|
|
56
|
+
this.shadowColor = opts.shadowColor || '#00000033'
|
|
57
|
+
|
|
58
|
+
/** @type {string} */
|
|
59
|
+
this.outlineCol = opts.outlineCol || '#FFFFFF'
|
|
60
|
+
/** @type {number} */
|
|
61
|
+
this.outlineWidthPix = opts.outlineWidthPix == undefined ? 0.5 : opts.outlineWidthPix
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Draw cells as segments.
|
|
66
|
+
*
|
|
67
|
+
* @param {Array.<import("../Dataset").Cell>} cells
|
|
68
|
+
* @param {number} r
|
|
69
|
+
* @param {import("../GeoCanvas").GeoCanvas} cg
|
|
70
|
+
*/
|
|
71
|
+
draw(cells, r, cg) {
|
|
72
|
+
//filter
|
|
73
|
+
if (this.filter) cells = cells.filter(this.filter)
|
|
74
|
+
|
|
75
|
+
//zoom factor
|
|
76
|
+
const zf = cg.getZf()
|
|
77
|
+
|
|
78
|
+
let statHeight
|
|
79
|
+
if (this.heightCol) {
|
|
80
|
+
//compute size variable statistics
|
|
81
|
+
statHeight = Style.getStatistics(cells, (c) => c[this.heightCol], true)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let statColor
|
|
85
|
+
if (this.colorCol) {
|
|
86
|
+
//compute color variable statistics
|
|
87
|
+
statColor = Style.getStatistics(cells, (c) => c[this.colorCol], true)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let statWidth
|
|
91
|
+
if (this.widthCol) {
|
|
92
|
+
//and compute size variable statistics
|
|
93
|
+
statWidth = Style.getStatistics(cells, (c) => c[this.widthCol], true)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
//get view center geo position
|
|
97
|
+
const cvx = cg.getCenter().x + this.viewSX * cg.w * zf
|
|
98
|
+
const cvy = cg.getCenter().y + this.viewSY * cg.h * zf
|
|
99
|
+
//get view height
|
|
100
|
+
const H = this.viewHeightFactor * (cg.w + cg.h) * 0.5 * zf
|
|
101
|
+
|
|
102
|
+
//sort cells by y and x
|
|
103
|
+
//const distToViewCenter = (c) => { const dx = cvx - c.x, dy = cvy - c.y; return Math.sqrt(dx * dx + dy * dy) }
|
|
104
|
+
cells.sort((c1, c2) => 100000000 * (c2.y - c1.y) + c1.x - c2.x)
|
|
105
|
+
|
|
106
|
+
cg.ctx.lineCap = this.simple ? 'butt' : 'round'
|
|
107
|
+
|
|
108
|
+
//draw in geo coordinates
|
|
109
|
+
cg.setCanvasTransform()
|
|
110
|
+
|
|
111
|
+
//draw shadows
|
|
112
|
+
cg.ctx.strokeStyle = this.shadowColor
|
|
113
|
+
cg.ctx.fillStyle = this.shadowColor
|
|
114
|
+
for (let c of cells) {
|
|
115
|
+
//width
|
|
116
|
+
/** @type {number|undefined} */
|
|
117
|
+
const wG = this.width ? this.width(c[this.widthCol], r, statWidth, zf) : undefined
|
|
118
|
+
if (!wG || wG < 0) continue
|
|
119
|
+
|
|
120
|
+
//height
|
|
121
|
+
/** @type {number|undefined} */
|
|
122
|
+
const hG = this.height ? this.height(c[this.heightCol], r, statHeight, zf) : undefined
|
|
123
|
+
if (!hG || hG < 0) continue
|
|
124
|
+
|
|
125
|
+
//get offset
|
|
126
|
+
//TODO use that
|
|
127
|
+
const offset = this.offset(c, r, zf)
|
|
128
|
+
|
|
129
|
+
//set width
|
|
130
|
+
cg.ctx.lineWidth = wG
|
|
131
|
+
|
|
132
|
+
//compute cell centre postition
|
|
133
|
+
const cx = c.x + r / 2
|
|
134
|
+
const cy = c.y + r / 2
|
|
135
|
+
const ls = hG * this.shadowFactor
|
|
136
|
+
|
|
137
|
+
//draw segment
|
|
138
|
+
cg.ctx.beginPath()
|
|
139
|
+
cg.ctx.moveTo(cx, cy)
|
|
140
|
+
cg.ctx.lineTo(cx + ls * Math.cos(this.shadowDirection), cy + ls * Math.sin(this.shadowDirection))
|
|
141
|
+
cg.ctx.stroke()
|
|
142
|
+
|
|
143
|
+
/*
|
|
144
|
+
if (this.simple) {
|
|
145
|
+
//draw base circle
|
|
146
|
+
cg.ctx.beginPath();
|
|
147
|
+
cg.ctx.arc(
|
|
148
|
+
cx, cy,
|
|
149
|
+
wG * 0.5,
|
|
150
|
+
0, 2 * Math.PI, false);
|
|
151
|
+
//cg.ctx.stroke();
|
|
152
|
+
cg.ctx.fill();
|
|
153
|
+
}*/
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
//draw pillars
|
|
157
|
+
for (let c of cells) {
|
|
158
|
+
//color
|
|
159
|
+
/** @type {string|undefined} */
|
|
160
|
+
const col = this.color ? this.color(c[this.colorCol], r, statColor) : undefined
|
|
161
|
+
if (!col) continue
|
|
162
|
+
|
|
163
|
+
//width
|
|
164
|
+
/** @type {number|undefined} */
|
|
165
|
+
const wG = this.width ? this.width(c[this.widthCol], r, statWidth, zf) : undefined
|
|
166
|
+
if (!wG || wG < 0) continue
|
|
167
|
+
|
|
168
|
+
//height
|
|
169
|
+
/** @type {number|undefined} */
|
|
170
|
+
const hG = this.height ? this.height(c[this.heightCol], r, statHeight, zf) : undefined
|
|
171
|
+
if (!hG || hG < 0) continue
|
|
172
|
+
|
|
173
|
+
//get offset
|
|
174
|
+
//TODO use that
|
|
175
|
+
const offset = this.offset(c, r, zf)
|
|
176
|
+
|
|
177
|
+
//compute cell centre postition
|
|
178
|
+
const cx = c.x + r / 2
|
|
179
|
+
const cy = c.y + r / 2
|
|
180
|
+
|
|
181
|
+
//compute angle
|
|
182
|
+
const dx = cx - cvx,
|
|
183
|
+
dy = cy - cvy
|
|
184
|
+
const a = Math.atan2(dy, dx)
|
|
185
|
+
const D = Math.sqrt(dx * dx + dy * dy)
|
|
186
|
+
const d = (D * hG) / (H - hG)
|
|
187
|
+
|
|
188
|
+
if (this.simple) {
|
|
189
|
+
//draw segment
|
|
190
|
+
cg.ctx.strokeStyle = col
|
|
191
|
+
cg.ctx.lineWidth = wG
|
|
192
|
+
cg.ctx.beginPath()
|
|
193
|
+
cg.ctx.moveTo(cx, cy)
|
|
194
|
+
cg.ctx.lineTo(cx + d * Math.cos(a), cy + d * Math.sin(a))
|
|
195
|
+
cg.ctx.stroke()
|
|
196
|
+
} else {
|
|
197
|
+
//draw background segment
|
|
198
|
+
cg.ctx.strokeStyle = this.outlineCol
|
|
199
|
+
cg.ctx.lineWidth = wG + 2 * this.outlineWidthPix * zf
|
|
200
|
+
cg.ctx.beginPath()
|
|
201
|
+
cg.ctx.moveTo(cx, cy)
|
|
202
|
+
cg.ctx.lineTo(cx + d * Math.cos(a), cy + d * Math.sin(a))
|
|
203
|
+
cg.ctx.stroke()
|
|
204
|
+
|
|
205
|
+
//draw segment
|
|
206
|
+
cg.ctx.strokeStyle = col
|
|
207
|
+
cg.ctx.lineWidth = wG
|
|
208
|
+
cg.ctx.beginPath()
|
|
209
|
+
cg.ctx.moveTo(cx, cy)
|
|
210
|
+
cg.ctx.lineTo(cx + d * Math.cos(a), cy + d * Math.sin(a))
|
|
211
|
+
cg.ctx.stroke()
|
|
212
|
+
|
|
213
|
+
//draw top circle
|
|
214
|
+
cg.ctx.strokeStyle = this.outlineCol
|
|
215
|
+
//cg.ctx.fillStyle = "#c08c59"
|
|
216
|
+
cg.ctx.lineWidth = this.outlineWidthPix * zf
|
|
217
|
+
cg.ctx.beginPath()
|
|
218
|
+
cg.ctx.arc(cx + d * Math.cos(a), cy + d * Math.sin(a), wG * 0.5, 0, 2 * Math.PI, false)
|
|
219
|
+
cg.ctx.stroke()
|
|
220
|
+
//cg.ctx.fill();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
//in case...
|
|
225
|
+
cg.ctx.lineCap = 'butt'
|
|
226
|
+
|
|
227
|
+
//update legends
|
|
228
|
+
this.updateLegends({ style: this, r: r, zf: zf, sColor: statColor })
|
|
229
|
+
}
|
|
230
|
+
}
|