gladly-plot 0.0.4 → 0.0.6
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 +9 -2
- package/package.json +10 -11
- package/src/axes/Axis.js +401 -0
- package/src/{AxisLink.js → axes/AxisLink.js} +6 -2
- package/src/{AxisQuantityKindRegistry.js → axes/AxisQuantityKindRegistry.js} +7 -0
- package/src/axes/AxisRegistry.js +179 -0
- package/src/axes/Camera.js +47 -0
- package/src/axes/ColorAxisRegistry.js +101 -0
- package/src/{FilterAxisRegistry.js → axes/FilterAxisRegistry.js} +63 -0
- package/src/axes/TickLabelAtlas.js +99 -0
- package/src/axes/ZoomController.js +463 -0
- package/src/colorscales/BivariateColorscales.js +205 -0
- package/src/colorscales/ColorscaleRegistry.js +144 -0
- package/src/compute/ComputationRegistry.js +179 -0
- package/src/compute/axisFilter.js +59 -0
- package/src/compute/conv.js +286 -0
- package/src/compute/elementwise.js +72 -0
- package/src/compute/fft.js +378 -0
- package/src/compute/filter.js +229 -0
- package/src/compute/hist.js +285 -0
- package/src/compute/kde.js +120 -0
- package/src/compute/scatter2dInterpolate.js +277 -0
- package/src/compute/util.js +196 -0
- package/src/core/ComputePipeline.js +153 -0
- package/src/core/GlBase.js +141 -0
- package/src/core/Layer.js +59 -0
- package/src/core/LayerType.js +433 -0
- package/src/core/Plot.js +1213 -0
- package/src/core/PlotGroup.js +204 -0
- package/src/core/ShaderQueue.js +73 -0
- package/src/data/ColumnData.js +269 -0
- package/src/data/Computation.js +95 -0
- package/src/data/Data.js +270 -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} +73 -30
- package/src/{EpsgUtils.js → geo/EpsgUtils.js} +1 -1
- package/src/index.js +47 -22
- package/src/layers/BarsLayer.js +168 -0
- package/src/{ColorbarLayer.js → layers/ColorbarLayer.js} +12 -16
- package/src/layers/ColorbarLayer2d.js +86 -0
- package/src/{FilterbarLayer.js → layers/FilterbarLayer.js} +6 -5
- package/src/layers/LinesLayer.js +185 -0
- package/src/layers/PointsLayer.js +118 -0
- package/src/layers/ScatterShared.js +98 -0
- package/src/{TileLayer.js → layers/TileLayer.js} +24 -20
- package/src/math/mat4.js +100 -0
- package/src/Axis.js +0 -48
- package/src/AxisRegistry.js +0 -54
- package/src/ColorAxisRegistry.js +0 -49
- package/src/ColorscaleRegistry.js +0 -52
- package/src/Data.js +0 -67
- package/src/Float.js +0 -159
- package/src/Layer.js +0 -44
- package/src/LayerType.js +0 -209
- package/src/Plot.js +0 -1073
- package/src/ScatterLayer.js +0 -287
- /package/src/{MatplotlibColorscales.js → colorscales/MatplotlibColorscales.js} +0 -0
- /package/src/{LayerTypeRegistry.js → core/LayerTypeRegistry.js} +0 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { LayerType } from "../core/LayerType.js"
|
|
2
|
+
import { Data } from "../data/Data.js"
|
|
3
|
+
import { registerLayerType } from "../core/LayerTypeRegistry.js"
|
|
4
|
+
import { AXES } from "../axes/AxisRegistry.js"
|
|
5
|
+
|
|
6
|
+
// Generic instanced bar layer. Renders `instanceCount` bars using live texture refs
|
|
7
|
+
// for bin center positions and bar lengths (counts).
|
|
8
|
+
//
|
|
9
|
+
// Each bar is a quad drawn as a triangle strip (4 vertices).
|
|
10
|
+
// Per-instance: x_center (bin centre, from texture) and a_pickId (bin index, divisor 1).
|
|
11
|
+
// Per-vertex: a_corner ∈ {0,1,2,3} — selects which corner of the rectangle.
|
|
12
|
+
// corner 0: bottom-left corner 1: bottom-right
|
|
13
|
+
// corner 2: top-left corner 3: top-right
|
|
14
|
+
//
|
|
15
|
+
// orientation "vertical" — bins on x-axis, bars extend upward (default)
|
|
16
|
+
// orientation "horizontal" — bins on y-axis, bars extend rightward
|
|
17
|
+
|
|
18
|
+
const BARS_VERT = `#version 300 es
|
|
19
|
+
precision mediump float;
|
|
20
|
+
|
|
21
|
+
in float a_corner;
|
|
22
|
+
in float x_center;
|
|
23
|
+
in float count;
|
|
24
|
+
|
|
25
|
+
uniform vec2 xDomain;
|
|
26
|
+
uniform vec2 yDomain;
|
|
27
|
+
uniform float xScaleType;
|
|
28
|
+
uniform float yScaleType;
|
|
29
|
+
uniform float u_binHalfWidth;
|
|
30
|
+
uniform float u_horizontal;
|
|
31
|
+
|
|
32
|
+
void main() {
|
|
33
|
+
float side = mod(a_corner, 2.0); // 0 = left, 1 = right
|
|
34
|
+
float vert = floor(a_corner / 2.0); // 0 = bottom, 1 = top
|
|
35
|
+
|
|
36
|
+
float bx = mix(x_center + (side * 2.0 - 1.0) * u_binHalfWidth, side * count, u_horizontal);
|
|
37
|
+
float by = mix(vert * count, x_center + (vert * 2.0 - 1.0) * u_binHalfWidth, u_horizontal);
|
|
38
|
+
|
|
39
|
+
gl_Position = plot_pos(vec2(bx, by));
|
|
40
|
+
}
|
|
41
|
+
`
|
|
42
|
+
|
|
43
|
+
const BARS_FRAG = `#version 300 es
|
|
44
|
+
precision mediump float;
|
|
45
|
+
uniform vec4 u_color;
|
|
46
|
+
void main() {
|
|
47
|
+
fragColor = gladly_apply_color(u_color);
|
|
48
|
+
}
|
|
49
|
+
`
|
|
50
|
+
|
|
51
|
+
class BarsLayerType extends LayerType {
|
|
52
|
+
constructor() {
|
|
53
|
+
super({ name: "bars", vert: BARS_VERT, frag: BARS_FRAG })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_getAxisConfig(parameters, data) {
|
|
57
|
+
const d = Data.wrap(data)
|
|
58
|
+
const { xData, yData, xAxis = "xaxis_bottom", yAxis = "yaxis_left", orientation = "vertical" } = parameters
|
|
59
|
+
if (orientation === "horizontal") {
|
|
60
|
+
return {
|
|
61
|
+
xAxis,
|
|
62
|
+
xAxisQuantityKind: d.getQuantityKind(yData) ?? yData,
|
|
63
|
+
yAxis,
|
|
64
|
+
yAxisQuantityKind: d.getQuantityKind(xData) ?? xData,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
xAxis,
|
|
69
|
+
xAxisQuantityKind: d.getQuantityKind(xData) ?? xData,
|
|
70
|
+
yAxis,
|
|
71
|
+
yAxisQuantityKind: d.getQuantityKind(yData) ?? yData,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
schema(data) {
|
|
76
|
+
const cols = Data.wrap(data).columns()
|
|
77
|
+
return {
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: {
|
|
80
|
+
xData: {
|
|
81
|
+
type: "string",
|
|
82
|
+
enum: cols,
|
|
83
|
+
description: "Column name for bin center x positions"
|
|
84
|
+
},
|
|
85
|
+
yData: {
|
|
86
|
+
type: "string",
|
|
87
|
+
enum: cols,
|
|
88
|
+
description: "Column name for bar heights (counts)"
|
|
89
|
+
},
|
|
90
|
+
color: {
|
|
91
|
+
type: "array",
|
|
92
|
+
items: { type: "number" },
|
|
93
|
+
minItems: 4,
|
|
94
|
+
maxItems: 4,
|
|
95
|
+
default: [0.2, 0.5, 0.8, 1.0],
|
|
96
|
+
description: "Bar colour as [R, G, B, A] in [0, 1]"
|
|
97
|
+
},
|
|
98
|
+
orientation: {
|
|
99
|
+
type: "string",
|
|
100
|
+
enum: ["vertical", "horizontal"],
|
|
101
|
+
default: "vertical",
|
|
102
|
+
description: "vertical: bins on x-axis, bars extend up; horizontal: bins on y-axis, bars extend right"
|
|
103
|
+
},
|
|
104
|
+
xAxis: {
|
|
105
|
+
type: "string",
|
|
106
|
+
enum: AXES.filter(a => a.includes("x")),
|
|
107
|
+
default: "xaxis_bottom"
|
|
108
|
+
},
|
|
109
|
+
yAxis: {
|
|
110
|
+
type: "string",
|
|
111
|
+
enum: AXES.filter(a => a.includes("y")),
|
|
112
|
+
default: "yaxis_left"
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
required: ["xData", "yData"]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_createLayer(regl, parameters, data, plot) {
|
|
120
|
+
const d = Data.wrap(data)
|
|
121
|
+
const {
|
|
122
|
+
xData,
|
|
123
|
+
yData,
|
|
124
|
+
color = [0.2, 0.5, 0.8, 1.0],
|
|
125
|
+
orientation = "vertical",
|
|
126
|
+
} = parameters
|
|
127
|
+
|
|
128
|
+
const xRef = d.getData(xData)
|
|
129
|
+
const yRef = d.getData(yData)
|
|
130
|
+
if (!xRef) throw new Error(`BarsLayer: column '${xData}' not found`)
|
|
131
|
+
if (!yRef) throw new Error(`BarsLayer: column '${yData}' not found`)
|
|
132
|
+
|
|
133
|
+
const bins = xRef.length ?? 1
|
|
134
|
+
|
|
135
|
+
const xDomain = d.getDomain(xData) ?? [0, 1]
|
|
136
|
+
const yDomain = d.getDomain(yData) ?? [0, 1]
|
|
137
|
+
const binHalfWidth = (xDomain[1] - xDomain[0]) / (2 * bins)
|
|
138
|
+
|
|
139
|
+
const xQK = d.getQuantityKind(xData) ?? xData
|
|
140
|
+
const yQK = d.getQuantityKind(yData) ?? yData
|
|
141
|
+
|
|
142
|
+
// Per-vertex corner indices 0–3 (triangle-strip quad)
|
|
143
|
+
const a_corner = new Float32Array([0, 1, 2, 3])
|
|
144
|
+
|
|
145
|
+
return [{
|
|
146
|
+
attributes: {
|
|
147
|
+
a_corner, // per-vertex, no divisor
|
|
148
|
+
x_center: xRef, // live ref → resolved via _isLive path in resolveToGlslExpr
|
|
149
|
+
count: yRef, // live ref → resolved via _isLive path
|
|
150
|
+
},
|
|
151
|
+
uniforms: {
|
|
152
|
+
u_binHalfWidth: binHalfWidth,
|
|
153
|
+
u_color: color,
|
|
154
|
+
u_horizontal: orientation === "horizontal" ? 1.0 : 0.0,
|
|
155
|
+
},
|
|
156
|
+
vertexCount: 4,
|
|
157
|
+
instanceCount: bins,
|
|
158
|
+
primitive: "triangle strip",
|
|
159
|
+
domains: {
|
|
160
|
+
[xQK]: xDomain,
|
|
161
|
+
[yQK]: yDomain,
|
|
162
|
+
},
|
|
163
|
+
}]
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export const barsLayerType = new BarsLayerType()
|
|
168
|
+
registerLayerType("bars", barsLayerType)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { LayerType } from "
|
|
2
|
-
import { registerLayerType } from "
|
|
1
|
+
import { LayerType } from "../core/LayerType.js"
|
|
2
|
+
import { registerLayerType } from "../core/LayerTypeRegistry.js"
|
|
3
3
|
|
|
4
4
|
// Four vertices for a triangle-strip quad covering the entire clip space.
|
|
5
5
|
const quadCx = new Float32Array([-1, 1, -1, 1])
|
|
@@ -7,6 +7,7 @@ const quadCy = new Float32Array([-1, -1, 1, 1])
|
|
|
7
7
|
|
|
8
8
|
export const colorbarLayerType = new LayerType({
|
|
9
9
|
name: "colorbar",
|
|
10
|
+
suppressWarnings: true,
|
|
10
11
|
|
|
11
12
|
getAxisConfig: function(parameters) {
|
|
12
13
|
const { colorAxis, orientation = "horizontal" } = parameters
|
|
@@ -15,33 +16,33 @@ export const colorbarLayerType = new LayerType({
|
|
|
15
16
|
xAxisQuantityKind: orientation === "horizontal" ? colorAxis : undefined,
|
|
16
17
|
yAxis: orientation === "vertical" ? "yaxis_left" : null,
|
|
17
18
|
yAxisQuantityKind: orientation === "vertical" ? colorAxis : undefined,
|
|
18
|
-
colorAxisQuantityKinds:
|
|
19
|
+
colorAxisQuantityKinds: { '': colorAxis },
|
|
19
20
|
}
|
|
20
21
|
},
|
|
21
22
|
|
|
22
|
-
vert:
|
|
23
|
+
vert: `#version 300 es
|
|
23
24
|
precision mediump float;
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
in float cx;
|
|
26
|
+
in float cy;
|
|
26
27
|
uniform int horizontal;
|
|
27
|
-
|
|
28
|
+
out float tval;
|
|
28
29
|
void main() {
|
|
29
30
|
gl_Position = vec4(cx, cy, 0.0, 1.0);
|
|
30
31
|
tval = horizontal == 1 ? (cx + 1.0) / 2.0 : (cy + 1.0) / 2.0;
|
|
31
32
|
}
|
|
32
33
|
`,
|
|
33
34
|
|
|
34
|
-
frag:
|
|
35
|
+
frag: `#version 300 es
|
|
35
36
|
precision mediump float;
|
|
36
37
|
uniform int colorscale;
|
|
37
38
|
uniform vec2 color_range;
|
|
38
39
|
uniform float color_scale_type;
|
|
39
|
-
|
|
40
|
+
in float tval;
|
|
40
41
|
void main() {
|
|
41
42
|
float r0 = color_scale_type > 0.5 ? log(color_range.x) : color_range.x;
|
|
42
43
|
float r1 = color_scale_type > 0.5 ? log(color_range.y) : color_range.y;
|
|
43
44
|
float v = r0 + tval * (r1 - r0);
|
|
44
|
-
|
|
45
|
+
fragColor = gladly_apply_color(map_color(colorscale, vec2(r0, r1), v));
|
|
45
46
|
}
|
|
46
47
|
`,
|
|
47
48
|
|
|
@@ -55,18 +56,13 @@ export const colorbarLayerType = new LayerType({
|
|
|
55
56
|
required: ["colorAxis"]
|
|
56
57
|
}),
|
|
57
58
|
|
|
58
|
-
createLayer: function(parameters) {
|
|
59
|
+
createLayer: function(regl, parameters) {
|
|
59
60
|
const { colorAxis, orientation = "horizontal" } = parameters
|
|
60
61
|
return [{
|
|
61
62
|
attributes: { cx: quadCx, cy: quadCy },
|
|
62
63
|
uniforms: { horizontal: orientation === "horizontal" ? 1 : 0 },
|
|
63
64
|
primitive: "triangle strip",
|
|
64
65
|
vertexCount: 4,
|
|
65
|
-
nameMap: {
|
|
66
|
-
[`colorscale_${colorAxis}`]: 'colorscale',
|
|
67
|
-
[`color_range_${colorAxis}`]: 'color_range',
|
|
68
|
-
[`color_scale_type_${colorAxis}`]: 'color_scale_type',
|
|
69
|
-
},
|
|
70
66
|
}]
|
|
71
67
|
}
|
|
72
68
|
})
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { LayerType } from "../core/LayerType.js"
|
|
2
|
+
import { registerLayerType } from "../core/LayerTypeRegistry.js"
|
|
3
|
+
|
|
4
|
+
// Four vertices for a triangle-strip quad covering the entire clip space.
|
|
5
|
+
const quadCx = new Float32Array([-1, 1, -1, 1])
|
|
6
|
+
const quadCy = new Float32Array([-1, -1, 1, 1])
|
|
7
|
+
|
|
8
|
+
export const colorbar2dLayerType = new LayerType({
|
|
9
|
+
name: "colorbar2d",
|
|
10
|
+
suppressWarnings: true,
|
|
11
|
+
|
|
12
|
+
getAxisConfig: function(parameters) {
|
|
13
|
+
const { xAxis, yAxis } = parameters
|
|
14
|
+
return {
|
|
15
|
+
xAxis: "xaxis_bottom",
|
|
16
|
+
xAxisQuantityKind: xAxis,
|
|
17
|
+
yAxis: "yaxis_left",
|
|
18
|
+
yAxisQuantityKind: yAxis,
|
|
19
|
+
colorAxisQuantityKinds: { '_a': xAxis, '_b': yAxis },
|
|
20
|
+
colorAxis2dQuantityKinds: { '': ['_a', '_b'] },
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
vert: `#version 300 es
|
|
25
|
+
precision mediump float;
|
|
26
|
+
in float cx;
|
|
27
|
+
in float cy;
|
|
28
|
+
out float tval_x;
|
|
29
|
+
out float tval_y;
|
|
30
|
+
void main() {
|
|
31
|
+
gl_Position = vec4(cx, cy, 0.0, 1.0);
|
|
32
|
+
tval_x = (cx + 1.0) / 2.0;
|
|
33
|
+
tval_y = (cy + 1.0) / 2.0;
|
|
34
|
+
}
|
|
35
|
+
`,
|
|
36
|
+
|
|
37
|
+
// tval_x/tval_y are [0,1] positions in the colorbar quad. We convert them to actual data
|
|
38
|
+
// values in each axis's range (undoing the log transform if needed), then pass those raw
|
|
39
|
+
// values to map_color_s_2d which re-applies the scale type internally. The exp() call
|
|
40
|
+
// is the inverse of the log() that map_color_s_2d will apply, so log-scale roundtrips
|
|
41
|
+
// correctly and linear-scale is a no-op (exp(log(v)) == v, but for linear vt == v anyway).
|
|
42
|
+
frag: `#version 300 es
|
|
43
|
+
precision mediump float;
|
|
44
|
+
uniform vec2 color_range_a;
|
|
45
|
+
uniform float color_scale_type_a;
|
|
46
|
+
uniform vec2 color_range_b;
|
|
47
|
+
uniform float color_scale_type_b;
|
|
48
|
+
in float tval_x;
|
|
49
|
+
in float tval_y;
|
|
50
|
+
void main() {
|
|
51
|
+
float r0_a = color_scale_type_a > 0.5 ? log(color_range_a.x) : color_range_a.x;
|
|
52
|
+
float r1_a = color_scale_type_a > 0.5 ? log(color_range_a.y) : color_range_a.y;
|
|
53
|
+
float vt_a = r0_a + tval_x * (r1_a - r0_a);
|
|
54
|
+
float v_a = color_scale_type_a > 0.5 ? exp(vt_a) : vt_a;
|
|
55
|
+
|
|
56
|
+
float r0_b = color_scale_type_b > 0.5 ? log(color_range_b.x) : color_range_b.x;
|
|
57
|
+
float r1_b = color_scale_type_b > 0.5 ? log(color_range_b.y) : color_range_b.y;
|
|
58
|
+
float vt_b = r0_b + tval_y * (r1_b - r0_b);
|
|
59
|
+
float v_b = color_scale_type_b > 0.5 ? exp(vt_b) : vt_b;
|
|
60
|
+
|
|
61
|
+
fragColor = map_color_2d_(vec2(v_a, v_b));
|
|
62
|
+
}
|
|
63
|
+
`,
|
|
64
|
+
|
|
65
|
+
schema: () => ({
|
|
66
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
67
|
+
type: "object",
|
|
68
|
+
properties: {
|
|
69
|
+
xAxis: { type: "string", description: "Quantity kind for the x axis (color axis A)" },
|
|
70
|
+
yAxis: { type: "string", description: "Quantity kind for the y axis (color axis B)" }
|
|
71
|
+
},
|
|
72
|
+
required: ["xAxis", "yAxis"]
|
|
73
|
+
}),
|
|
74
|
+
|
|
75
|
+
createLayer: function(regl, parameters) {
|
|
76
|
+
const { xAxis, yAxis } = parameters
|
|
77
|
+
return [{
|
|
78
|
+
attributes: { cx: quadCx, cy: quadCy },
|
|
79
|
+
uniforms: {},
|
|
80
|
+
primitive: "triangle strip",
|
|
81
|
+
vertexCount: 4,
|
|
82
|
+
}]
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
registerLayerType("colorbar2d", colorbar2dLayerType)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { LayerType } from "
|
|
2
|
-
import { registerLayerType } from "
|
|
1
|
+
import { LayerType } from "../core/LayerType.js"
|
|
2
|
+
import { registerLayerType } from "../core/LayerTypeRegistry.js"
|
|
3
3
|
|
|
4
4
|
export const filterbarLayerType = new LayerType({
|
|
5
5
|
name: "filterbar",
|
|
6
|
+
suppressWarnings: true,
|
|
6
7
|
|
|
7
8
|
getAxisConfig: function(parameters) {
|
|
8
9
|
const { filterAxis, orientation = "horizontal" } = parameters
|
|
@@ -16,15 +17,15 @@ export const filterbarLayerType = new LayerType({
|
|
|
16
17
|
|
|
17
18
|
// Nothing is rendered — vertexCount is always 0.
|
|
18
19
|
// These minimal shaders satisfy the WebGL compiler but never execute.
|
|
19
|
-
vert:
|
|
20
|
+
vert: `#version 300 es
|
|
20
21
|
precision mediump float;
|
|
21
22
|
uniform vec2 xDomain;
|
|
22
23
|
uniform vec2 yDomain;
|
|
23
24
|
void main() { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }
|
|
24
25
|
`,
|
|
25
|
-
frag:
|
|
26
|
+
frag: `#version 300 es
|
|
26
27
|
precision mediump float;
|
|
27
|
-
void main() {
|
|
28
|
+
void main() { fragColor = gladly_apply_color(vec4(0.0, 0.0, 0.0, 0.0)); }
|
|
28
29
|
`,
|
|
29
30
|
|
|
30
31
|
schema: () => ({
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Lines mode uses instanced rendering:
|
|
2
|
+
// - Template: 2 vertices with a_endPoint in {0.0, 1.0} (divisor=0 → per-vertex)
|
|
3
|
+
// - Per-segment data (x0/x1, y0/y1, v0/v1, seg0/seg1, f0/f1) sampled from GPU textures
|
|
4
|
+
// using a_pickId (= gl_InstanceID) + 0.0 or + 1.0 as the index.
|
|
5
|
+
//
|
|
6
|
+
// Segment boundary handling: when a_seg0 != a_seg1, collapse both template vertices to
|
|
7
|
+
// (a_x0, a_y0) producing a zero-length degenerate line that the rasterizer discards.
|
|
8
|
+
|
|
9
|
+
import { ScatterLayerTypeBase } from "./ScatterShared.js"
|
|
10
|
+
import { Data } from "../data/Data.js"
|
|
11
|
+
import { registerLayerType } from "../core/LayerTypeRegistry.js"
|
|
12
|
+
import { EXPRESSION_REF_OPT, resolveQuantityKind, resolveExprToColumn } from "../compute/ComputationRegistry.js"
|
|
13
|
+
|
|
14
|
+
function makeLinesVert(hasFilter, hasSegIds, hasV, hasV2, hasZ) {
|
|
15
|
+
return `#version 300 es
|
|
16
|
+
precision mediump float;
|
|
17
|
+
in float a_endPoint;
|
|
18
|
+
in float a_x0;
|
|
19
|
+
in float a_y0;
|
|
20
|
+
${hasZ ? 'in float a_z0;\n in float a_z1;' : ''}
|
|
21
|
+
in float a_x1;
|
|
22
|
+
in float a_y1;
|
|
23
|
+
${hasV ? 'in float a_v0;\n in float a_v1;' : ''}
|
|
24
|
+
${hasV2 ? 'in float a_v20;\n in float a_v21;' : ''}
|
|
25
|
+
${hasSegIds ? 'in float a_seg0;\n in float a_seg1;' : ''}
|
|
26
|
+
${hasFilter ? 'in float a_f0;\n in float a_f1;' : ''}
|
|
27
|
+
uniform vec2 xDomain;
|
|
28
|
+
uniform vec2 yDomain;
|
|
29
|
+
uniform float xScaleType;
|
|
30
|
+
uniform float yScaleType;
|
|
31
|
+
out float v_color_start;
|
|
32
|
+
out float v_color_end;
|
|
33
|
+
out float v_color2_start;
|
|
34
|
+
out float v_color2_end;
|
|
35
|
+
out float v_t;
|
|
36
|
+
void main() {
|
|
37
|
+
float same_seg = ${hasSegIds ? 'abs(a_seg0 - a_seg1) < 0.5 ? 1.0 : 0.0' : '1.0'};
|
|
38
|
+
${hasFilter ? 'if (!filter_(a_f0) || !filter_(a_f1)) same_seg = 0.0;' : ''}
|
|
39
|
+
float t = same_seg * a_endPoint;
|
|
40
|
+
float x = mix(a_x0, a_x1, t);
|
|
41
|
+
float y = mix(a_y0, a_y1, t);
|
|
42
|
+
${hasZ ? 'float z = mix(a_z0, a_z1, t);\n gl_Position = plot_pos_3d(vec3(x, y, z));'
|
|
43
|
+
: 'gl_Position = plot_pos(vec2(x, y));'}
|
|
44
|
+
v_color_start = ${hasV ? 'a_v0' : '0.0'};
|
|
45
|
+
v_color_end = ${hasV ? 'a_v1' : '0.0'};
|
|
46
|
+
v_color2_start = ${hasV2 ? 'a_v20' : '0.0'};
|
|
47
|
+
v_color2_end = ${hasV2 ? 'a_v21' : '0.0'};
|
|
48
|
+
v_t = a_endPoint;
|
|
49
|
+
}
|
|
50
|
+
`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function makeLinesFrag(hasFirst, hasSecond) {
|
|
54
|
+
return `#version 300 es
|
|
55
|
+
precision mediump float;
|
|
56
|
+
uniform float u_lineColorMode;
|
|
57
|
+
in float v_color_start;
|
|
58
|
+
in float v_color_end;
|
|
59
|
+
in float v_color2_start;
|
|
60
|
+
in float v_color2_end;
|
|
61
|
+
in float v_t;
|
|
62
|
+
void main() {
|
|
63
|
+
${!hasFirst ? `
|
|
64
|
+
fragColor = vec4(0.0, 0.0, 0.0, 1.0);` : hasSecond ? `
|
|
65
|
+
float value = u_lineColorMode > 0.5
|
|
66
|
+
? (v_t < 0.5 ? v_color_start : v_color_end)
|
|
67
|
+
: mix(v_color_start, v_color_end, v_t);
|
|
68
|
+
float value2 = u_lineColorMode > 0.5
|
|
69
|
+
? (v_t < 0.5 ? v_color2_start : v_color2_end)
|
|
70
|
+
: mix(v_color2_start, v_color2_end, v_t);
|
|
71
|
+
fragColor = map_color_2d_(vec2(value, value2));` : `
|
|
72
|
+
float value = u_lineColorMode > 0.5
|
|
73
|
+
? (v_t < 0.5 ? v_color_start : v_color_end)
|
|
74
|
+
: mix(v_color_start, v_color_end, v_t);
|
|
75
|
+
fragColor = map_color_(value);`}
|
|
76
|
+
}
|
|
77
|
+
`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class LinesLayerType extends ScatterLayerTypeBase {
|
|
81
|
+
constructor() {
|
|
82
|
+
super({ name: "lines", vert: makeLinesVert(false, false, false, false), frag: makeLinesFrag(false, false) })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
schema(data) {
|
|
86
|
+
const d = Data.wrap(data)
|
|
87
|
+
return {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
...this._commonSchemaProperties(d),
|
|
91
|
+
lineSegmentIdData: EXPRESSION_REF_OPT,
|
|
92
|
+
lineColorMode: {
|
|
93
|
+
type: "string",
|
|
94
|
+
enum: ["gradient", "midpoint"],
|
|
95
|
+
default: "gradient",
|
|
96
|
+
description: "Color mode for lines: gradient interpolates vData linearly; midpoint uses each endpoint's color up to the segment center"
|
|
97
|
+
},
|
|
98
|
+
lineWidth: {
|
|
99
|
+
type: "number",
|
|
100
|
+
default: 1.0,
|
|
101
|
+
minimum: 1,
|
|
102
|
+
description: "Line width in pixels (note: browsers may clamp values above 1)"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
required: ["xData", "yData", "zData", "zAxis"]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async _createLayer(regl, parameters, data, plot) {
|
|
110
|
+
const d = Data.wrap(data)
|
|
111
|
+
const { lineSegmentIdData: lineSegmentIdDataRaw, lineColorMode = "gradient", lineWidth = 1.0 } = parameters
|
|
112
|
+
const lineSegmentIdData = (lineSegmentIdDataRaw == null || lineSegmentIdDataRaw === "none") ? null : lineSegmentIdDataRaw
|
|
113
|
+
const { xData, yData, zData: zDataOrig, vData: vDataOrig, vData2: vData2Orig, fData: fDataOrig } = parameters
|
|
114
|
+
const zData = (zDataOrig == null || zDataOrig === "none") ? null : zDataOrig
|
|
115
|
+
const vData = (vDataOrig == null || vDataOrig === "none") ? null : vDataOrig
|
|
116
|
+
const vData2 = (vData2Orig == null || vData2Orig === "none") ? null : vData2Orig
|
|
117
|
+
const fData = (fDataOrig == null || fDataOrig === "none") ? null : fDataOrig
|
|
118
|
+
|
|
119
|
+
const xQK = resolveQuantityKind(xData, d) ?? xData
|
|
120
|
+
const yQK = resolveQuantityKind(yData, d) ?? yData
|
|
121
|
+
const zQK = zData ? (resolveQuantityKind(zData, d) ?? zData) : null
|
|
122
|
+
const vQK = vData ? (resolveQuantityKind(vData, d) ?? vData) : null
|
|
123
|
+
const vQK2 = vData2 ? (resolveQuantityKind(vData2, d) ?? vData2) : null
|
|
124
|
+
|
|
125
|
+
const colX = await resolveExprToColumn(xData, d, regl, plot)
|
|
126
|
+
const colY = await resolveExprToColumn(yData, d, regl, plot)
|
|
127
|
+
const colZ = zData ? await resolveExprToColumn(zData, d, regl, plot) : null
|
|
128
|
+
const colV = vData ? await resolveExprToColumn(vData, d, regl, plot) : null
|
|
129
|
+
const colV2 = vData2 ? await resolveExprToColumn(vData2, d, regl, plot) : null
|
|
130
|
+
const colF = fData ? await resolveExprToColumn(fData, d, regl, plot) : null
|
|
131
|
+
const colSeg = lineSegmentIdData ? await resolveExprToColumn(lineSegmentIdData, d, regl, plot) : null
|
|
132
|
+
|
|
133
|
+
if (!colX) throw new Error(`Data column '${xData}' not found`)
|
|
134
|
+
if (!colY) throw new Error(`Data column '${yData}' not found`)
|
|
135
|
+
|
|
136
|
+
const N = colX.length
|
|
137
|
+
const domains = this._buildDomains(d, xData, yData, zData, vData, vData2, xQK, yQK, zQK, vQK, vQK2)
|
|
138
|
+
|
|
139
|
+
// For vData: if a string column, offset-sample start/end; if a computed expression,
|
|
140
|
+
// pass through as-is (both endpoints get the same value, matching old behaviour).
|
|
141
|
+
const vAttr0 = vData ? (colV ? colV.withOffset('0.0') : vData) : null
|
|
142
|
+
const vAttr1 = vData ? (colV ? colV.withOffset('1.0') : vData) : null
|
|
143
|
+
const vAttr20 = vData2 ? (colV2 ? colV2.withOffset('0.0') : vData2) : null
|
|
144
|
+
const vAttr21 = vData2 ? (colV2 ? colV2.withOffset('1.0') : vData2) : null
|
|
145
|
+
|
|
146
|
+
return [{
|
|
147
|
+
attributes: {
|
|
148
|
+
a_endPoint: new Float32Array([0.0, 1.0]),
|
|
149
|
+
a_x0: colX.withOffset('0.0'),
|
|
150
|
+
a_x1: colX.withOffset('1.0'),
|
|
151
|
+
a_y0: colY.withOffset('0.0'),
|
|
152
|
+
a_y1: colY.withOffset('1.0'),
|
|
153
|
+
...(colZ ? { a_z0: colZ.withOffset('0.0'), a_z1: colZ.withOffset('1.0') } : {}),
|
|
154
|
+
...(vAttr0 !== null ? { a_v0: vAttr0, a_v1: vAttr1 } : {}),
|
|
155
|
+
...(vAttr20 !== null ? { a_v20: vAttr20, a_v21: vAttr21 } : {}),
|
|
156
|
+
...(colSeg ? { a_seg0: colSeg.withOffset('0.0'), a_seg1: colSeg.withOffset('1.0') } : {}),
|
|
157
|
+
...(fData ? { a_f0: colF.withOffset('0.0'), a_f1: colF.withOffset('1.0') } : {}),
|
|
158
|
+
},
|
|
159
|
+
uniforms: {
|
|
160
|
+
u_lineColorMode: lineColorMode === "midpoint" ? 1.0 : 0.0,
|
|
161
|
+
},
|
|
162
|
+
domains,
|
|
163
|
+
primitive: "lines",
|
|
164
|
+
lineWidth,
|
|
165
|
+
vertexCount: 2,
|
|
166
|
+
instanceCount: N - 1,
|
|
167
|
+
}]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async createDrawCommand(regl, layer, plot) {
|
|
171
|
+
const hasFilter = Object.keys(layer.filterAxes).length > 0
|
|
172
|
+
const hasFirst = '' in layer.colorAxes
|
|
173
|
+
const hasSecond = '2' in layer.colorAxes
|
|
174
|
+
const hasSegIds = 'a_seg0' in layer.attributes
|
|
175
|
+
const hasV = 'a_v0' in layer.attributes
|
|
176
|
+
const hasV2 = 'a_v20' in layer.attributes
|
|
177
|
+
const hasZ = 'a_z0' in layer.attributes
|
|
178
|
+
this.vert = makeLinesVert(hasFilter, hasSegIds, hasV, hasV2, hasZ)
|
|
179
|
+
this.frag = makeLinesFrag(hasFirst, hasSecond)
|
|
180
|
+
return await super.createDrawCommand(regl, layer, plot)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const linesLayerType = new LinesLayerType()
|
|
185
|
+
registerLayerType("lines", linesLayerType)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { ScatterLayerTypeBase } from "./ScatterShared.js"
|
|
2
|
+
import { Data } from "../data/Data.js"
|
|
3
|
+
import { registerLayerType } from "../core/LayerTypeRegistry.js"
|
|
4
|
+
import { resolveQuantityKind } from "../compute/ComputationRegistry.js"
|
|
5
|
+
|
|
6
|
+
function makePointsVert(hasFilter, hasZ) {
|
|
7
|
+
return `#version 300 es
|
|
8
|
+
precision mediump float;
|
|
9
|
+
in float x;
|
|
10
|
+
in float y;
|
|
11
|
+
${hasZ ? 'in float z;' : ''}
|
|
12
|
+
in float color_data;
|
|
13
|
+
in float color_data2;
|
|
14
|
+
${hasFilter ? 'in float filter_data;' : ''}
|
|
15
|
+
uniform vec2 xDomain;
|
|
16
|
+
uniform vec2 yDomain;
|
|
17
|
+
uniform float xScaleType;
|
|
18
|
+
uniform float yScaleType;
|
|
19
|
+
uniform float u_pointSize;
|
|
20
|
+
out float value;
|
|
21
|
+
out float value2;
|
|
22
|
+
void main() {
|
|
23
|
+
${hasFilter ? 'if (!filter_(filter_data)) { gl_Position = vec4(2.0, 2.0, 2.0, 1.0); return; }' : ''}
|
|
24
|
+
${hasZ ? 'gl_Position = plot_pos_3d(vec3(x, y, z));' : 'gl_Position = plot_pos(vec2(x, y));'}
|
|
25
|
+
gl_PointSize = u_pointSize;
|
|
26
|
+
value = color_data;
|
|
27
|
+
value2 = color_data2;
|
|
28
|
+
}
|
|
29
|
+
`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function makePointsFrag(hasFirst, hasSecond) {
|
|
33
|
+
return `#version 300 es
|
|
34
|
+
precision mediump float;
|
|
35
|
+
in float value;
|
|
36
|
+
in float value2;
|
|
37
|
+
void main() {
|
|
38
|
+
${hasFirst
|
|
39
|
+
? (hasSecond
|
|
40
|
+
? 'fragColor = map_color_2d_(vec2(value, value2));'
|
|
41
|
+
: 'fragColor = map_color_2d_x_(value);')
|
|
42
|
+
: (hasSecond
|
|
43
|
+
? 'fragColor = map_color_2d_y_2(value2);'
|
|
44
|
+
: 'fragColor = vec4(0.0, 0.0, 0.0, 1.0);')}
|
|
45
|
+
}
|
|
46
|
+
`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class PointsLayerType extends ScatterLayerTypeBase {
|
|
50
|
+
constructor() {
|
|
51
|
+
super({ name: "points", vert: makePointsVert(false), frag: makePointsFrag(false) })
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
schema(data) {
|
|
55
|
+
const d = Data.wrap(data)
|
|
56
|
+
return {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
...this._commonSchemaProperties(d),
|
|
60
|
+
pointSize: { type: "integer", default: 4, minimum: 1 },
|
|
61
|
+
},
|
|
62
|
+
required: ["xData", "yData", "zData", "zAxis", "pointSize"]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_createLayer(regl, parameters, data, plot) {
|
|
67
|
+
const d = Data.wrap(data)
|
|
68
|
+
const { vData: vDataRaw, vData2: vData2Raw, fData: fDataRaw, zData: zDataRaw } = parameters
|
|
69
|
+
const vDataIn = (vDataRaw == null || vDataRaw === "none") ? null : vDataRaw
|
|
70
|
+
const vData2In = (vData2Raw == null || vData2Raw === "none") ? null : vData2Raw
|
|
71
|
+
const fData = (fDataRaw == null || fDataRaw === "none") ? null : fDataRaw
|
|
72
|
+
const zData = (zDataRaw == null || zDataRaw === "none") ? null : zDataRaw
|
|
73
|
+
const vData = vDataIn
|
|
74
|
+
const vData2 = vData2In
|
|
75
|
+
|
|
76
|
+
const xQK = resolveQuantityKind(parameters.xData, d) ?? undefined
|
|
77
|
+
const yQK = resolveQuantityKind(parameters.yData, d) ?? undefined
|
|
78
|
+
const zQK = zData ? (resolveQuantityKind(zData, d) ?? undefined) : undefined
|
|
79
|
+
const vQK = vData ? resolveQuantityKind(vData, d) : null
|
|
80
|
+
const vQK2 = vData2 ? resolveQuantityKind(vData2, d) : null
|
|
81
|
+
|
|
82
|
+
const domains = this._buildDomains(d, parameters.xData, parameters.yData, zData, vData, vData2, xQK, yQK, zQK, vQK, vQK2)
|
|
83
|
+
|
|
84
|
+
// Vertex count: read from data when xData is a plain column name so that
|
|
85
|
+
// Plot.render() can determine how many vertices to draw even when other
|
|
86
|
+
// attributes are computed expressions resolved at draw time.
|
|
87
|
+
const vertexCount = typeof parameters.xData === 'string'
|
|
88
|
+
? (d.getData(parameters.xData)?.length ?? null)
|
|
89
|
+
: null
|
|
90
|
+
|
|
91
|
+
return [{
|
|
92
|
+
attributes: {
|
|
93
|
+
x: parameters.xData,
|
|
94
|
+
y: parameters.yData,
|
|
95
|
+
...(zData !== null ? { z: zData } : {}),
|
|
96
|
+
color_data: vData !== null ? vData : new Float32Array(vertexCount ?? 0).fill(NaN),
|
|
97
|
+
color_data2: vData2 !== null ? vData2 : new Float32Array(vertexCount ?? 0).fill(NaN),
|
|
98
|
+
...(fData != null ? { filter_data: fData } : {}),
|
|
99
|
+
},
|
|
100
|
+
uniforms: { u_pointSize: () => parameters.pointSize ?? 4 },
|
|
101
|
+
domains,
|
|
102
|
+
vertexCount,
|
|
103
|
+
}]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async createDrawCommand(regl, layer, plot) {
|
|
107
|
+
const hasFilter = Object.keys(layer.filterAxes).length > 0
|
|
108
|
+
const hasFirst = '' in layer.colorAxes
|
|
109
|
+
const hasSecond = '2' in layer.colorAxes
|
|
110
|
+
const hasZ = 'z' in layer.attributes
|
|
111
|
+
this.vert = makePointsVert(hasFilter, hasZ)
|
|
112
|
+
this.frag = makePointsFrag(hasFirst, hasSecond)
|
|
113
|
+
return await super.createDrawCommand(regl, layer, plot)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const pointsLayerType = new PointsLayerType()
|
|
118
|
+
registerLayerType("points", pointsLayerType)
|