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.
Files changed (60) hide show
  1. package/README.md +9 -2
  2. package/package.json +10 -11
  3. package/src/axes/Axis.js +401 -0
  4. package/src/{AxisLink.js → axes/AxisLink.js} +6 -2
  5. package/src/{AxisQuantityKindRegistry.js → axes/AxisQuantityKindRegistry.js} +7 -0
  6. package/src/axes/AxisRegistry.js +179 -0
  7. package/src/axes/Camera.js +47 -0
  8. package/src/axes/ColorAxisRegistry.js +101 -0
  9. package/src/{FilterAxisRegistry.js → axes/FilterAxisRegistry.js} +63 -0
  10. package/src/axes/TickLabelAtlas.js +99 -0
  11. package/src/axes/ZoomController.js +463 -0
  12. package/src/colorscales/BivariateColorscales.js +205 -0
  13. package/src/colorscales/ColorscaleRegistry.js +144 -0
  14. package/src/compute/ComputationRegistry.js +179 -0
  15. package/src/compute/axisFilter.js +59 -0
  16. package/src/compute/conv.js +286 -0
  17. package/src/compute/elementwise.js +72 -0
  18. package/src/compute/fft.js +378 -0
  19. package/src/compute/filter.js +229 -0
  20. package/src/compute/hist.js +285 -0
  21. package/src/compute/kde.js +120 -0
  22. package/src/compute/scatter2dInterpolate.js +277 -0
  23. package/src/compute/util.js +196 -0
  24. package/src/core/ComputePipeline.js +153 -0
  25. package/src/core/GlBase.js +141 -0
  26. package/src/core/Layer.js +59 -0
  27. package/src/core/LayerType.js +433 -0
  28. package/src/core/Plot.js +1213 -0
  29. package/src/core/PlotGroup.js +204 -0
  30. package/src/core/ShaderQueue.js +73 -0
  31. package/src/data/ColumnData.js +269 -0
  32. package/src/data/Computation.js +95 -0
  33. package/src/data/Data.js +270 -0
  34. package/src/{Colorbar.js → floats/Colorbar.js} +19 -5
  35. package/src/floats/Colorbar2d.js +77 -0
  36. package/src/{Filterbar.js → floats/Filterbar.js} +18 -4
  37. package/src/{FilterbarFloat.js → floats/Float.js} +73 -30
  38. package/src/{EpsgUtils.js → geo/EpsgUtils.js} +1 -1
  39. package/src/index.js +47 -22
  40. package/src/layers/BarsLayer.js +168 -0
  41. package/src/{ColorbarLayer.js → layers/ColorbarLayer.js} +12 -16
  42. package/src/layers/ColorbarLayer2d.js +86 -0
  43. package/src/{FilterbarLayer.js → layers/FilterbarLayer.js} +6 -5
  44. package/src/layers/LinesLayer.js +185 -0
  45. package/src/layers/PointsLayer.js +118 -0
  46. package/src/layers/ScatterShared.js +98 -0
  47. package/src/{TileLayer.js → layers/TileLayer.js} +24 -20
  48. package/src/math/mat4.js +100 -0
  49. package/src/Axis.js +0 -48
  50. package/src/AxisRegistry.js +0 -54
  51. package/src/ColorAxisRegistry.js +0 -49
  52. package/src/ColorscaleRegistry.js +0 -52
  53. package/src/Data.js +0 -67
  54. package/src/Float.js +0 -159
  55. package/src/Layer.js +0 -44
  56. package/src/LayerType.js +0 -209
  57. package/src/Plot.js +0 -1073
  58. package/src/ScatterLayer.js +0 -287
  59. /package/src/{MatplotlibColorscales.js → colorscales/MatplotlibColorscales.js} +0 -0
  60. /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 "./LayerType.js"
2
- import { registerLayerType } from "./LayerTypeRegistry.js"
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: [colorAxis],
19
+ colorAxisQuantityKinds: { '': colorAxis },
19
20
  }
20
21
  },
21
22
 
22
- vert: `
23
+ vert: `#version 300 es
23
24
  precision mediump float;
24
- attribute float cx;
25
- attribute float cy;
25
+ in float cx;
26
+ in float cy;
26
27
  uniform int horizontal;
27
- varying float tval;
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
- varying float tval;
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
- gl_FragColor = gladly_apply_color(map_color(colorscale, vec2(r0, r1), v));
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 "./LayerType.js"
2
- import { registerLayerType } from "./LayerTypeRegistry.js"
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() { gl_FragColor = gladly_apply_color(vec4(0.0, 0.0, 0.0, 0.0)); }
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)