gridviz 3.0.34 → 3.0.36
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 +129 -129
- package/dist/gridviz.js +17487 -17471
- package/dist/gridviz.min.js +2 -2
- package/package.json +73 -73
- package/src/button/Button.js +67 -67
- package/src/button/FullscreenButton.js +121 -121
- package/src/button/ZoomButtons.js +103 -103
- package/src/core/Dataset.js +154 -154
- package/src/core/Drawable.js +78 -78
- package/src/core/GeoCanvas.js +443 -443
- package/src/core/Layer.js +23 -23
- package/src/core/Legend.js +108 -108
- package/src/core/Map.js +568 -552
- package/src/core/MultiResolutionDataset.js +93 -93
- package/src/core/Style.js +98 -98
- package/src/core/Tooltip.js +182 -182
- package/src/dataset/COGeo.js +25 -25
- package/src/dataset/CSVGrid.js +112 -112
- package/src/dataset/GeoTIFF.js +122 -122
- package/src/dataset/JSGrid.js +55 -55
- package/src/dataset/TiledGrid.js +302 -302
- package/src/dataset/worker.js +18 -18
- package/src/index.js +79 -79
- package/src/layer/BackgroundLayer.js +173 -173
- package/src/layer/BackgroundLayerImage.js +81 -81
- package/src/layer/BackgroundLayerWMS.js +104 -104
- package/src/layer/GeoJSONLayer.js +276 -276
- package/src/layer/GridLayer.js +166 -166
- package/src/layer/LabelLayer.js +194 -194
- package/src/legend/ColorCategoryLegend.js +109 -109
- package/src/legend/ColorDiscreteLegend.js +100 -100
- package/src/legend/ColorLegend.js +115 -115
- package/src/legend/OrientationLegend.js +89 -89
- package/src/legend/SizeLegend.js +224 -224
- package/src/legend/TernaryLegend.js +302 -302
- package/src/style/CompositionStyle.js +291 -291
- package/src/style/DotDensityStyle.js +133 -133
- package/src/style/ImageStyle.js +100 -100
- package/src/style/Interpolator.js +258 -258
- package/src/style/IsoFenceStyle.js +217 -217
- package/src/style/JoyPlotStyle.js +180 -180
- package/src/style/LegoStyle.js +132 -132
- package/src/style/MosaicStyle.js +99 -99
- package/src/style/NinjaStarStyle.js +102 -102
- package/src/style/PillarStyle.js +207 -207
- package/src/style/SegmentStyle.js +105 -105
- package/src/style/ShadingRayStyle.js +280 -280
- package/src/style/ShadingStyle.js +72 -72
- package/src/style/ShapeColorSizeStyle.js +147 -147
- package/src/style/SideCategoryStyle.js +112 -112
- package/src/style/SideStyle.js +217 -217
- package/src/style/SideTanakaStyle.js +154 -154
- package/src/style/SquareColorCategoryWebGLStyle.js +307 -294
- package/src/style/SquareColorWebGLStyle.js +126 -126
- package/src/style/StrokeStyle.js +135 -135
- package/src/style/TextStyle.js +110 -110
- package/src/style/TimeSeriesStyle.js +222 -222
- package/src/utils/WebGLRectangleColoring.js +106 -106
- package/src/utils/WebGLSquareColoring.js +118 -118
- package/src/utils/WebGLSquareColoringAdvanced.js +227 -227
- package/src/utils/WebGLSquareColoringKS.js +153 -153
- package/src/utils/scale.js +255 -255
- package/src/utils/stretching.js +120 -120
- package/src/utils/ternary.js +208 -208
- package/src/utils/utils.js +125 -125
- package/src/utils/webGLUtils.js +73 -73
- package/src/utils/zoomUtils.js +142 -142
package/src/utils/stretching.js
CHANGED
|
@@ -1,120 +1,120 @@
|
|
|
1
|
-
//@ts-check
|
|
2
|
-
'use strict'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @module utils
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
//TODO invert for circular
|
|
9
|
-
//TODO use Math.sqrt
|
|
10
|
-
//TODO validate
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Some function [0,1]->[0,1] to stretch range of values.
|
|
14
|
-
* @see https://github.com/eurostat/gridviz/blob/master/docs/reference.md#stretching
|
|
15
|
-
* @see https://observablehq.com/@jgaffuri/stretching
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
//identity function
|
|
19
|
-
const identity = (t) => t
|
|
20
|
-
identity.invert = identity
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @param {number} base
|
|
24
|
-
* @returns {function(number):number}
|
|
25
|
-
*/
|
|
26
|
-
export const exponentialScale = (base = 3) => {
|
|
27
|
-
if (base == 0) return identity
|
|
28
|
-
const a = Math.exp(base) - 1
|
|
29
|
-
const f = (t) => (Math.exp(t * base) - 1) / a
|
|
30
|
-
f.invert = (t) => Math.log(a * t + 1) / base
|
|
31
|
-
return f
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @param {number} base
|
|
36
|
-
* @returns {function(number):number}
|
|
37
|
-
*/
|
|
38
|
-
export const logarithmicScale = (base = 3) => {
|
|
39
|
-
if (base == 0) return identity
|
|
40
|
-
const a = Math.exp(base),
|
|
41
|
-
b = 1 - a
|
|
42
|
-
const f = (t) => 1 - Math.log(a + t * b) / base
|
|
43
|
-
f.invert = (t) => (Math.exp((1 - t) * base) - a) / b
|
|
44
|
-
return f
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* @param {number} exponent
|
|
49
|
-
* @returns {function(number):number}
|
|
50
|
-
*/
|
|
51
|
-
export const powerScale = (exponent = 3) => {
|
|
52
|
-
if (exponent == 1) return identity
|
|
53
|
-
//TODO if (exponent == 0.5) return Math.sqrt
|
|
54
|
-
const f = (t) => Math.pow(t, exponent)
|
|
55
|
-
const a = 1 / exponent
|
|
56
|
-
f.invert = (t) => Math.pow(t, a)
|
|
57
|
-
return f
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* @param {number} exponent
|
|
62
|
-
* @returns {function(number):number}
|
|
63
|
-
*/
|
|
64
|
-
export const powerInverseScale = (exponent = 3) => {
|
|
65
|
-
if (exponent == 1) return identity
|
|
66
|
-
//TODO if (exponent == 2) return t => 1 - Math.sqrt(1 - t)
|
|
67
|
-
const a = 1 / exponent
|
|
68
|
-
const f = (t) => 1 - Math.pow(1 - t, a)
|
|
69
|
-
f.invert = (t) => 1 - Math.pow(1 - t, exponent)
|
|
70
|
-
return f
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* @param {number} circularity
|
|
75
|
-
* @returns {function(number):number}
|
|
76
|
-
*/
|
|
77
|
-
export const circularScale = (circularity = 0.8) => {
|
|
78
|
-
if (circularity == 0) return identity
|
|
79
|
-
if (circularity == 1) return (t) => Math.sqrt(t * (2 - t))
|
|
80
|
-
else {
|
|
81
|
-
const a = circularity / (1 - circularity)
|
|
82
|
-
return (t) => Math.sqrt(1 / (a * a) + t * (2 / a + 2 - t)) - 1 / a
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* @param {number} circularity
|
|
88
|
-
* @returns {function(number):number}
|
|
89
|
-
*/
|
|
90
|
-
export const circularInverseScale = (circularity = 0.8) => {
|
|
91
|
-
if (circularity == 0) return identity
|
|
92
|
-
const f = circularScale(circularity)
|
|
93
|
-
return (t) => 1 - f(1 - t)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
//test
|
|
97
|
-
/*
|
|
98
|
-
const test = (f, fun, a, err = 1e-12) => {
|
|
99
|
-
for (let t = 0; t <= 1; t += 1 / 50) {
|
|
100
|
-
const er = t - f.invert(f(t))
|
|
101
|
-
if (Math.abs(er) < err) continue
|
|
102
|
-
console.log(fun, a, er)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
for (let fun of [powerScale, powerInverseScale])
|
|
107
|
-
for (let exp = -30; exp <= 50; exp += 1) {
|
|
108
|
-
if (exp == 0) continue
|
|
109
|
-
const f = fun(exp)
|
|
110
|
-
test(f, fun, exp)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
for (let fun of [exponentialScale, logarithmicScale])
|
|
115
|
-
for (let base = -20; base <= 20; base += 1) {
|
|
116
|
-
//if (exp == 0) continue
|
|
117
|
-
const f = fun(base)
|
|
118
|
-
test(f, fun, base, 1e-10)
|
|
119
|
-
}
|
|
120
|
-
*/
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @module utils
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
//TODO invert for circular
|
|
9
|
+
//TODO use Math.sqrt
|
|
10
|
+
//TODO validate
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Some function [0,1]->[0,1] to stretch range of values.
|
|
14
|
+
* @see https://github.com/eurostat/gridviz/blob/master/docs/reference.md#stretching
|
|
15
|
+
* @see https://observablehq.com/@jgaffuri/stretching
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
//identity function
|
|
19
|
+
const identity = (t) => t
|
|
20
|
+
identity.invert = identity
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {number} base
|
|
24
|
+
* @returns {function(number):number}
|
|
25
|
+
*/
|
|
26
|
+
export const exponentialScale = (base = 3) => {
|
|
27
|
+
if (base == 0) return identity
|
|
28
|
+
const a = Math.exp(base) - 1
|
|
29
|
+
const f = (t) => (Math.exp(t * base) - 1) / a
|
|
30
|
+
f.invert = (t) => Math.log(a * t + 1) / base
|
|
31
|
+
return f
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {number} base
|
|
36
|
+
* @returns {function(number):number}
|
|
37
|
+
*/
|
|
38
|
+
export const logarithmicScale = (base = 3) => {
|
|
39
|
+
if (base == 0) return identity
|
|
40
|
+
const a = Math.exp(base),
|
|
41
|
+
b = 1 - a
|
|
42
|
+
const f = (t) => 1 - Math.log(a + t * b) / base
|
|
43
|
+
f.invert = (t) => (Math.exp((1 - t) * base) - a) / b
|
|
44
|
+
return f
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {number} exponent
|
|
49
|
+
* @returns {function(number):number}
|
|
50
|
+
*/
|
|
51
|
+
export const powerScale = (exponent = 3) => {
|
|
52
|
+
if (exponent == 1) return identity
|
|
53
|
+
//TODO if (exponent == 0.5) return Math.sqrt
|
|
54
|
+
const f = (t) => Math.pow(t, exponent)
|
|
55
|
+
const a = 1 / exponent
|
|
56
|
+
f.invert = (t) => Math.pow(t, a)
|
|
57
|
+
return f
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {number} exponent
|
|
62
|
+
* @returns {function(number):number}
|
|
63
|
+
*/
|
|
64
|
+
export const powerInverseScale = (exponent = 3) => {
|
|
65
|
+
if (exponent == 1) return identity
|
|
66
|
+
//TODO if (exponent == 2) return t => 1 - Math.sqrt(1 - t)
|
|
67
|
+
const a = 1 / exponent
|
|
68
|
+
const f = (t) => 1 - Math.pow(1 - t, a)
|
|
69
|
+
f.invert = (t) => 1 - Math.pow(1 - t, exponent)
|
|
70
|
+
return f
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {number} circularity
|
|
75
|
+
* @returns {function(number):number}
|
|
76
|
+
*/
|
|
77
|
+
export const circularScale = (circularity = 0.8) => {
|
|
78
|
+
if (circularity == 0) return identity
|
|
79
|
+
if (circularity == 1) return (t) => Math.sqrt(t * (2 - t))
|
|
80
|
+
else {
|
|
81
|
+
const a = circularity / (1 - circularity)
|
|
82
|
+
return (t) => Math.sqrt(1 / (a * a) + t * (2 / a + 2 - t)) - 1 / a
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {number} circularity
|
|
88
|
+
* @returns {function(number):number}
|
|
89
|
+
*/
|
|
90
|
+
export const circularInverseScale = (circularity = 0.8) => {
|
|
91
|
+
if (circularity == 0) return identity
|
|
92
|
+
const f = circularScale(circularity)
|
|
93
|
+
return (t) => 1 - f(1 - t)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
//test
|
|
97
|
+
/*
|
|
98
|
+
const test = (f, fun, a, err = 1e-12) => {
|
|
99
|
+
for (let t = 0; t <= 1; t += 1 / 50) {
|
|
100
|
+
const er = t - f.invert(f(t))
|
|
101
|
+
if (Math.abs(er) < err) continue
|
|
102
|
+
console.log(fun, a, er)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (let fun of [powerScale, powerInverseScale])
|
|
107
|
+
for (let exp = -30; exp <= 50; exp += 1) {
|
|
108
|
+
if (exp == 0) continue
|
|
109
|
+
const f = fun(exp)
|
|
110
|
+
test(f, fun, exp)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
for (let fun of [exponentialScale, logarithmicScale])
|
|
115
|
+
for (let base = -20; base <= 20; base += 1) {
|
|
116
|
+
//if (exp == 0) continue
|
|
117
|
+
const f = fun(base)
|
|
118
|
+
test(f, fun, base, 1e-10)
|
|
119
|
+
}
|
|
120
|
+
*/
|
package/src/utils/ternary.js
CHANGED
|
@@ -1,208 +1,208 @@
|
|
|
1
|
-
//@ts-check
|
|
2
|
-
'use strict'
|
|
3
|
-
|
|
4
|
-
import { interpolateLab } from 'd3-interpolate'
|
|
5
|
-
|
|
6
|
-
export const ternaryClassifier = (properties, totalFunction, opts = {}) => {
|
|
7
|
-
//the three properties
|
|
8
|
-
const p0 = properties[0],
|
|
9
|
-
p1 = properties[1],
|
|
10
|
-
p2 = properties[2]
|
|
11
|
-
|
|
12
|
-
//the classifier center point. sum must be equal to 1
|
|
13
|
-
const [c0, c1, c2] = opts.center || [1 / 3, 1 / 3, 1 / 3]
|
|
14
|
-
|
|
15
|
-
//parameter to decide wether to use mixed classes m0, m1, m2.
|
|
16
|
-
const withMixedClasses = opts.withMixedClasses != undefined ? opts.withMixedClasses : true
|
|
17
|
-
|
|
18
|
-
//parameter to decide wether to use a central class, and the size of this central class.
|
|
19
|
-
//set to 0 or undefined for not showing any central class. Set to 1 for a central class that contains the mix classes
|
|
20
|
-
const cc = opts.centerCoefficient ? 1 - opts.centerCoefficient : undefined
|
|
21
|
-
|
|
22
|
-
//the output classifier method
|
|
23
|
-
const fun = (c) => {
|
|
24
|
-
//get total
|
|
25
|
-
const tot = totalFunction(c)
|
|
26
|
-
if (!tot) return undefined
|
|
27
|
-
//compute shares
|
|
28
|
-
const [s0, s1, s2] = [+c[p0] / tot, +c[p1] / tot, +c[p2] / tot]
|
|
29
|
-
|
|
30
|
-
//class 0
|
|
31
|
-
if (s0 >= c0 && s1 <= c1 && s2 <= c2) {
|
|
32
|
-
//central class near class 0
|
|
33
|
-
if (cc != undefined && (s2 - c2) * (c1 - cc * c1) >= (s1 - cc * c1) * (cc * c2 - c2))
|
|
34
|
-
return 'center'
|
|
35
|
-
return '0'
|
|
36
|
-
}
|
|
37
|
-
//class 1
|
|
38
|
-
if (s0 <= c0 && s1 >= c1 && s2 <= c2) {
|
|
39
|
-
//central class near class 1
|
|
40
|
-
if (cc != undefined && (s2 - c2) * (c0 - cc * c0) >= (s0 - cc * c0) * (cc * c2 - c2))
|
|
41
|
-
return 'center'
|
|
42
|
-
return '1'
|
|
43
|
-
}
|
|
44
|
-
//class 2
|
|
45
|
-
if (s0 <= c0 && s1 <= c1 && s2 >= c2) {
|
|
46
|
-
//central class near class 2
|
|
47
|
-
if (cc != undefined && (s1 - c1) * (c0 - cc * c0) >= (s0 - cc * c0) * (cc * c1 - c1))
|
|
48
|
-
return 'center'
|
|
49
|
-
return '2'
|
|
50
|
-
}
|
|
51
|
-
//middle class 0 - intersection class 1 and 2
|
|
52
|
-
if (s0 <= c0 && s1 >= c1 && s2 >= c2) {
|
|
53
|
-
//central class
|
|
54
|
-
if (cc != undefined && s0 > cc * c0) return 'center'
|
|
55
|
-
if (withMixedClasses) return 'm12'
|
|
56
|
-
return s1 > s2 ? '1' : '2'
|
|
57
|
-
}
|
|
58
|
-
//middle class 1 - intersection class 0 and 1
|
|
59
|
-
if (s0 >= c0 && s1 <= c1 && s2 >= c2) {
|
|
60
|
-
//central class
|
|
61
|
-
if (cc != undefined && s1 > cc * c1) return 'center'
|
|
62
|
-
if (withMixedClasses) return 'm02'
|
|
63
|
-
return s0 > s2 ? '0' : '2'
|
|
64
|
-
}
|
|
65
|
-
//middle class 2 - intersection class 0 and 1
|
|
66
|
-
if (s0 >= c0 && s1 >= c1 && s2 <= c2) {
|
|
67
|
-
//central class
|
|
68
|
-
if (cc != undefined && s2 > cc * c2) return 'center'
|
|
69
|
-
if (withMixedClasses) return 'm01'
|
|
70
|
-
return s1 > s0 ? '1' : '0'
|
|
71
|
-
}
|
|
72
|
-
//should not happen
|
|
73
|
-
return 'unknown'
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
//attach information to output function
|
|
77
|
-
fun.center = [c0, c1, c2]
|
|
78
|
-
fun.centerCoefficient = opts.centerCoefficient
|
|
79
|
-
|
|
80
|
-
return fun
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export const ternaryColorClassifier = (properties, totalFunction, colors, opts = {}) => {
|
|
84
|
-
//the three colors
|
|
85
|
-
const [color0, color1, color2] = colors || ['red', 'green', 'blue']
|
|
86
|
-
|
|
87
|
-
//the color interpolation function
|
|
88
|
-
const colorInterpolation = opts.colorInterpolation || interpolateLab
|
|
89
|
-
|
|
90
|
-
//parameter to decide wether to use mixed classes.
|
|
91
|
-
const withMixedClasses = opts.withMixedClasses != undefined ? opts.withMixedClasses : true
|
|
92
|
-
//https://d3js.org/d3-interpolate/color
|
|
93
|
-
const mixColorFunction = (color1, color2) => colorInterpolation(color1, color2)(0.5)
|
|
94
|
-
//the colors corresponding to the mixed classes
|
|
95
|
-
const [mixColor0, mixColor1, mixColor2] =
|
|
96
|
-
opts.mixedColors || withMixedClasses
|
|
97
|
-
? [
|
|
98
|
-
mixColorFunction(color1, color2),
|
|
99
|
-
mixColorFunction(color0, color2),
|
|
100
|
-
mixColorFunction(color0, color1),
|
|
101
|
-
]
|
|
102
|
-
: []
|
|
103
|
-
|
|
104
|
-
//the central color, used for the central class, if any. The central class is the class of relatively balanced values, around the center point
|
|
105
|
-
const centerColor =
|
|
106
|
-
opts.centerColor || colorInterpolation(mixColorFunction(color0, color1), color2)(0.333)
|
|
107
|
-
|
|
108
|
-
//make classifier
|
|
109
|
-
const classifier = ternaryClassifier(properties, totalFunction, opts)
|
|
110
|
-
|
|
111
|
-
//the output color classifier method
|
|
112
|
-
const fun = (c) => {
|
|
113
|
-
const cla = classifier(c)
|
|
114
|
-
if (cla == '0') return color0
|
|
115
|
-
if (cla == '1') return color1
|
|
116
|
-
if (cla == '2') return color2
|
|
117
|
-
if (cla == 'm12') return mixColor0
|
|
118
|
-
if (cla == 'm02') return mixColor1
|
|
119
|
-
if (cla == 'm01') return mixColor2
|
|
120
|
-
if (cla == 'center') return centerColor
|
|
121
|
-
return opts.defaultColor || 'black'
|
|
122
|
-
}
|
|
123
|
-
fun.center = classifier.center
|
|
124
|
-
fun.centerCoefficient = opts.centerCoefficient
|
|
125
|
-
fun.colors = [color0, color1, color2]
|
|
126
|
-
fun.mixColors = [mixColor0, mixColor1, mixColor2]
|
|
127
|
-
fun.centerColor = centerColor
|
|
128
|
-
fun.classifier = classifier
|
|
129
|
-
|
|
130
|
-
return fun
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// OLD VERSIONS
|
|
137
|
-
|
|
138
|
-
/*
|
|
139
|
-
const orderedIndexesDec = arr => [...arr.keys()].sort((a, b) => arr[b] - arr[a]);
|
|
140
|
-
const orderedIndexesInc = arr => [...arr.keys()].sort((a, b) => arr[a] - arr[b]);
|
|
141
|
-
|
|
142
|
-
export const ternaryClassifier = (properties, totalFunction, opts = {}) => {
|
|
143
|
-
const lowThreshold = opts.lowThreshold || [1 / 3, 1 / 3, 1 / 3]
|
|
144
|
-
const highThreshold = opts.highThreshold || [2 / 3, 2 / 3, 2 / 3]
|
|
145
|
-
const colors = opts.colors || ["red", "green", "blue"]
|
|
146
|
-
const colorInterpolation = opts.colorInterpolation || interpolateLab
|
|
147
|
-
|
|
148
|
-
//https://d3js.org/d3-interpolate/color
|
|
149
|
-
const middleColorFunction = (color1, color2) => colorInterpolation(color1, color2)(0.5)
|
|
150
|
-
const middleColors = opts.middleColors || [middleColorFunction(colors[1], colors[2]), middleColorFunction(colors[0], colors[2]), middleColorFunction(colors[0], colors[1])]
|
|
151
|
-
const centerColor = opts.centerColor || colorInterpolation(middleColors[2], colors[2])(0.333)
|
|
152
|
-
|
|
153
|
-
const high_ = orderedIndexesDec(highThreshold)
|
|
154
|
-
const low_ = orderedIndexesInc(lowThreshold)
|
|
155
|
-
|
|
156
|
-
const p0 = properties[0], p1 = properties[1], p2 = properties[2]
|
|
157
|
-
const fun = c => {
|
|
158
|
-
//get total
|
|
159
|
-
const tot = totalFunction(c)
|
|
160
|
-
if (!tot) return undefined
|
|
161
|
-
//compute shares
|
|
162
|
-
const shares = [+c[p0] / tot, +c[p1] / tot, +c[p2] / tot]
|
|
163
|
-
//return colors
|
|
164
|
-
//start first with the extreme high (triangles): from the larger value (small triangle) to the lower value (large triangle)
|
|
165
|
-
for (let i of high_) if (shares[i] > highThreshold[i]) return colors[i]
|
|
166
|
-
//then draw the extreme low (trapeziums): from the lower value (small trapeziums) to the larger values (large trapezium)
|
|
167
|
-
for (let i of low_) if (shares[i] < lowThreshold[i]) return middleColors[i]
|
|
168
|
-
//else central color
|
|
169
|
-
return centerColor
|
|
170
|
-
}
|
|
171
|
-
fun.lowThreshold = lowThreshold
|
|
172
|
-
fun.highThreshold = highThreshold
|
|
173
|
-
fun.colors = colors
|
|
174
|
-
fun.middleColors = middleColors
|
|
175
|
-
fun.centerColor = centerColor
|
|
176
|
-
fun.lowIndex = low_
|
|
177
|
-
fun.highIndex = high_
|
|
178
|
-
return fun
|
|
179
|
-
}
|
|
180
|
-
*/
|
|
181
|
-
|
|
182
|
-
/*
|
|
183
|
-
export const ternaryClassifier3 = (properties, totalFunction, opts = {}) => {
|
|
184
|
-
const [a01, a12, a20] = opts.thresholds || [1/3, 1/3, 1/3]
|
|
185
|
-
const [c0, c1, c2] = opts.colors || ["red", "green", "blue"]
|
|
186
|
-
const centerColor = opts.centerColor || "gray"
|
|
187
|
-
|
|
188
|
-
const fff = a => a == 1 ? Infinity : a / (1 - a)
|
|
189
|
-
const c01 = fff(a01), c12 = fff(a12), c20 = fff(a20)
|
|
190
|
-
|
|
191
|
-
const p0 = properties[0], p1 = properties[1], p2 = properties[2]
|
|
192
|
-
const fun = c => {
|
|
193
|
-
//get total
|
|
194
|
-
const tot = totalFunction(c)
|
|
195
|
-
if (!tot) return undefined
|
|
196
|
-
//compute shares
|
|
197
|
-
const [s0, s1, s2] = [+c[p0] / tot, +c[p1] / tot, +c[p2] / tot]
|
|
198
|
-
//return colors
|
|
199
|
-
if (s0 * c01 > s1 && s0 > s2 * c20) return c0
|
|
200
|
-
else if (s1 > s0 * c01 && s1 * c12 > s2) return c1
|
|
201
|
-
else if (s2 * c20 > s0 && s2 > s1 * c12) return c2
|
|
202
|
-
else return centerColor
|
|
203
|
-
}
|
|
204
|
-
fun.colors = [c0, c1, c2]
|
|
205
|
-
fun.centerColor = centerColor
|
|
206
|
-
return fun
|
|
207
|
-
}
|
|
208
|
-
*/
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
import { interpolateLab } from 'd3-interpolate'
|
|
5
|
+
|
|
6
|
+
export const ternaryClassifier = (properties, totalFunction, opts = {}) => {
|
|
7
|
+
//the three properties
|
|
8
|
+
const p0 = properties[0],
|
|
9
|
+
p1 = properties[1],
|
|
10
|
+
p2 = properties[2]
|
|
11
|
+
|
|
12
|
+
//the classifier center point. sum must be equal to 1
|
|
13
|
+
const [c0, c1, c2] = opts.center || [1 / 3, 1 / 3, 1 / 3]
|
|
14
|
+
|
|
15
|
+
//parameter to decide wether to use mixed classes m0, m1, m2.
|
|
16
|
+
const withMixedClasses = opts.withMixedClasses != undefined ? opts.withMixedClasses : true
|
|
17
|
+
|
|
18
|
+
//parameter to decide wether to use a central class, and the size of this central class.
|
|
19
|
+
//set to 0 or undefined for not showing any central class. Set to 1 for a central class that contains the mix classes
|
|
20
|
+
const cc = opts.centerCoefficient ? 1 - opts.centerCoefficient : undefined
|
|
21
|
+
|
|
22
|
+
//the output classifier method
|
|
23
|
+
const fun = (c) => {
|
|
24
|
+
//get total
|
|
25
|
+
const tot = totalFunction(c)
|
|
26
|
+
if (!tot) return undefined
|
|
27
|
+
//compute shares
|
|
28
|
+
const [s0, s1, s2] = [+c[p0] / tot, +c[p1] / tot, +c[p2] / tot]
|
|
29
|
+
|
|
30
|
+
//class 0
|
|
31
|
+
if (s0 >= c0 && s1 <= c1 && s2 <= c2) {
|
|
32
|
+
//central class near class 0
|
|
33
|
+
if (cc != undefined && (s2 - c2) * (c1 - cc * c1) >= (s1 - cc * c1) * (cc * c2 - c2))
|
|
34
|
+
return 'center'
|
|
35
|
+
return '0'
|
|
36
|
+
}
|
|
37
|
+
//class 1
|
|
38
|
+
if (s0 <= c0 && s1 >= c1 && s2 <= c2) {
|
|
39
|
+
//central class near class 1
|
|
40
|
+
if (cc != undefined && (s2 - c2) * (c0 - cc * c0) >= (s0 - cc * c0) * (cc * c2 - c2))
|
|
41
|
+
return 'center'
|
|
42
|
+
return '1'
|
|
43
|
+
}
|
|
44
|
+
//class 2
|
|
45
|
+
if (s0 <= c0 && s1 <= c1 && s2 >= c2) {
|
|
46
|
+
//central class near class 2
|
|
47
|
+
if (cc != undefined && (s1 - c1) * (c0 - cc * c0) >= (s0 - cc * c0) * (cc * c1 - c1))
|
|
48
|
+
return 'center'
|
|
49
|
+
return '2'
|
|
50
|
+
}
|
|
51
|
+
//middle class 0 - intersection class 1 and 2
|
|
52
|
+
if (s0 <= c0 && s1 >= c1 && s2 >= c2) {
|
|
53
|
+
//central class
|
|
54
|
+
if (cc != undefined && s0 > cc * c0) return 'center'
|
|
55
|
+
if (withMixedClasses) return 'm12'
|
|
56
|
+
return s1 > s2 ? '1' : '2'
|
|
57
|
+
}
|
|
58
|
+
//middle class 1 - intersection class 0 and 1
|
|
59
|
+
if (s0 >= c0 && s1 <= c1 && s2 >= c2) {
|
|
60
|
+
//central class
|
|
61
|
+
if (cc != undefined && s1 > cc * c1) return 'center'
|
|
62
|
+
if (withMixedClasses) return 'm02'
|
|
63
|
+
return s0 > s2 ? '0' : '2'
|
|
64
|
+
}
|
|
65
|
+
//middle class 2 - intersection class 0 and 1
|
|
66
|
+
if (s0 >= c0 && s1 >= c1 && s2 <= c2) {
|
|
67
|
+
//central class
|
|
68
|
+
if (cc != undefined && s2 > cc * c2) return 'center'
|
|
69
|
+
if (withMixedClasses) return 'm01'
|
|
70
|
+
return s1 > s0 ? '1' : '0'
|
|
71
|
+
}
|
|
72
|
+
//should not happen
|
|
73
|
+
return 'unknown'
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//attach information to output function
|
|
77
|
+
fun.center = [c0, c1, c2]
|
|
78
|
+
fun.centerCoefficient = opts.centerCoefficient
|
|
79
|
+
|
|
80
|
+
return fun
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const ternaryColorClassifier = (properties, totalFunction, colors, opts = {}) => {
|
|
84
|
+
//the three colors
|
|
85
|
+
const [color0, color1, color2] = colors || ['red', 'green', 'blue']
|
|
86
|
+
|
|
87
|
+
//the color interpolation function
|
|
88
|
+
const colorInterpolation = opts.colorInterpolation || interpolateLab
|
|
89
|
+
|
|
90
|
+
//parameter to decide wether to use mixed classes.
|
|
91
|
+
const withMixedClasses = opts.withMixedClasses != undefined ? opts.withMixedClasses : true
|
|
92
|
+
//https://d3js.org/d3-interpolate/color
|
|
93
|
+
const mixColorFunction = (color1, color2) => colorInterpolation(color1, color2)(0.5)
|
|
94
|
+
//the colors corresponding to the mixed classes
|
|
95
|
+
const [mixColor0, mixColor1, mixColor2] =
|
|
96
|
+
opts.mixedColors || withMixedClasses
|
|
97
|
+
? [
|
|
98
|
+
mixColorFunction(color1, color2),
|
|
99
|
+
mixColorFunction(color0, color2),
|
|
100
|
+
mixColorFunction(color0, color1),
|
|
101
|
+
]
|
|
102
|
+
: []
|
|
103
|
+
|
|
104
|
+
//the central color, used for the central class, if any. The central class is the class of relatively balanced values, around the center point
|
|
105
|
+
const centerColor =
|
|
106
|
+
opts.centerColor || colorInterpolation(mixColorFunction(color0, color1), color2)(0.333)
|
|
107
|
+
|
|
108
|
+
//make classifier
|
|
109
|
+
const classifier = ternaryClassifier(properties, totalFunction, opts)
|
|
110
|
+
|
|
111
|
+
//the output color classifier method
|
|
112
|
+
const fun = (c) => {
|
|
113
|
+
const cla = classifier(c)
|
|
114
|
+
if (cla == '0') return color0
|
|
115
|
+
if (cla == '1') return color1
|
|
116
|
+
if (cla == '2') return color2
|
|
117
|
+
if (cla == 'm12') return mixColor0
|
|
118
|
+
if (cla == 'm02') return mixColor1
|
|
119
|
+
if (cla == 'm01') return mixColor2
|
|
120
|
+
if (cla == 'center') return centerColor
|
|
121
|
+
return opts.defaultColor || 'black'
|
|
122
|
+
}
|
|
123
|
+
fun.center = classifier.center
|
|
124
|
+
fun.centerCoefficient = opts.centerCoefficient
|
|
125
|
+
fun.colors = [color0, color1, color2]
|
|
126
|
+
fun.mixColors = [mixColor0, mixColor1, mixColor2]
|
|
127
|
+
fun.centerColor = centerColor
|
|
128
|
+
fun.classifier = classifier
|
|
129
|
+
|
|
130
|
+
return fun
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
// OLD VERSIONS
|
|
137
|
+
|
|
138
|
+
/*
|
|
139
|
+
const orderedIndexesDec = arr => [...arr.keys()].sort((a, b) => arr[b] - arr[a]);
|
|
140
|
+
const orderedIndexesInc = arr => [...arr.keys()].sort((a, b) => arr[a] - arr[b]);
|
|
141
|
+
|
|
142
|
+
export const ternaryClassifier = (properties, totalFunction, opts = {}) => {
|
|
143
|
+
const lowThreshold = opts.lowThreshold || [1 / 3, 1 / 3, 1 / 3]
|
|
144
|
+
const highThreshold = opts.highThreshold || [2 / 3, 2 / 3, 2 / 3]
|
|
145
|
+
const colors = opts.colors || ["red", "green", "blue"]
|
|
146
|
+
const colorInterpolation = opts.colorInterpolation || interpolateLab
|
|
147
|
+
|
|
148
|
+
//https://d3js.org/d3-interpolate/color
|
|
149
|
+
const middleColorFunction = (color1, color2) => colorInterpolation(color1, color2)(0.5)
|
|
150
|
+
const middleColors = opts.middleColors || [middleColorFunction(colors[1], colors[2]), middleColorFunction(colors[0], colors[2]), middleColorFunction(colors[0], colors[1])]
|
|
151
|
+
const centerColor = opts.centerColor || colorInterpolation(middleColors[2], colors[2])(0.333)
|
|
152
|
+
|
|
153
|
+
const high_ = orderedIndexesDec(highThreshold)
|
|
154
|
+
const low_ = orderedIndexesInc(lowThreshold)
|
|
155
|
+
|
|
156
|
+
const p0 = properties[0], p1 = properties[1], p2 = properties[2]
|
|
157
|
+
const fun = c => {
|
|
158
|
+
//get total
|
|
159
|
+
const tot = totalFunction(c)
|
|
160
|
+
if (!tot) return undefined
|
|
161
|
+
//compute shares
|
|
162
|
+
const shares = [+c[p0] / tot, +c[p1] / tot, +c[p2] / tot]
|
|
163
|
+
//return colors
|
|
164
|
+
//start first with the extreme high (triangles): from the larger value (small triangle) to the lower value (large triangle)
|
|
165
|
+
for (let i of high_) if (shares[i] > highThreshold[i]) return colors[i]
|
|
166
|
+
//then draw the extreme low (trapeziums): from the lower value (small trapeziums) to the larger values (large trapezium)
|
|
167
|
+
for (let i of low_) if (shares[i] < lowThreshold[i]) return middleColors[i]
|
|
168
|
+
//else central color
|
|
169
|
+
return centerColor
|
|
170
|
+
}
|
|
171
|
+
fun.lowThreshold = lowThreshold
|
|
172
|
+
fun.highThreshold = highThreshold
|
|
173
|
+
fun.colors = colors
|
|
174
|
+
fun.middleColors = middleColors
|
|
175
|
+
fun.centerColor = centerColor
|
|
176
|
+
fun.lowIndex = low_
|
|
177
|
+
fun.highIndex = high_
|
|
178
|
+
return fun
|
|
179
|
+
}
|
|
180
|
+
*/
|
|
181
|
+
|
|
182
|
+
/*
|
|
183
|
+
export const ternaryClassifier3 = (properties, totalFunction, opts = {}) => {
|
|
184
|
+
const [a01, a12, a20] = opts.thresholds || [1/3, 1/3, 1/3]
|
|
185
|
+
const [c0, c1, c2] = opts.colors || ["red", "green", "blue"]
|
|
186
|
+
const centerColor = opts.centerColor || "gray"
|
|
187
|
+
|
|
188
|
+
const fff = a => a == 1 ? Infinity : a / (1 - a)
|
|
189
|
+
const c01 = fff(a01), c12 = fff(a12), c20 = fff(a20)
|
|
190
|
+
|
|
191
|
+
const p0 = properties[0], p1 = properties[1], p2 = properties[2]
|
|
192
|
+
const fun = c => {
|
|
193
|
+
//get total
|
|
194
|
+
const tot = totalFunction(c)
|
|
195
|
+
if (!tot) return undefined
|
|
196
|
+
//compute shares
|
|
197
|
+
const [s0, s1, s2] = [+c[p0] / tot, +c[p1] / tot, +c[p2] / tot]
|
|
198
|
+
//return colors
|
|
199
|
+
if (s0 * c01 > s1 && s0 > s2 * c20) return c0
|
|
200
|
+
else if (s1 > s0 * c01 && s1 * c12 > s2) return c1
|
|
201
|
+
else if (s2 * c20 > s0 && s2 > s1 * c12) return c2
|
|
202
|
+
else return centerColor
|
|
203
|
+
}
|
|
204
|
+
fun.colors = [c0, c1, c2]
|
|
205
|
+
fun.centerColor = centerColor
|
|
206
|
+
return fun
|
|
207
|
+
}
|
|
208
|
+
*/
|