gladly-plot 0.0.3 → 0.0.5
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 +1 -0
- package/package.json +16 -8
- package/src/axes/Axis.js +253 -0
- package/src/{AxisQuantityKindRegistry.js → axes/AxisQuantityKindRegistry.js} +7 -0
- package/src/{AxisRegistry.js → axes/AxisRegistry.js} +48 -0
- package/src/axes/ColorAxisRegistry.js +93 -0
- package/src/{FilterAxisRegistry.js → axes/FilterAxisRegistry.js} +63 -0
- package/src/axes/ZoomController.js +141 -0
- package/src/colorscales/BivariateColorscales.js +205 -0
- package/src/colorscales/ColorscaleRegistry.js +124 -0
- package/src/compute/ComputationRegistry.js +237 -0
- package/src/compute/axisFilter.js +47 -0
- package/src/compute/conv.js +230 -0
- package/src/compute/fft.js +292 -0
- package/src/compute/filter.js +227 -0
- package/src/compute/hist.js +180 -0
- package/src/compute/kde.js +102 -0
- package/src/{Layer.js → core/Layer.js} +4 -3
- package/src/{LayerType.js → core/LayerType.js} +72 -7
- package/src/core/Plot.js +735 -0
- package/src/{Colorbar.js → floats/Colorbar.js} +19 -5
- package/src/floats/Colorbar2d.js +77 -0
- package/src/{Filterbar.js → floats/Filterbar.js} +18 -4
- package/src/{FilterbarFloat.js → floats/Float.js} +17 -30
- package/src/{EpsgUtils.js → geo/EpsgUtils.js} +1 -1
- package/src/index.js +35 -22
- package/src/{ColorbarLayer.js → layers/ColorbarLayer.js} +2 -2
- package/src/layers/ColorbarLayer2d.js +97 -0
- package/src/{FilterbarLayer.js → layers/FilterbarLayer.js} +2 -2
- package/src/layers/HistogramLayer.js +212 -0
- package/src/layers/LinesLayer.js +199 -0
- package/src/layers/PointsLayer.js +114 -0
- package/src/layers/ScatterShared.js +142 -0
- package/src/{TileLayer.js → layers/TileLayer.js} +4 -4
- package/src/Axis.js +0 -48
- package/src/ColorAxisRegistry.js +0 -49
- package/src/ColorscaleRegistry.js +0 -52
- package/src/Float.js +0 -159
- package/src/Plot.js +0 -1068
- package/src/ScatterLayer.js +0 -133
- /package/src/{AxisLink.js → axes/AxisLink.js} +0 -0
- /package/src/{MatplotlibColorscales.js → colorscales/MatplotlibColorscales.js} +0 -0
- /package/src/{Data.js → core/Data.js} +0 -0
- /package/src/{LayerTypeRegistry.js → core/LayerTypeRegistry.js} +0 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// Lines mode uses instanced rendering:
|
|
2
|
+
// - Template: 2 vertices with a_endPoint in {0.0, 1.0} (divisor=0 → interpolates)
|
|
3
|
+
// - Per-segment: a_x0/x1, a_y0/y1, a_v0/v1, a_seg0/seg1 (divisor=1 → constant per instance)
|
|
4
|
+
//
|
|
5
|
+
// Because a_v0 and a_v1 are instanced, they are the same at both template vertices for a given
|
|
6
|
+
// segment, so varyings set from them are constant across the line (no GPU interpolation).
|
|
7
|
+
// Only v_t (from a_endPoint) interpolates, giving the position along the segment.
|
|
8
|
+
//
|
|
9
|
+
// Segment boundary handling: when a_seg0 != a_seg1, collapse both template vertices to
|
|
10
|
+
// (a_x0, a_y0) producing a zero-length degenerate line that the rasterizer discards.
|
|
11
|
+
|
|
12
|
+
import { ScatterLayerTypeBase } from "./ScatterShared.js"
|
|
13
|
+
import { Data } from "../core/Data.js"
|
|
14
|
+
import { registerLayerType } from "../core/LayerTypeRegistry.js"
|
|
15
|
+
|
|
16
|
+
function makeLinesVert(hasFilter) {
|
|
17
|
+
return `
|
|
18
|
+
precision mediump float;
|
|
19
|
+
attribute float a_endPoint;
|
|
20
|
+
attribute float a_x0, a_y0;
|
|
21
|
+
attribute float a_x1, a_y1;
|
|
22
|
+
attribute float a_v0, a_v1;
|
|
23
|
+
attribute float a_v20, a_v21;
|
|
24
|
+
attribute float a_seg0, a_seg1;
|
|
25
|
+
${hasFilter ? 'attribute float a_f0, a_f1;\n uniform vec4 filter_range;' : ''}
|
|
26
|
+
uniform vec2 xDomain;
|
|
27
|
+
uniform vec2 yDomain;
|
|
28
|
+
uniform float xScaleType;
|
|
29
|
+
uniform float yScaleType;
|
|
30
|
+
varying float v_color_start;
|
|
31
|
+
varying float v_color_end;
|
|
32
|
+
varying float v_color2_start;
|
|
33
|
+
varying float v_color2_end;
|
|
34
|
+
varying float v_t;
|
|
35
|
+
void main() {
|
|
36
|
+
float same_seg = abs(a_seg0 - a_seg1) < 0.5 ? 1.0 : 0.0;
|
|
37
|
+
${hasFilter ? 'if (!filter_in_range(filter_range, a_f0) || !filter_in_range(filter_range, a_f1)) same_seg = 0.0;' : ''}
|
|
38
|
+
float t = same_seg * a_endPoint;
|
|
39
|
+
float x = mix(a_x0, a_x1, t);
|
|
40
|
+
float y = mix(a_y0, a_y1, t);
|
|
41
|
+
float nx = normalize_axis(x, xDomain, xScaleType);
|
|
42
|
+
float ny = normalize_axis(y, yDomain, yScaleType);
|
|
43
|
+
gl_Position = vec4(nx * 2.0 - 1.0, ny * 2.0 - 1.0, 0, 1);
|
|
44
|
+
v_color_start = a_v0;
|
|
45
|
+
v_color_end = a_v1;
|
|
46
|
+
v_color2_start = a_v20;
|
|
47
|
+
v_color2_end = a_v21;
|
|
48
|
+
v_t = a_endPoint;
|
|
49
|
+
}
|
|
50
|
+
`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const LINES_FRAG = `
|
|
54
|
+
precision mediump float;
|
|
55
|
+
|
|
56
|
+
uniform int colorscale;
|
|
57
|
+
uniform vec2 color_range;
|
|
58
|
+
uniform float color_scale_type;
|
|
59
|
+
|
|
60
|
+
uniform int colorscale2;
|
|
61
|
+
uniform vec2 color_range2;
|
|
62
|
+
uniform float color_scale_type2;
|
|
63
|
+
|
|
64
|
+
uniform float alphaBlend;
|
|
65
|
+
uniform float u_lineColorMode;
|
|
66
|
+
uniform float u_useSecondColor;
|
|
67
|
+
|
|
68
|
+
varying float v_color_start;
|
|
69
|
+
varying float v_color_end;
|
|
70
|
+
varying float v_color2_start;
|
|
71
|
+
varying float v_color2_end;
|
|
72
|
+
varying float v_t;
|
|
73
|
+
|
|
74
|
+
void main() {
|
|
75
|
+
float value = u_lineColorMode > 0.5
|
|
76
|
+
? (v_t < 0.5 ? v_color_start : v_color_end)
|
|
77
|
+
: mix(v_color_start, v_color_end, v_t);
|
|
78
|
+
|
|
79
|
+
if (u_useSecondColor > 0.5) {
|
|
80
|
+
float value2 = u_lineColorMode > 0.5
|
|
81
|
+
? (v_t < 0.5 ? v_color2_start : v_color2_end)
|
|
82
|
+
: mix(v_color2_start, v_color2_end, v_t);
|
|
83
|
+
|
|
84
|
+
gl_FragColor = map_color_s_2d(
|
|
85
|
+
colorscale, color_range, value, color_scale_type,
|
|
86
|
+
colorscale2, color_range2, value2, color_scale_type2
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (alphaBlend > 0.5) {
|
|
90
|
+
gl_FragColor.a *= gl_FragColor.a;
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
gl_FragColor = map_color_s(colorscale, color_range, value, color_scale_type, alphaBlend);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
`
|
|
97
|
+
|
|
98
|
+
class LinesLayerType extends ScatterLayerTypeBase {
|
|
99
|
+
constructor() {
|
|
100
|
+
super({ name: "lines", vert: makeLinesVert(false), frag: LINES_FRAG })
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
schema(data) {
|
|
104
|
+
const dataProperties = Data.wrap(data).columns()
|
|
105
|
+
return {
|
|
106
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
107
|
+
type: "object",
|
|
108
|
+
properties: {
|
|
109
|
+
...this._commonSchemaProperties(dataProperties),
|
|
110
|
+
lineSegmentIdData: {
|
|
111
|
+
type: "string",
|
|
112
|
+
enum: dataProperties,
|
|
113
|
+
description: "Column for segment IDs; only consecutive points sharing the same ID are connected"
|
|
114
|
+
},
|
|
115
|
+
lineColorMode: {
|
|
116
|
+
type: "string",
|
|
117
|
+
enum: ["gradient", "midpoint"],
|
|
118
|
+
default: "gradient",
|
|
119
|
+
description: "Color mode for lines: gradient interpolates vData linearly; midpoint uses each endpoint's color up to the segment center"
|
|
120
|
+
},
|
|
121
|
+
lineWidth: {
|
|
122
|
+
type: "number",
|
|
123
|
+
default: 1.0,
|
|
124
|
+
minimum: 1,
|
|
125
|
+
description: "Line width in pixels (note: browsers may clamp values above 1)"
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
required: ["xData", "yData", "vData"]
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
_createLayer(parameters, data) {
|
|
133
|
+
const d = Data.wrap(data)
|
|
134
|
+
const { lineSegmentIdData, lineColorMode = "gradient", lineWidth = 1.0 } = parameters
|
|
135
|
+
const { xData, yData, vData, vData2, fData, alphaBlend, xQK, yQK, vQK, vQK2, fQK, srcX, srcY, srcV, srcV2, srcF } =
|
|
136
|
+
this._resolveColorData(parameters, d)
|
|
137
|
+
|
|
138
|
+
const useSecond = vData2 ? 1.0 : 0.0
|
|
139
|
+
const domains = this._buildDomains(d, xData, yData, vData, vData2, xQK, yQK, vQK, vQK2)
|
|
140
|
+
const blendConfig = this._buildBlendConfig(alphaBlend)
|
|
141
|
+
|
|
142
|
+
const N = srcX.length
|
|
143
|
+
const segIds = lineSegmentIdData ? d.getData(lineSegmentIdData) : null
|
|
144
|
+
const zeroSegs = new Float32Array(N - 1)
|
|
145
|
+
const seg0 = segIds ? segIds.subarray(0, N - 1) : zeroSegs
|
|
146
|
+
const seg1 = segIds ? segIds.subarray(1, N) : zeroSegs
|
|
147
|
+
|
|
148
|
+
return [{
|
|
149
|
+
attributes: {
|
|
150
|
+
a_endPoint: new Float32Array([0.0, 1.0]),
|
|
151
|
+
a_x0: srcX.subarray(0, N - 1),
|
|
152
|
+
a_x1: srcX.subarray(1, N),
|
|
153
|
+
a_y0: srcY.subarray(0, N - 1),
|
|
154
|
+
a_y1: srcY.subarray(1, N),
|
|
155
|
+
a_v0: vData ? srcV.subarray(0, N - 1) : new Float32Array(N - 1),
|
|
156
|
+
a_v1: vData ? srcV.subarray(1, N) : new Float32Array(N - 1),
|
|
157
|
+
a_v20: vData2 ? srcV2.subarray(0, N - 1) : new Float32Array(N - 1),
|
|
158
|
+
a_v21: vData2 ? srcV2.subarray(1, N) : new Float32Array(N - 1),
|
|
159
|
+
a_seg0: seg0,
|
|
160
|
+
a_seg1: seg1,
|
|
161
|
+
...(fData ? {
|
|
162
|
+
a_f0: srcF.subarray(0, N - 1),
|
|
163
|
+
a_f1: srcF.subarray(1, N),
|
|
164
|
+
} : {}),
|
|
165
|
+
},
|
|
166
|
+
attributeDivisors: {
|
|
167
|
+
a_x0: 1, a_x1: 1,
|
|
168
|
+
a_y0: 1, a_y1: 1,
|
|
169
|
+
a_v0: 1, a_v1: 1,
|
|
170
|
+
a_v20: 1, a_v21: 1,
|
|
171
|
+
a_seg0: 1, a_seg1: 1,
|
|
172
|
+
...(fData ? { a_f0: 1, a_f1: 1 } : {}),
|
|
173
|
+
},
|
|
174
|
+
uniforms: {
|
|
175
|
+
alphaBlend: alphaBlend ? 1.0 : 0.0,
|
|
176
|
+
u_lineColorMode: lineColorMode === "midpoint" ? 1.0 : 0.0,
|
|
177
|
+
u_useSecondColor: useSecond,
|
|
178
|
+
...(vData ? {} : { colorscale: 0, color_range: [0, 1], color_scale_type: 0.0 }),
|
|
179
|
+
...(vData2 ? {} : { colorscale2: 0, color_range2: [0, 1], color_scale_type2: 0.0 })
|
|
180
|
+
},
|
|
181
|
+
nameMap: this._buildNameMap(vData, vQK, vData2, vQK2, fData, fQK),
|
|
182
|
+
domains,
|
|
183
|
+
primitive: "lines",
|
|
184
|
+
lineWidth,
|
|
185
|
+
vertexCount: 2,
|
|
186
|
+
instanceCount: N - 1,
|
|
187
|
+
blend: blendConfig,
|
|
188
|
+
}]
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
createDrawCommand(regl, layer) {
|
|
192
|
+
const hasFilter = layer.filterAxes.length > 0
|
|
193
|
+
this.vert = makeLinesVert(hasFilter)
|
|
194
|
+
return super.createDrawCommand(regl, layer)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const linesLayerType = new LinesLayerType()
|
|
199
|
+
registerLayerType("lines", linesLayerType)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { ScatterLayerTypeBase } from "./ScatterShared.js"
|
|
2
|
+
import { Data } from "../core/Data.js"
|
|
3
|
+
import { registerLayerType } from "../core/LayerTypeRegistry.js"
|
|
4
|
+
|
|
5
|
+
function makePointsVert(hasFilter) {
|
|
6
|
+
return `
|
|
7
|
+
precision mediump float;
|
|
8
|
+
attribute float x;
|
|
9
|
+
attribute float y;
|
|
10
|
+
attribute float color_data;
|
|
11
|
+
attribute float color_data2;
|
|
12
|
+
${hasFilter ? 'attribute float filter_data;\n uniform vec4 filter_range;' : ''}
|
|
13
|
+
uniform vec2 xDomain;
|
|
14
|
+
uniform vec2 yDomain;
|
|
15
|
+
uniform float xScaleType;
|
|
16
|
+
uniform float yScaleType;
|
|
17
|
+
varying float value;
|
|
18
|
+
varying float value2;
|
|
19
|
+
void main() {
|
|
20
|
+
${hasFilter ? 'if (!filter_in_range(filter_range, filter_data)) { gl_Position = vec4(2.0, 2.0, 2.0, 1.0); return; }' : ''}
|
|
21
|
+
float nx = normalize_axis(x, xDomain, xScaleType);
|
|
22
|
+
float ny = normalize_axis(y, yDomain, yScaleType);
|
|
23
|
+
gl_Position = vec4(nx*2.0-1.0, ny*2.0-1.0, 0, 1);
|
|
24
|
+
gl_PointSize = 4.0;
|
|
25
|
+
value = color_data;
|
|
26
|
+
value2 = color_data2;
|
|
27
|
+
}
|
|
28
|
+
`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const POINTS_FRAG = `
|
|
32
|
+
precision mediump float;
|
|
33
|
+
uniform int colorscale;
|
|
34
|
+
uniform vec2 color_range;
|
|
35
|
+
uniform float color_scale_type;
|
|
36
|
+
|
|
37
|
+
uniform int colorscale2;
|
|
38
|
+
uniform vec2 color_range2;
|
|
39
|
+
uniform float color_scale_type2;
|
|
40
|
+
|
|
41
|
+
uniform float alphaBlend;
|
|
42
|
+
uniform float u_useSecondColor;
|
|
43
|
+
|
|
44
|
+
varying float value;
|
|
45
|
+
varying float value2;
|
|
46
|
+
|
|
47
|
+
void main() {
|
|
48
|
+
if (u_useSecondColor > 0.5) {
|
|
49
|
+
gl_FragColor = map_color_s_2d(
|
|
50
|
+
colorscale, color_range, value, color_scale_type,
|
|
51
|
+
colorscale2, color_range2, value2, color_scale_type2
|
|
52
|
+
);
|
|
53
|
+
if (alphaBlend > 0.5) {
|
|
54
|
+
gl_FragColor.a *= gl_FragColor.a;
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
gl_FragColor = map_color_s(colorscale, color_range, value, color_scale_type, alphaBlend);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
`
|
|
61
|
+
|
|
62
|
+
class PointsLayerType extends ScatterLayerTypeBase {
|
|
63
|
+
constructor() {
|
|
64
|
+
super({ name: "points", vert: makePointsVert(false), frag: POINTS_FRAG })
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
schema(data) {
|
|
68
|
+
const dataProperties = Data.wrap(data).columns()
|
|
69
|
+
return {
|
|
70
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: this._commonSchemaProperties(dataProperties),
|
|
73
|
+
required: ["xData", "yData", "vData"]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_createLayer(parameters, data) {
|
|
78
|
+
const d = Data.wrap(data)
|
|
79
|
+
const { xData, yData, vData, vData2, fData, alphaBlend, xQK, yQK, vQK, vQK2, fQK, srcX, srcY, srcV, srcV2, srcF } =
|
|
80
|
+
this._resolveColorData(parameters, d)
|
|
81
|
+
|
|
82
|
+
const useSecond = vData2 ? 1.0 : 0.0
|
|
83
|
+
const domains = this._buildDomains(d, xData, yData, vData, vData2, xQK, yQK, vQK, vQK2)
|
|
84
|
+
const blendConfig = this._buildBlendConfig(alphaBlend)
|
|
85
|
+
|
|
86
|
+
return [{
|
|
87
|
+
attributes: {
|
|
88
|
+
x: srcX,
|
|
89
|
+
y: srcY,
|
|
90
|
+
color_data: vData ? srcV : new Float32Array(srcX.length),
|
|
91
|
+
color_data2: vData2 ? srcV2 : new Float32Array(srcX.length),
|
|
92
|
+
...(fData ? { filter_data: srcF } : {}),
|
|
93
|
+
},
|
|
94
|
+
uniforms: {
|
|
95
|
+
alphaBlend: alphaBlend ? 1.0 : 0.0,
|
|
96
|
+
u_useSecondColor: useSecond,
|
|
97
|
+
...(vData ? {} : { colorscale: 0, color_range: [0, 1], color_scale_type: 0.0 }),
|
|
98
|
+
...(vData2 ? {} : { colorscale2: 0, color_range2: [0, 1], color_scale_type2: 0.0 })
|
|
99
|
+
},
|
|
100
|
+
domains,
|
|
101
|
+
nameMap: this._buildNameMap(vData, vQK, vData2, vQK2, fData, fQK),
|
|
102
|
+
blend: blendConfig,
|
|
103
|
+
}]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
createDrawCommand(regl, layer) {
|
|
107
|
+
const hasFilter = layer.filterAxes.length > 0
|
|
108
|
+
this.vert = makePointsVert(hasFilter)
|
|
109
|
+
return super.createDrawCommand(regl, layer)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const pointsLayerType = new PointsLayerType()
|
|
114
|
+
registerLayerType("points", pointsLayerType)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { LayerType } from "../core/LayerType.js"
|
|
2
|
+
import { AXES } from "../axes/AxisRegistry.js"
|
|
3
|
+
import { Data } from "../core/Data.js"
|
|
4
|
+
|
|
5
|
+
export class ScatterLayerTypeBase extends LayerType {
|
|
6
|
+
_getAxisConfig(parameters, data) {
|
|
7
|
+
const d = Data.wrap(data)
|
|
8
|
+
const { xData, yData, vData, vData2, fData, xAxis, yAxis } = parameters
|
|
9
|
+
const colorAxisQuantityKinds = [d.getQuantityKind(vData) ?? vData]
|
|
10
|
+
if (vData2) {
|
|
11
|
+
colorAxisQuantityKinds.push(d.getQuantityKind(vData2) ?? vData2)
|
|
12
|
+
}
|
|
13
|
+
const filterAxisQuantityKinds = fData ? [d.getQuantityKind(fData) ?? fData] : []
|
|
14
|
+
return {
|
|
15
|
+
xAxis,
|
|
16
|
+
xAxisQuantityKind: d.getQuantityKind(xData) ?? xData,
|
|
17
|
+
yAxis,
|
|
18
|
+
yAxisQuantityKind: d.getQuantityKind(yData) ?? yData,
|
|
19
|
+
colorAxisQuantityKinds,
|
|
20
|
+
filterAxisQuantityKinds,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
_commonSchemaProperties(dataProperties) {
|
|
25
|
+
return {
|
|
26
|
+
xData: {
|
|
27
|
+
type: "string",
|
|
28
|
+
enum: dataProperties,
|
|
29
|
+
description: "Property name in data object for x coordinates"
|
|
30
|
+
},
|
|
31
|
+
yData: {
|
|
32
|
+
type: "string",
|
|
33
|
+
enum: dataProperties,
|
|
34
|
+
description: "Property name in data object for y coordinates"
|
|
35
|
+
},
|
|
36
|
+
vData: {
|
|
37
|
+
type: "string",
|
|
38
|
+
enum: ["none"].concat(dataProperties),
|
|
39
|
+
description: "Primary property name in data object for color values"
|
|
40
|
+
},
|
|
41
|
+
vData2: {
|
|
42
|
+
type: "string",
|
|
43
|
+
enum: ["none"].concat(dataProperties),
|
|
44
|
+
description: "Optional secondary property name for 2D color mapping"
|
|
45
|
+
},
|
|
46
|
+
fData: {
|
|
47
|
+
type: "string",
|
|
48
|
+
enum: ["none"].concat(dataProperties),
|
|
49
|
+
description: "Optional property name for filter axis values"
|
|
50
|
+
},
|
|
51
|
+
xAxis: {
|
|
52
|
+
type: "string",
|
|
53
|
+
enum: AXES.filter(a => a.includes("x")),
|
|
54
|
+
default: "xaxis_bottom",
|
|
55
|
+
description: "Which x-axis to use for this layer"
|
|
56
|
+
},
|
|
57
|
+
yAxis: {
|
|
58
|
+
type: "string",
|
|
59
|
+
enum: AXES.filter(a => a.includes("y")),
|
|
60
|
+
default: "yaxis_left",
|
|
61
|
+
description: "Which y-axis to use for this layer"
|
|
62
|
+
},
|
|
63
|
+
alphaBlend: {
|
|
64
|
+
type: "boolean",
|
|
65
|
+
default: false,
|
|
66
|
+
description: "Map the normalized color value to alpha so low values fade to transparent"
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_resolveColorData(parameters, d) {
|
|
72
|
+
const { xData, yData, vData: vDataOrig, vData2: vData2Orig, fData: fDataOrig, alphaBlend = false } = parameters
|
|
73
|
+
const vData = vDataOrig == "none" ? null : vDataOrig
|
|
74
|
+
const vData2 = vData2Orig == "none" ? null : vData2Orig
|
|
75
|
+
const fData = fDataOrig == "none" ? null : fDataOrig
|
|
76
|
+
|
|
77
|
+
const xQK = d.getQuantityKind(xData) ?? xData
|
|
78
|
+
const yQK = d.getQuantityKind(yData) ?? yData
|
|
79
|
+
const vQK = vData ? (d.getQuantityKind(vData) ?? vData) : null
|
|
80
|
+
const vQK2 = vData2 ? (d.getQuantityKind(vData2) ?? vData2) : null
|
|
81
|
+
const fQK = fData ? (d.getQuantityKind(fData) ?? fData) : null
|
|
82
|
+
|
|
83
|
+
const srcX = d.getData(xData)
|
|
84
|
+
const srcY = d.getData(yData)
|
|
85
|
+
const srcV = vData ? d.getData(vData) : null
|
|
86
|
+
const srcV2 = vData2 ? d.getData(vData2) : null
|
|
87
|
+
const srcF = fData ? d.getData(fData) : null
|
|
88
|
+
|
|
89
|
+
if (!srcX) throw new Error(`Data column '${xData}' not found`)
|
|
90
|
+
if (!srcY) throw new Error(`Data column '${yData}' not found`)
|
|
91
|
+
if (vData && !srcV) throw new Error(`Data column '${vData}' not found`)
|
|
92
|
+
if (vData2 && !srcV2) throw new Error(`Data column '${vData2}' not found`)
|
|
93
|
+
if (fData && !srcF) throw new Error(`Data column '${fData}' not found`)
|
|
94
|
+
|
|
95
|
+
return { xData, yData, vData, vData2, fData, alphaBlend, xQK, yQK, vQK, vQK2, fQK, srcX, srcY, srcV, srcV2, srcF }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
_buildDomains(d, xData, yData, vData, vData2, xQK, yQK, vQK, vQK2) {
|
|
99
|
+
const domains = {}
|
|
100
|
+
|
|
101
|
+
const xDomain = d.getDomain(xData)
|
|
102
|
+
if (xDomain) domains[xQK] = xDomain
|
|
103
|
+
|
|
104
|
+
const yDomain = d.getDomain(yData)
|
|
105
|
+
if (yDomain) domains[yQK] = yDomain
|
|
106
|
+
|
|
107
|
+
if (vData) {
|
|
108
|
+
const vDomain = d.getDomain(vData)
|
|
109
|
+
if (vDomain) domains[vQK] = vDomain
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (vData2) {
|
|
113
|
+
const vDomain2 = d.getDomain(vData2)
|
|
114
|
+
if (vDomain2) domains[vQK2] = vDomain2
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return domains
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
_buildNameMap(vData, vQK, vData2, vQK2, fData, fQK) {
|
|
121
|
+
return {
|
|
122
|
+
...(vData ? {
|
|
123
|
+
[`colorscale_${vQK}`]: 'colorscale',
|
|
124
|
+
[`color_range_${vQK}`]: 'color_range',
|
|
125
|
+
[`color_scale_type_${vQK}`]: 'color_scale_type',
|
|
126
|
+
} : {}),
|
|
127
|
+
...(vData2 ? {
|
|
128
|
+
[`colorscale_${vQK2}`]: 'colorscale2',
|
|
129
|
+
[`color_range_${vQK2}`]: 'color_range2',
|
|
130
|
+
[`color_scale_type_${vQK2}`]: 'color_scale_type2',
|
|
131
|
+
} : {}),
|
|
132
|
+
...(fData ? { [`filter_range_${fQK}`]: 'filter_range' } : {}),
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
_buildBlendConfig(alphaBlend) {
|
|
137
|
+
return alphaBlend ? {
|
|
138
|
+
enable: true,
|
|
139
|
+
func: { srcRGB: 'src alpha', dstRGB: 'one minus src alpha', srcAlpha: 0, dstAlpha: 1 },
|
|
140
|
+
} : null
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import proj4 from 'proj4'
|
|
2
|
-
import { LayerType } from '
|
|
3
|
-
import { AXES } from '
|
|
4
|
-
import { registerLayerType } from '
|
|
5
|
-
import { parseCrsCode, crsToQkX, crsToQkY, ensureCrsDefined } from '
|
|
2
|
+
import { LayerType } from '../core/LayerType.js'
|
|
3
|
+
import { AXES } from '../axes/AxisRegistry.js'
|
|
4
|
+
import { registerLayerType } from '../core/LayerTypeRegistry.js'
|
|
5
|
+
import { parseCrsCode, crsToQkX, crsToQkY, ensureCrsDefined } from '../geo/EpsgUtils.js'
|
|
6
6
|
|
|
7
7
|
// ─── Tile math (standard Web Mercator / "slippy map" grid) ────────────────────
|
|
8
8
|
|
package/src/Axis.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* An Axis represents a single data axis on a plot. Axis instances are stable across
|
|
3
|
-
* plot.update() calls and can be linked together with linkAxes().
|
|
4
|
-
*
|
|
5
|
-
* Public interface (duck-typing compatible):
|
|
6
|
-
* - axis.quantityKind — string | null
|
|
7
|
-
* - axis.getDomain() — [min, max] | null
|
|
8
|
-
* - axis.setDomain(domain) — update domain, schedule render, notify subscribers
|
|
9
|
-
* - axis.subscribe(callback) — callback([min, max]) called on domain changes
|
|
10
|
-
* - axis.unsubscribe(callback) — remove a previously added callback
|
|
11
|
-
*/
|
|
12
|
-
export class Axis {
|
|
13
|
-
constructor(plot, name) {
|
|
14
|
-
this._plot = plot
|
|
15
|
-
this._name = name
|
|
16
|
-
this._listeners = new Set()
|
|
17
|
-
this._propagating = false
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** The quantity kind for this axis, or null if the plot hasn't been initialized yet. */
|
|
21
|
-
get quantityKind() { return this._plot.getAxisQuantityKind(this._name) }
|
|
22
|
-
|
|
23
|
-
/** Returns [min, max], or null if the axis has no domain yet. */
|
|
24
|
-
getDomain() { return this._plot.getAxisDomain(this._name) }
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Sets the axis domain, schedules a render on the owning plot, and notifies all
|
|
28
|
-
* subscribers (e.g. linked axes). A _propagating guard prevents infinite loops
|
|
29
|
-
* when axes are linked bidirectionally.
|
|
30
|
-
*/
|
|
31
|
-
setDomain(domain) {
|
|
32
|
-
if (this._propagating) return
|
|
33
|
-
this._propagating = true
|
|
34
|
-
try {
|
|
35
|
-
this._plot.setAxisDomain(this._name, domain)
|
|
36
|
-
this._plot.scheduleRender()
|
|
37
|
-
for (const cb of this._listeners) cb(domain)
|
|
38
|
-
} finally {
|
|
39
|
-
this._propagating = false
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Add a subscriber. callback([min, max]) is called after every setDomain(). */
|
|
44
|
-
subscribe(callback) { this._listeners.add(callback) }
|
|
45
|
-
|
|
46
|
-
/** Remove a previously added subscriber. */
|
|
47
|
-
unsubscribe(callback) { this._listeners.delete(callback) }
|
|
48
|
-
}
|
package/src/ColorAxisRegistry.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { getAxisQuantityKind } from './AxisQuantityKindRegistry.js'
|
|
2
|
-
import { getColorscaleIndex } from './ColorscaleRegistry.js'
|
|
3
|
-
|
|
4
|
-
export class ColorAxisRegistry {
|
|
5
|
-
constructor() {
|
|
6
|
-
this._axes = new Map()
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
ensureColorAxis(quantityKind, colorscaleOverride = null) {
|
|
10
|
-
if (!this._axes.has(quantityKind)) {
|
|
11
|
-
this._axes.set(quantityKind, { colorscaleOverride, range: null })
|
|
12
|
-
} else if (colorscaleOverride !== null) {
|
|
13
|
-
this._axes.get(quantityKind).colorscaleOverride = colorscaleOverride
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
setRange(quantityKind, min, max) {
|
|
18
|
-
if (!this._axes.has(quantityKind)) {
|
|
19
|
-
throw new Error(`Color axis '${quantityKind}' not found in registry`)
|
|
20
|
-
}
|
|
21
|
-
this._axes.get(quantityKind).range = [min, max]
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
getRange(quantityKind) {
|
|
25
|
-
return this._axes.get(quantityKind)?.range ?? null
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
getColorscale(quantityKind) {
|
|
29
|
-
const entry = this._axes.get(quantityKind)
|
|
30
|
-
if (!entry) return null
|
|
31
|
-
if (entry.colorscaleOverride) return entry.colorscaleOverride
|
|
32
|
-
const unitDef = getAxisQuantityKind(quantityKind)
|
|
33
|
-
return unitDef.colorscale ?? null
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
getColorscaleIndex(quantityKind) {
|
|
37
|
-
const colorscale = this.getColorscale(quantityKind)
|
|
38
|
-
if (colorscale === null) return 0
|
|
39
|
-
return getColorscaleIndex(colorscale)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
hasAxis(quantityKind) {
|
|
43
|
-
return this._axes.has(quantityKind)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
getQuantityKinds() {
|
|
47
|
-
return Array.from(this._axes.keys())
|
|
48
|
-
}
|
|
49
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
const colorscales = new Map()
|
|
2
|
-
|
|
3
|
-
export function registerColorscale(name, glslFn) {
|
|
4
|
-
colorscales.set(name, glslFn)
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function getRegisteredColorscales() {
|
|
8
|
-
return colorscales
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function getColorscaleIndex(name) {
|
|
12
|
-
let idx = 0
|
|
13
|
-
for (const key of colorscales.keys()) {
|
|
14
|
-
if (key === name) return idx
|
|
15
|
-
idx++
|
|
16
|
-
}
|
|
17
|
-
return 0
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function buildColorGlsl() {
|
|
21
|
-
if (colorscales.size === 0) return ''
|
|
22
|
-
|
|
23
|
-
const parts = []
|
|
24
|
-
|
|
25
|
-
for (const glslFn of colorscales.values()) {
|
|
26
|
-
parts.push(glslFn)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
parts.push('vec4 map_color(int cs, vec2 range, float value) {')
|
|
30
|
-
parts.push(' float t = clamp((value - range.x) / (range.y - range.x), 0.0, 1.0);')
|
|
31
|
-
|
|
32
|
-
let idx = 0
|
|
33
|
-
for (const name of colorscales.keys()) {
|
|
34
|
-
parts.push(` if (cs == ${idx}) return colorscale_${name}(t);`)
|
|
35
|
-
idx++
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
parts.push(' return vec4(0.5, 0.5, 0.5, 1.0);')
|
|
39
|
-
parts.push('}')
|
|
40
|
-
|
|
41
|
-
parts.push('vec4 map_color_s(int cs, vec2 range, float v, float scaleType, float useAlpha) {')
|
|
42
|
-
parts.push(' float vt = scaleType > 0.5 ? log(v) : v;')
|
|
43
|
-
parts.push(' float r0 = scaleType > 0.5 ? log(range.x) : range.x;')
|
|
44
|
-
parts.push(' float r1 = scaleType > 0.5 ? log(range.y) : range.y;')
|
|
45
|
-
parts.push(' float t = clamp((vt - r0) / (r1 - r0), 0.0, 1.0);')
|
|
46
|
-
parts.push(' vec4 color = map_color(cs, vec2(r0, r1), vt);')
|
|
47
|
-
parts.push(' if (useAlpha > 0.5) color.a = t;')
|
|
48
|
-
parts.push(' return gladly_apply_color(color);')
|
|
49
|
-
parts.push('}')
|
|
50
|
-
|
|
51
|
-
return parts.join('\n')
|
|
52
|
-
}
|