gridviz 2.1.16 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/gridviz.js +4971 -5654
- package/dist/gridviz.min.js +1 -1
- package/package.json +3 -2
- package/src/button/Button.js +14 -15
- package/src/button/FullscreenButton.js +14 -14
- package/src/button/ZoomButtons.js +2 -2
- package/src/{DatasetComponent.js → core/Dataset.js} +33 -12
- package/src/core/Drawable.js +60 -0
- package/src/{GeoCanvas.js → core/GeoCanvas.js} +65 -155
- package/src/core/Layer.js +24 -0
- package/src/{Legend.js → core/Legend.js} +24 -1
- package/src/core/Map.js +460 -0
- package/src/core/MultiResolutionDataset.js +96 -0
- package/src/core/Style.js +84 -0
- package/src/dataset/COGeo.js +5 -5
- package/src/dataset/CSVGrid.js +43 -41
- package/src/dataset/GeoTIFF.js +9 -14
- package/src/dataset/{LGrid.js → JSGrid.js} +9 -10
- package/src/dataset/TiledGrid.js +68 -45
- package/src/index.js +22 -29
- package/src/{BackgroundLayer.js → layer/BackgroundLayer.js} +27 -52
- package/src/{BackgroundLayerWMS.js → layer/BackgroundLayerWMS.js} +21 -45
- package/src/{LineLayer.js → layer/GeoJSONLayer.js} +22 -23
- package/src/layer/GridLayer.js +140 -0
- package/src/{LabelLayer.js → layer/LabelLayer.js} +25 -23
- package/src/legend/ColorCategoryLegend.js +9 -24
- package/src/legend/ColorDiscreteLegend.js +20 -24
- package/src/legend/ColorLegend.js +21 -33
- package/src/legend/OrientationLegend.js +90 -0
- package/src/legend/SizeLegend.js +153 -72
- package/src/style/CompositionStyle.js +71 -84
- package/src/style/ContourStyle.js +2 -36
- package/src/style/DotDensityStyle.js +50 -73
- package/src/style/IsoFenceStyle.js +59 -70
- package/src/style/JoyPlotStyle.js +41 -48
- package/src/style/LegoStyle.js +4 -4
- package/src/style/MosaicStyle.js +30 -39
- package/src/style/NinjaStarStyle.js +45 -68
- package/src/style/PillarStyle.js +70 -93
- package/src/style/SegmentStyle.js +37 -75
- package/src/style/ShapeColorSizeStyle.js +49 -98
- package/src/style/SideCategoryStyle.js +112 -0
- package/src/style/SideStyle.js +99 -133
- package/src/style/{SquareColorCatWGLStyle.js → SquareColorCategoryWebGLStyle.js} +28 -41
- package/src/style/{SquareColorWGLStyle.js → SquareColorWebGLStyle.js} +28 -49
- package/src/style/StrokeStyle.js +43 -70
- package/src/style/TanakaStyle.js +34 -132
- package/src/style/TextStyle.js +43 -64
- package/src/style/TimeSeriesStyle.js +44 -75
- package/src/utils/WebGLSquareColoringAdvanced.js +4 -4
- package/src/utils/scale.js +202 -0
- package/src/utils/stretching.js +86 -129
- package/src/utils/{Utils.js → utils.js} +15 -0
- package/src/utils/webGLUtils.js +4 -5
- package/src/utils/zoomUtils.js +30 -30
- package/src/App.js +0 -760
- package/src/Dataset.js +0 -76
- package/src/Layer.js +0 -103
- package/src/Style.js +0 -131
- package/src/dataset/GridTile.js +0 -41
- package/src/legend/SegmentOrientationLegend.js +0 -87
- package/src/legend/SegmentWidthLegend.js +0 -101
- package/src/style/CompositionStyle_.js +0 -293
- package/src/style/ShapeColorSizeStyle_.js +0 -108
- package/src/style/SideCatStyle.js +0 -172
- /package/src/{Tooltip.js → core/Tooltip.js} +0 -0
package/src/legend/SizeLegend.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
//@ts-check
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
|
-
import { Legend } from '../Legend.js'
|
|
5
|
-
import {
|
|
4
|
+
import { Legend } from '../core/Legend.js'
|
|
5
|
+
import { nice } from '../utils/utils.js'
|
|
6
|
+
import { max } from 'd3-array'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* A legend element for proportional symbols.
|
|
@@ -15,103 +16,78 @@ export class SizeLegend extends Legend {
|
|
|
15
16
|
super(opts)
|
|
16
17
|
opts = opts || {}
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
/** A function returning the text label
|
|
20
|
+
* @type { function(object, Array.<import('../core/Dataset.js').Cell>):(number|string) } */
|
|
21
|
+
this.label = opts.label || undefined
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
//title
|
|
25
|
-
this.title = opts.title
|
|
26
|
-
this.titleFontSize = opts.titleFontSize || '0.8em'
|
|
27
|
-
this.titleFontWeight = opts.titleFontWeight || 'bold'
|
|
23
|
+
/** A function returning the size of the legend symbol
|
|
24
|
+
* @type { function(object):number } */
|
|
25
|
+
this.size = opts.size || undefined
|
|
28
26
|
|
|
29
27
|
//symbol
|
|
30
|
-
/**
|
|
31
|
-
* @private
|
|
32
|
-
* @type {import("../Style").Shape} */
|
|
28
|
+
/** @type {(import("../core/Style").Shape)|"line"} */
|
|
33
29
|
this.shape = opts.shape || 'circle'
|
|
30
|
+
|
|
31
|
+
//general case
|
|
34
32
|
this.fillColor = opts.fillColor || 'none'
|
|
35
33
|
this.strokeColor = opts.strokeColor || 'gray'
|
|
36
34
|
this.strokeWidth = opts.strokeWidth || 1
|
|
37
35
|
|
|
38
|
-
//
|
|
39
|
-
this.
|
|
40
|
-
this.
|
|
41
|
-
this.
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
//this.div.style("text-align", "center")
|
|
36
|
+
//for line shape
|
|
37
|
+
//TODO this.orientation = opts.orientation || 0
|
|
38
|
+
this.color = opts.color || 'gray'
|
|
39
|
+
this.length = opts.length || ((resolution, z, viewScale) => resolution)
|
|
45
40
|
}
|
|
46
41
|
|
|
47
42
|
/**
|
|
48
|
-
* @param {{
|
|
43
|
+
* @param {{ viewScale:object, resolution: number, z:number, cells:Array.<import('../core/Dataset.js').Cell> }} opts
|
|
49
44
|
*/
|
|
50
45
|
update(opts) {
|
|
51
|
-
//could happen when data is still loading
|
|
52
|
-
if (!opts.sSize) return
|
|
53
46
|
|
|
54
47
|
//clear
|
|
55
48
|
this.div.selectAll('*').remove()
|
|
56
49
|
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
if (value == undefined) {
|
|
60
|
-
//compute 'nice value
|
|
61
|
-
|
|
62
|
-
//get max value
|
|
63
|
-
const value_ = opts.sSize.max * this.exaggerationFactor
|
|
64
|
-
|
|
65
|
-
//take 'nice' value (power of ten, or multiple)
|
|
66
|
-
let pow10 = Math.log10(value_)
|
|
67
|
-
pow10 = Math.floor(pow10)
|
|
68
|
-
value = Math.pow(10, pow10)
|
|
69
|
-
if (value * 8 <= value_) value *= 8
|
|
70
|
-
else if (value * 6 <= value_) value *= 6
|
|
71
|
-
else if (value * 5 <= value_) value *= 5
|
|
72
|
-
else if (value * 4 <= value_) value *= 4
|
|
73
|
-
else if (value * 2.5 <= value_) value *= 2.5
|
|
74
|
-
else if (value * 2 <= value_) value *= 2
|
|
75
|
-
}
|
|
50
|
+
//title
|
|
51
|
+
this.makeTitle()
|
|
76
52
|
|
|
77
|
-
|
|
53
|
+
//get label. May not be a number (!)
|
|
54
|
+
let label = this.label(opts.viewScale, opts.cells)
|
|
55
|
+
|
|
56
|
+
//compute size of symbol, in pix
|
|
57
|
+
let sizePix
|
|
58
|
+
if (this.size)
|
|
59
|
+
sizePix = this.size(opts.viewScale) / opts.z
|
|
60
|
+
else
|
|
61
|
+
sizePix = opts.viewScale(+label) / opts.z
|
|
62
|
+
if (!sizePix) return
|
|
63
|
+
|
|
64
|
+
//format label, if specified and possible
|
|
65
|
+
if (this.labelFormat && !isNaN(+label)) label = this.labelFormat(label)
|
|
78
66
|
|
|
79
67
|
const d = this.div.append('div')
|
|
80
68
|
//to enable vertical centering
|
|
81
69
|
//.style("position", "relative")
|
|
82
70
|
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
d.append('div')
|
|
86
|
-
.attr('class', 'title')
|
|
87
|
-
.style('font-size', this.titleFontSize)
|
|
88
|
-
.style('font-weight', this.titleFontWeight)
|
|
89
|
-
.text(this.title)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
//compute size of symbol, in pix
|
|
93
|
-
const size = opts.style.size(value, opts.r, opts.sSize, opts.zf) / opts.zf
|
|
94
|
-
|
|
95
|
-
const svg = d
|
|
71
|
+
//default svg construction, for square and circle
|
|
72
|
+
const svg = () => d
|
|
96
73
|
.append('svg')
|
|
97
|
-
.attr('width',
|
|
98
|
-
.attr('height',
|
|
74
|
+
.attr('width', sizePix + this.strokeWidth + 2)
|
|
75
|
+
.attr('height', sizePix + this.strokeWidth + 2)
|
|
99
76
|
.style('', 'inline-block')
|
|
100
77
|
|
|
101
78
|
if (this.shape === 'square') {
|
|
102
|
-
svg.append('rect')
|
|
79
|
+
svg().append('rect')
|
|
103
80
|
.attr('x', 0)
|
|
104
81
|
.attr('y', 0)
|
|
105
|
-
.attr('width',
|
|
106
|
-
.attr('height',
|
|
82
|
+
.attr('width', sizePix)
|
|
83
|
+
.attr('height', sizePix)
|
|
107
84
|
.style('fill', this.fillColor)
|
|
108
85
|
.style('stroke', this.strokeColor)
|
|
109
86
|
.style('stroke-width', this.strokeWidth)
|
|
110
|
-
//TODO test
|
|
111
87
|
} else if (this.shape === 'circle') {
|
|
112
88
|
// <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
|
|
113
|
-
const r = (
|
|
114
|
-
svg.append('circle')
|
|
89
|
+
const r = (sizePix + this.strokeWidth) * 0.5
|
|
90
|
+
svg().append('circle')
|
|
115
91
|
.attr('cx', r + 1)
|
|
116
92
|
.attr('cy', r + 1)
|
|
117
93
|
.attr('r', r)
|
|
@@ -122,20 +98,125 @@ export class SizeLegend extends Legend {
|
|
|
122
98
|
//TODO
|
|
123
99
|
} else if (this.shape === 'diamond') {
|
|
124
100
|
//TODO
|
|
101
|
+
} else if (this.shape === 'line') {
|
|
102
|
+
|
|
103
|
+
//get segment length
|
|
104
|
+
let lengthPix = this.length ? this.length(opts.resolution, opts.z, opts.viewScale) : opts.resolution
|
|
105
|
+
lengthPix /= opts.z
|
|
106
|
+
|
|
107
|
+
const svg = d.append('svg').attr('width', lengthPix).attr('height', sizePix).style('', 'inline-block')
|
|
108
|
+
|
|
109
|
+
//TODO orientation
|
|
110
|
+
//<line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" />
|
|
111
|
+
svg.append('line')
|
|
112
|
+
.attr('x1', 0)
|
|
113
|
+
.attr('y1', sizePix / 2)
|
|
114
|
+
.attr('x2', lengthPix)
|
|
115
|
+
.attr('y2', sizePix / 2)
|
|
116
|
+
.style('stroke', this.color)
|
|
117
|
+
.style('stroke-width', sizePix)
|
|
125
118
|
} else {
|
|
126
119
|
throw new Error('Unexpected shape:' + this.shape)
|
|
127
120
|
}
|
|
128
121
|
|
|
129
|
-
|
|
122
|
+
//label
|
|
130
123
|
d.append('div')
|
|
131
|
-
//show on right of graphic
|
|
132
124
|
.style('display', 'inline')
|
|
133
|
-
|
|
134
|
-
//center vertically
|
|
135
|
-
//.style("position", "absolute").style("top", "0").style("bottom", "0")
|
|
136
|
-
|
|
137
125
|
.style('padding-left', '5px')
|
|
138
126
|
.style('font-size', this.labelFontSize)
|
|
139
|
-
.text(
|
|
127
|
+
.text(label + (this.labelUnitText ? ' ' : '') + this.labelUnitText)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {Array.<number>} values
|
|
139
|
+
* @param {function(number):number} size
|
|
140
|
+
* @param { object } opts
|
|
141
|
+
* @returns {Array.<SizeLegend>}
|
|
142
|
+
*/
|
|
143
|
+
export function sizeLegend(values, size, opts = {}) {
|
|
144
|
+
const legends = []
|
|
145
|
+
for (let value of values) {
|
|
146
|
+
opts.title = value == values[0] ? opts.title : undefined;
|
|
147
|
+
opts.size = () => size(value)
|
|
148
|
+
opts.label = () => value
|
|
149
|
+
legends.push(new SizeLegend(opts))
|
|
150
|
+
}
|
|
151
|
+
return legends
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @param { function(import('../core/Dataset.js').Cell):number } value
|
|
156
|
+
* @param {*} opts
|
|
157
|
+
* @returns {Array.<SizeLegend>}
|
|
158
|
+
*/
|
|
159
|
+
export function sizeLegendViewScale(value, opts = {}) {
|
|
160
|
+
const k = opts.k || [0.9, 0.5, 0.2, 0.05]
|
|
161
|
+
const legends = []
|
|
162
|
+
for (let k_ of k) {
|
|
163
|
+
opts.title = k_ == k[0] ? opts.title : undefined
|
|
164
|
+
opts.label = (viewScale, cells) => nice(k_ * max(cells, value))
|
|
165
|
+
legends.push(new SizeLegend(opts))
|
|
166
|
+
}
|
|
167
|
+
return legends
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* A function which return a stack of size legends for a discrete classification.
|
|
172
|
+
*
|
|
173
|
+
* @param { Array.<number> } breaks
|
|
174
|
+
* @param { Array.<number> } sizes
|
|
175
|
+
* @param { object } opts
|
|
176
|
+
* @returns {Array.<SizeLegend>}
|
|
177
|
+
*/
|
|
178
|
+
export function sizeDiscreteLegend(breaks, sizes, opts = {}) {
|
|
179
|
+
const f = opts.labelFormat || (x => x)
|
|
180
|
+
const labelText = opts.labelText || defaultLabelText(f)
|
|
181
|
+
const legends = []
|
|
182
|
+
for (let i = sizes.length - 1; i >= 0; i--) {
|
|
183
|
+
opts.title = i == sizes.length - 1 ? opts.title : undefined
|
|
184
|
+
opts.size = () => sizes[i]
|
|
185
|
+
opts.label = () => labelText(breaks[i - 1], breaks[i])
|
|
186
|
+
legends.push(new SizeLegend(opts))
|
|
187
|
+
}
|
|
188
|
+
return legends
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* A function which return a stack of size legends for a discrete classification using a viewscale.
|
|
193
|
+
* @param { number } classNumber
|
|
194
|
+
* @param { object } opts
|
|
195
|
+
* @returns {Array.<SizeLegend>}
|
|
196
|
+
*/
|
|
197
|
+
export function sizeDiscreteViewScaleLegend(classNumber, opts = {}) {
|
|
198
|
+
const f = opts.labelFormat || (x => x)
|
|
199
|
+
const labelText = opts.labelText || defaultLabelText(f)
|
|
200
|
+
const legends = []
|
|
201
|
+
for (let i = classNumber - 1; i >= 0; i--) {
|
|
202
|
+
opts.title = i == classNumber - 1 ? opts.title : undefined
|
|
203
|
+
opts.size = (viewScale) => viewScale.values[i]
|
|
204
|
+
opts.label = (viewScale) => labelText(viewScale.breaks[i - 1], viewScale.breaks[i])
|
|
205
|
+
legends.push(new SizeLegend(opts))
|
|
206
|
+
}
|
|
207
|
+
return legends
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* A function that returns a function to format laberls for discrete scale legends.
|
|
212
|
+
* @param { function(number):string } format
|
|
213
|
+
* @returns { function(number|undefined, number|undefined): string }
|
|
214
|
+
*/
|
|
215
|
+
function defaultLabelText(format) {
|
|
216
|
+
return (v0, v1) => {
|
|
217
|
+
if (v0 == undefined && v1 == undefined) return ""
|
|
218
|
+
if (v1 == undefined) return "> " + format(v0)
|
|
219
|
+
if (v0 == undefined) return "< " + format(v1)
|
|
220
|
+
return format(v0) + " - " + format(v1)
|
|
140
221
|
}
|
|
141
222
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//@ts-check
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
|
-
import { Style } from '../Style.js'
|
|
4
|
+
import { Style } from '../core/Style.js'
|
|
5
5
|
|
|
6
6
|
/** @typedef {"flag"|"piechart"|"ring"|"segment"|"radar"|"agepyramid"|"halftone"} CompositionType */
|
|
7
7
|
|
|
@@ -26,29 +26,25 @@ export class CompositionStyle extends Style {
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
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
|
|
29
|
+
* @type {function(import("../core/Dataset.js").Cell,number, number,object):CompositionType} */
|
|
30
|
+
this.type = opts.type || (() => "flag") //(c,r,z,vs) => {}
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
* @type {
|
|
34
|
-
this.
|
|
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)
|
|
32
|
+
/** A function returning the size of a cell in geographical unit.
|
|
33
|
+
* @type {function(import('../core/Dataset.js').Cell, number, number, object):number} */
|
|
34
|
+
this.size = opts.size || ((c, r) => r) //(c,r,z,vs) => {}
|
|
39
35
|
|
|
40
36
|
/** 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,
|
|
37
|
+
* @type {function(import("../core/Dataset.js").Cell,number,number,object):number} */
|
|
38
|
+
this.stripesOrientation = opts.stripesOrientation || (() => 0) //(c,r,z,vs) => ...
|
|
43
39
|
|
|
44
40
|
/** The function specifying an offset angle for a radar, halftone or pie chart style.
|
|
45
41
|
* 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) //(
|
|
42
|
+
* @type {function(import("../core/Dataset.js").Cell,number,number,object):number} */
|
|
43
|
+
this.offsetAngle = opts.offsetAngle || (() => 0) //(c,r,z,vs) => ...
|
|
48
44
|
|
|
49
45
|
/** 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
|
|
46
|
+
* @type {function(import("../core/Dataset.js").Cell,number,number,object):number} */
|
|
47
|
+
this.agePyramidHeight = opts.agePyramidHeight || ((c, r) => r) //(c,r,z,vs) => ...
|
|
52
48
|
|
|
53
49
|
/** For pie chart, this is parameter for internal radius, so that the pie chart looks like a donut.
|
|
54
50
|
* 0 for normal pie charts, 0.5 to empty half of the radius.
|
|
@@ -59,52 +55,43 @@ export class CompositionStyle extends Style {
|
|
|
59
55
|
/**
|
|
60
56
|
* Draw cells as squares depending on their value.
|
|
61
57
|
*
|
|
62
|
-
* @param {Array.<import("../Dataset").Cell>} cells
|
|
63
|
-
* @param {
|
|
64
|
-
* @param {
|
|
58
|
+
* @param {Array.<import("../core/Dataset.js").Cell>} cells
|
|
59
|
+
* @param {import("../core/GeoCanvas.js").GeoCanvas} geoCanvas
|
|
60
|
+
* @param {number} resolution
|
|
65
61
|
*/
|
|
66
|
-
draw(cells,
|
|
62
|
+
draw(cells, geoCanvas, resolution) {
|
|
63
|
+
|
|
67
64
|
//filter
|
|
68
65
|
if (this.filter) cells = cells.filter(this.filter)
|
|
69
66
|
|
|
70
|
-
//
|
|
71
|
-
const
|
|
67
|
+
//
|
|
68
|
+
const z = geoCanvas.view.z
|
|
72
69
|
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
}
|
|
70
|
+
//get view scale
|
|
71
|
+
const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
|
|
80
72
|
|
|
81
73
|
//nb categories - used for radar and agepyramid
|
|
82
74
|
const nbCat = Object.entries(this.color).length
|
|
83
75
|
|
|
84
|
-
//draw in geo coordinates
|
|
85
|
-
cg.setCanvasTransform()
|
|
86
|
-
|
|
87
76
|
//draw calls
|
|
88
77
|
for (let cell of cells) {
|
|
78
|
+
|
|
89
79
|
//size
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
//size - in geo
|
|
93
|
-
/** @type {number} */
|
|
94
|
-
const sG = s_(cell[this.sizeCol], r, stat, zf)
|
|
80
|
+
const sG = this.size ? this.size(cell, resolution, z, viewScale) : resolution
|
|
81
|
+
if (!sG) continue
|
|
95
82
|
|
|
96
83
|
//get offset
|
|
97
|
-
const offset = this.offset(cell,
|
|
84
|
+
const offset = this.offset(cell, resolution, z)
|
|
98
85
|
|
|
99
86
|
//get symbol type
|
|
100
|
-
const type_ = this.type ? this.type(cell) : 'flag'
|
|
87
|
+
const type_ = this.type ? this.type(cell, resolution, z, viewScale) : 'flag'
|
|
101
88
|
|
|
102
89
|
//compute center position
|
|
103
|
-
const xc = cell.x + offset.dx + (type_ === 'agepyramid' ? 0 :
|
|
104
|
-
const yc = cell.y + offset.dy + (type_ === 'agepyramid' ? 0 :
|
|
90
|
+
const xc = cell.x + offset.dx + (type_ === 'agepyramid' ? 0 : resolution * 0.5)
|
|
91
|
+
const yc = cell.y + offset.dy + (type_ === 'agepyramid' ? 0 : resolution * 0.5)
|
|
105
92
|
|
|
106
93
|
//compute offset angle, when relevant
|
|
107
|
-
const offAng = this.offsetAngle ? (this.offsetAngle(cell,
|
|
94
|
+
const offAng = this.offsetAngle ? (this.offsetAngle(cell, resolution, z, viewScale) * Math.PI) / 180 : 0
|
|
108
95
|
|
|
109
96
|
if (type_ === 'agepyramid' || type_ === 'radar' || type_ === 'halftone') {
|
|
110
97
|
//get cell category max value
|
|
@@ -117,22 +104,22 @@ export class CompositionStyle extends Style {
|
|
|
117
104
|
//cumul
|
|
118
105
|
let cumul = 0
|
|
119
106
|
if (type_ === 'agepyramid' && this.agePyramidHeight)
|
|
120
|
-
cumul = (
|
|
107
|
+
cumul = (resolution - this.agePyramidHeight(cell, resolution, z, viewScale)) / 2
|
|
121
108
|
if (type_ === 'radar' || type_ === 'halftone') cumul = Math.PI / 2 + offAng
|
|
122
109
|
|
|
123
110
|
//compute the increment, which is the value to increment the cumul for each category
|
|
124
111
|
const incr =
|
|
125
112
|
type_ === 'agepyramid'
|
|
126
|
-
? (this.agePyramidHeight ? this.agePyramidHeight(cell,
|
|
113
|
+
? (this.agePyramidHeight ? this.agePyramidHeight(cell, resolution, z, viewScale) : resolution) / nbCat
|
|
127
114
|
: type_ === 'radar' || type_ === 'halftone'
|
|
128
|
-
|
|
129
|
-
|
|
115
|
+
? (2 * Math.PI) / nbCat
|
|
116
|
+
: undefined
|
|
130
117
|
if (incr === undefined) throw new Error('Unexpected symbol type:' + type_)
|
|
131
118
|
|
|
132
119
|
for (let [column, color] of Object.entries(this.color)) {
|
|
133
120
|
if (type_ === 'agepyramid') {
|
|
134
121
|
//set category color
|
|
135
|
-
|
|
122
|
+
geoCanvas.ctx.fillStyle = color
|
|
136
123
|
|
|
137
124
|
//get category value
|
|
138
125
|
const val = cell[column]
|
|
@@ -142,34 +129,34 @@ export class CompositionStyle extends Style {
|
|
|
142
129
|
const wG = (sG * val) / maxVal
|
|
143
130
|
|
|
144
131
|
//draw bar
|
|
145
|
-
|
|
132
|
+
geoCanvas.ctx.fillRect(xc + (resolution - wG) / 2, yc + cumul, wG, incr)
|
|
146
133
|
|
|
147
134
|
//next height
|
|
148
135
|
cumul += incr
|
|
149
136
|
} else if (type_ === 'radar') {
|
|
150
137
|
//set category color
|
|
151
|
-
|
|
138
|
+
geoCanvas.ctx.fillStyle = color
|
|
152
139
|
|
|
153
140
|
//get categroy value
|
|
154
141
|
const val = cell[column]
|
|
155
142
|
|
|
156
143
|
//compute category radius - in geo
|
|
157
144
|
/** @type {number} */
|
|
158
|
-
//const rG = this.radius(val, r, stat, cellStat,
|
|
145
|
+
//const rG = this.radius(val, r, stat, cellStat, z)
|
|
159
146
|
const rG = (sG / 2) * Math.sqrt(val / maxVal)
|
|
160
147
|
|
|
161
148
|
//draw angular sector
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
149
|
+
geoCanvas.ctx.beginPath()
|
|
150
|
+
geoCanvas.ctx.moveTo(xc, yc)
|
|
151
|
+
geoCanvas.ctx.arc(xc, yc, rG, cumul - incr, cumul)
|
|
152
|
+
geoCanvas.ctx.lineTo(xc, yc)
|
|
153
|
+
geoCanvas.ctx.fill()
|
|
167
154
|
|
|
168
155
|
//next angular sector
|
|
169
156
|
cumul += incr
|
|
170
157
|
} else if (type_ === 'halftone') {
|
|
171
158
|
//set category color
|
|
172
|
-
|
|
159
|
+
geoCanvas.ctx.fillStyle = color
|
|
173
160
|
|
|
174
161
|
//get categroy value
|
|
175
162
|
const val = cell[column]
|
|
@@ -179,15 +166,15 @@ export class CompositionStyle extends Style {
|
|
|
179
166
|
const rG = sG * 0.333 * Math.sqrt(val / maxVal)
|
|
180
167
|
|
|
181
168
|
//draw circle
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
xc +
|
|
185
|
-
yc +
|
|
169
|
+
geoCanvas.ctx.beginPath()
|
|
170
|
+
geoCanvas.ctx.arc(
|
|
171
|
+
xc + resolution * 0.25 * Math.cos(cumul),
|
|
172
|
+
yc + resolution * 0.25 * Math.sin(cumul),
|
|
186
173
|
rG,
|
|
187
174
|
0,
|
|
188
175
|
2 * Math.PI
|
|
189
176
|
)
|
|
190
|
-
|
|
177
|
+
geoCanvas.ctx.fill()
|
|
191
178
|
|
|
192
179
|
//next angular sector
|
|
193
180
|
cumul += incr
|
|
@@ -207,8 +194,8 @@ export class CompositionStyle extends Style {
|
|
|
207
194
|
|
|
208
195
|
//draw decomposition symbol
|
|
209
196
|
let cumul = 0
|
|
210
|
-
const d =
|
|
211
|
-
const ori = this.stripesOrientation(cell,
|
|
197
|
+
const d = resolution * (1 - sG / resolution) * 0.5
|
|
198
|
+
const ori = this.stripesOrientation(cell, resolution, z, viewScale)
|
|
212
199
|
|
|
213
200
|
for (let [column, color] of Object.entries(this.color)) {
|
|
214
201
|
//get share
|
|
@@ -216,14 +203,14 @@ export class CompositionStyle extends Style {
|
|
|
216
203
|
if (!share || isNaN(share)) continue
|
|
217
204
|
|
|
218
205
|
//set color
|
|
219
|
-
|
|
206
|
+
geoCanvas.ctx.fillStyle = color
|
|
220
207
|
|
|
221
208
|
//draw symbol part
|
|
222
209
|
if (type_ === 'flag') {
|
|
223
210
|
//draw flag stripe
|
|
224
211
|
if (ori == 0) {
|
|
225
212
|
//horizontal
|
|
226
|
-
|
|
213
|
+
geoCanvas.ctx.fillRect(
|
|
227
214
|
cell.x + d + offset.dx,
|
|
228
215
|
cell.y + d + cumul * sG + offset.dy,
|
|
229
216
|
sG,
|
|
@@ -231,7 +218,7 @@ export class CompositionStyle extends Style {
|
|
|
231
218
|
)
|
|
232
219
|
} else {
|
|
233
220
|
//vertical
|
|
234
|
-
|
|
221
|
+
geoCanvas.ctx.fillRect(
|
|
235
222
|
cell.x + d + cumul * sG + offset.dx,
|
|
236
223
|
cell.y + d + offset.dy,
|
|
237
224
|
share * sG,
|
|
@@ -246,11 +233,11 @@ export class CompositionStyle extends Style {
|
|
|
246
233
|
const a2 = (cumul + share) * 2 * Math.PI
|
|
247
234
|
|
|
248
235
|
//draw
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
236
|
+
geoCanvas.ctx.beginPath()
|
|
237
|
+
geoCanvas.ctx.moveTo(xc, yc)
|
|
238
|
+
geoCanvas.ctx.arc(xc, yc, sG * 0.5, a1 + offAng, a2 + offAng)
|
|
252
239
|
if (this.pieChartInternalRadiusFactor)
|
|
253
|
-
|
|
240
|
+
geoCanvas.ctx.arc(
|
|
254
241
|
xc,
|
|
255
242
|
yc,
|
|
256
243
|
sG * 0.5 * this.pieChartInternalRadiusFactor,
|
|
@@ -258,30 +245,30 @@ export class CompositionStyle extends Style {
|
|
|
258
245
|
a2 + offAng,
|
|
259
246
|
true
|
|
260
247
|
)
|
|
261
|
-
|
|
262
|
-
|
|
248
|
+
geoCanvas.ctx.closePath()
|
|
249
|
+
geoCanvas.ctx.fill()
|
|
263
250
|
} else if (type_ === 'ring') {
|
|
264
251
|
//draw ring
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
252
|
+
geoCanvas.ctx.beginPath()
|
|
253
|
+
geoCanvas.ctx.arc(xc, yc, Math.sqrt(1 - cumul) * sG * 0.5, 0, 2 * Math.PI)
|
|
254
|
+
geoCanvas.ctx.fill()
|
|
268
255
|
} else if (type_ === 'segment') {
|
|
269
256
|
//draw segment sections
|
|
270
|
-
const wG = (sG * sG) /
|
|
257
|
+
const wG = (sG * sG) / resolution
|
|
271
258
|
if (ori == 0) {
|
|
272
259
|
//horizontal
|
|
273
|
-
|
|
260
|
+
geoCanvas.ctx.fillRect(
|
|
274
261
|
cell.x + offset.dx,
|
|
275
|
-
cell.y + (
|
|
276
|
-
|
|
262
|
+
cell.y + (resolution - wG) / 2 + cumul * wG + offset.dy,
|
|
263
|
+
resolution,
|
|
277
264
|
share * wG
|
|
278
265
|
)
|
|
279
266
|
} else {
|
|
280
267
|
//vertical
|
|
281
|
-
|
|
282
|
-
cell.x + cumul *
|
|
283
|
-
cell.y + (
|
|
284
|
-
share *
|
|
268
|
+
geoCanvas.ctx.fillRect(
|
|
269
|
+
cell.x + cumul * resolution + offset.dx,
|
|
270
|
+
cell.y + (resolution - wG) / 2 + offset.dy,
|
|
271
|
+
share * resolution,
|
|
285
272
|
wG
|
|
286
273
|
)
|
|
287
274
|
}
|
|
@@ -295,6 +282,6 @@ export class CompositionStyle extends Style {
|
|
|
295
282
|
}
|
|
296
283
|
|
|
297
284
|
//update legends
|
|
298
|
-
this.updateLegends({ style: this,
|
|
285
|
+
this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
|
|
299
286
|
}
|
|
300
287
|
}
|
|
@@ -13,42 +13,8 @@ export class ContourStyle extends SideStyle {
|
|
|
13
13
|
/** @param {object} opts */
|
|
14
14
|
constructor(opts) {
|
|
15
15
|
super(opts)
|
|
16
|
-
opts = opts || {}
|
|
16
|
+
//opts = opts || {}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
//opts.interval = opts.interval || 100
|
|
20
|
-
|
|
21
|
-
/** @type {Array.<number>} */
|
|
22
|
-
opts.breaks = opts.breaks || [100, 1000, 10000, 100000, 1000000]
|
|
23
|
-
|
|
24
|
-
/** @type {function(Side,number,number):string} */
|
|
25
|
-
opts.width = opts.width || (() => 1) //(s, r, zf) => ...
|
|
26
|
-
|
|
27
|
-
/** @type {function(Side,number,number):string} */
|
|
28
|
-
opts.color = opts.color || (() => '#E7A935') //(s, r, zf) => ...
|
|
29
|
-
|
|
30
|
-
//override method for contour drawing
|
|
31
|
-
|
|
32
|
-
const getClass = function (v) {
|
|
33
|
-
if (v == undefined) return 0
|
|
34
|
-
for (let i = 0; i < opts.breaks.length; i++) if (v < opts.breaks[i]) return i
|
|
35
|
-
return opts.breaks.length
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
this.value = (v1, v2, r, s, zf) => {
|
|
39
|
-
//if (!v1 || !v2) return 0
|
|
40
|
-
return Math.abs(getClass(v2) - getClass(v1))
|
|
41
|
-
|
|
42
|
-
//check if v1 - v2 cross a contour line
|
|
43
|
-
//const r1 = Math.floor(v1 / opts.interval);
|
|
44
|
-
//const r2 = Math.floor(v2 / opts.interval);
|
|
45
|
-
//return Math.abs(r2 - r1);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
//same color for all
|
|
49
|
-
this.color = (side, r, s, zf) => (side.value ? opts.color(side, r, zf) : undefined)
|
|
50
|
-
|
|
51
|
-
//width: multiple of
|
|
52
|
-
this.width = (side, r, s, zf) => side.value * zf * opts.width(side, r, zf)
|
|
18
|
+
//TODO
|
|
53
19
|
}
|
|
54
20
|
}
|