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.
Files changed (44) hide show
  1. package/README.md +1 -0
  2. package/package.json +16 -8
  3. package/src/axes/Axis.js +253 -0
  4. package/src/{AxisQuantityKindRegistry.js → axes/AxisQuantityKindRegistry.js} +7 -0
  5. package/src/{AxisRegistry.js → axes/AxisRegistry.js} +48 -0
  6. package/src/axes/ColorAxisRegistry.js +93 -0
  7. package/src/{FilterAxisRegistry.js → axes/FilterAxisRegistry.js} +63 -0
  8. package/src/axes/ZoomController.js +141 -0
  9. package/src/colorscales/BivariateColorscales.js +205 -0
  10. package/src/colorscales/ColorscaleRegistry.js +124 -0
  11. package/src/compute/ComputationRegistry.js +237 -0
  12. package/src/compute/axisFilter.js +47 -0
  13. package/src/compute/conv.js +230 -0
  14. package/src/compute/fft.js +292 -0
  15. package/src/compute/filter.js +227 -0
  16. package/src/compute/hist.js +180 -0
  17. package/src/compute/kde.js +102 -0
  18. package/src/{Layer.js → core/Layer.js} +4 -3
  19. package/src/{LayerType.js → core/LayerType.js} +72 -7
  20. package/src/core/Plot.js +735 -0
  21. package/src/{Colorbar.js → floats/Colorbar.js} +19 -5
  22. package/src/floats/Colorbar2d.js +77 -0
  23. package/src/{Filterbar.js → floats/Filterbar.js} +18 -4
  24. package/src/{FilterbarFloat.js → floats/Float.js} +17 -30
  25. package/src/{EpsgUtils.js → geo/EpsgUtils.js} +1 -1
  26. package/src/index.js +35 -22
  27. package/src/{ColorbarLayer.js → layers/ColorbarLayer.js} +2 -2
  28. package/src/layers/ColorbarLayer2d.js +97 -0
  29. package/src/{FilterbarLayer.js → layers/FilterbarLayer.js} +2 -2
  30. package/src/layers/HistogramLayer.js +212 -0
  31. package/src/layers/LinesLayer.js +199 -0
  32. package/src/layers/PointsLayer.js +114 -0
  33. package/src/layers/ScatterShared.js +142 -0
  34. package/src/{TileLayer.js → layers/TileLayer.js} +4 -4
  35. package/src/Axis.js +0 -48
  36. package/src/ColorAxisRegistry.js +0 -49
  37. package/src/ColorscaleRegistry.js +0 -52
  38. package/src/Float.js +0 -159
  39. package/src/Plot.js +0 -1068
  40. package/src/ScatterLayer.js +0 -133
  41. /package/src/{AxisLink.js → axes/AxisLink.js} +0 -0
  42. /package/src/{MatplotlibColorscales.js → colorscales/MatplotlibColorscales.js} +0 -0
  43. /package/src/{Data.js → core/Data.js} +0 -0
  44. /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 './LayerType.js'
3
- import { AXES } from './AxisRegistry.js'
4
- import { registerLayerType } from './LayerTypeRegistry.js'
5
- import { parseCrsCode, crsToQkX, crsToQkY, ensureCrsDefined } from './EpsgUtils.js'
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
- }
@@ -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
- }