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,270 @@
1
+ import { ArrayColumn, TextureColumn } from './ColumnData.js'
2
+
3
+ function domainsEqual(a, b) {
4
+ if (a === b) return true
5
+ if (a == null || b == null) return a === b
6
+ return a[0] === b[0] && a[1] === b[1]
7
+ }
8
+
9
+ // Runtime wrapper for a ComputedData instance. Manages live texture references
10
+ // and tracks which axes were accessed so it can recompute when they change.
11
+ export class ComputedDataNode {
12
+ constructor(computedData, params) {
13
+ this._computedData = computedData
14
+ this._params = params
15
+ this._liveRefs = {} // { colName: { texture } }
16
+ this._meta = null
17
+ this._accessedAxes = new Set()
18
+ this._cachedDomains = {}
19
+ this._regl = null
20
+ this._dataGroup = null
21
+ this._version = 0
22
+ }
23
+
24
+ columns() {
25
+ return this._computedData.columns(this._params)
26
+ }
27
+
28
+ getData(col) {
29
+ const ref = this._liveRefs[col]
30
+ if (!ref) return null
31
+ const node = this
32
+ let lastVersion = this._version
33
+ return new TextureColumn(ref, {
34
+ domain: this._meta?.domains?.[col] ?? null,
35
+ quantityKind: this._meta?.quantityKinds?.[col] ?? null,
36
+ length: ref.texture ? (ref.texture._dataLength ?? ref.texture.width) : null,
37
+ shape: this._meta?.shapes?.[col] ?? null,
38
+ refreshFn: async (plot) => {
39
+ await node.refreshIfNeeded(plot)
40
+ if (node._version !== lastVersion) {
41
+ lastVersion = node._version
42
+ return true
43
+ }
44
+ return false
45
+ }
46
+ })
47
+ }
48
+
49
+ getQuantityKind(col) {
50
+ return this._meta?.quantityKinds?.[col] ?? null
51
+ }
52
+
53
+ getDomain(col) {
54
+ return this._meta?.domains?.[col] ?? null
55
+ }
56
+
57
+ async _initialize(regl, dataGroup, plot) {
58
+ this._regl = regl
59
+ this._dataGroup = dataGroup
60
+
61
+ const getAxisDomain = (axisId) => {
62
+ this._accessedAxes.add(axisId)
63
+ return plot ? plot.getAxisDomain(axisId) : null
64
+ }
65
+
66
+ const result = await this._computedData.compute(regl, this._params, dataGroup, getAxisDomain)
67
+ this._meta = result._meta ?? null
68
+
69
+ for (const [key, val] of Object.entries(result)) {
70
+ if (key === '_meta') continue
71
+ this._liveRefs[key] = { texture: val }
72
+ }
73
+
74
+ for (const axisId of this._accessedAxes) {
75
+ this._cachedDomains[axisId] = plot ? plot.getAxisDomain(axisId) : null
76
+ }
77
+ }
78
+
79
+ async refreshIfNeeded(plot) {
80
+ if (this._accessedAxes.size === 0) return
81
+
82
+ let needsRecompute = false
83
+ for (const axisId of this._accessedAxes) {
84
+ if (!domainsEqual(plot.getAxisDomain(axisId), this._cachedDomains[axisId])) {
85
+ needsRecompute = true
86
+ break
87
+ }
88
+ }
89
+ if (!needsRecompute) return
90
+
91
+ const newAccessedAxes = new Set()
92
+ const newCachedDomains = {}
93
+ const getAxisDomain = (axisId) => {
94
+ newAccessedAxes.add(axisId)
95
+ return plot ? plot.getAxisDomain(axisId) : null
96
+ }
97
+
98
+ const result = await this._computedData.compute(this._regl, this._params, this._dataGroup, getAxisDomain)
99
+ this._meta = result._meta ?? null
100
+
101
+ for (const [key, val] of Object.entries(result)) {
102
+ if (key === '_meta') continue
103
+ if (this._liveRefs[key]) {
104
+ this._liveRefs[key].texture = val
105
+ } else {
106
+ this._liveRefs[key] = { texture: val }
107
+ }
108
+ }
109
+
110
+ this._accessedAxes = newAccessedAxes
111
+ for (const axisId of newAccessedAxes) {
112
+ newCachedDomains[axisId] = plot ? plot.getAxisDomain(axisId) : null
113
+ }
114
+ this._cachedDomains = newCachedDomains
115
+ this._version++
116
+ }
117
+ }
118
+
119
+ export class DataGroup {
120
+ constructor(raw) {
121
+ this._children = {}
122
+ for (const [key, value] of Object.entries(raw)) {
123
+ this._children[key] = Data.wrap(value)
124
+ }
125
+ }
126
+
127
+ listData() {
128
+ const result = {}
129
+ for (const [key, child] of Object.entries(this._children)) {
130
+ if (child instanceof Data) result[key] = child
131
+ }
132
+ return result
133
+ }
134
+
135
+ subgroups() {
136
+ const result = {}
137
+ for (const [key, child] of Object.entries(this._children)) {
138
+ if (child instanceof DataGroup) result[key] = child
139
+ }
140
+ return result
141
+ }
142
+
143
+ columns() {
144
+ const cols = []
145
+ for (const [key, child] of Object.entries(this._children)) {
146
+ for (const col of child.columns()) {
147
+ cols.push(`${key}.${col}`)
148
+ }
149
+ }
150
+ return cols
151
+ }
152
+
153
+ _resolve(col) {
154
+ const dotIdx = col.indexOf('.')
155
+ if (dotIdx === -1) return null
156
+ const prefix = col.slice(0, dotIdx)
157
+ const rest = col.slice(dotIdx + 1)
158
+ const child = this._children[prefix]
159
+ if (!child) return null
160
+ return { child, rest }
161
+ }
162
+
163
+ getData(col) {
164
+ const r = this._resolve(col)
165
+ return r ? r.child.getData(r.rest) : undefined
166
+ }
167
+
168
+ getQuantityKind(col) {
169
+ const r = this._resolve(col)
170
+ return r ? r.child.getQuantityKind(r.rest) : undefined
171
+ }
172
+
173
+ getDomain(col) {
174
+ const r = this._resolve(col)
175
+ return r ? r.child.getDomain(r.rest) : undefined
176
+ }
177
+ }
178
+
179
+ export function normalizeData(data) {
180
+ if (data == null) return null
181
+ const wrapped = Data.wrap(data)
182
+ return (wrapped instanceof DataGroup) ? wrapped : new DataGroup({ input: wrapped })
183
+ }
184
+
185
+ export class Data {
186
+ constructor(raw) {
187
+ raw = raw ?? {}
188
+ if (raw.data != null && typeof raw.data === 'object' && !(raw.data instanceof Float32Array)) {
189
+ this._columnar = true
190
+ this._data = raw.data
191
+ this._quantityKinds = raw.quantity_kinds ?? {}
192
+ this._rawDomains = raw.domains ?? {}
193
+ } else {
194
+ this._columnar = false
195
+ this._raw = raw
196
+ }
197
+ }
198
+
199
+ static wrap(data) {
200
+ if (data != null && typeof data.columns === 'function' && typeof data.getData === 'function') {
201
+ return data
202
+ }
203
+
204
+ if (data != null && typeof data === 'object') {
205
+ const isColumnar = data.data != null && typeof data.data === 'object' && !(data.data instanceof Float32Array)
206
+
207
+ if (!isColumnar) {
208
+ const vals = Object.values(data)
209
+ if (vals.length > 0) {
210
+ const isRawFormat = vals.every(v =>
211
+ v instanceof Float32Array ||
212
+ (v && typeof v === 'object' && v.data instanceof Float32Array)
213
+ )
214
+ if (!isRawFormat) {
215
+ return new DataGroup(data)
216
+ }
217
+ }
218
+ }
219
+ }
220
+
221
+ return new Data(data)
222
+ }
223
+
224
+ _entry(col) {
225
+ if (this._columnar) {
226
+ const rawDomain = this._rawDomains[col]
227
+ let domain
228
+ if (Array.isArray(rawDomain)) {
229
+ domain = [rawDomain[0], rawDomain[1]]
230
+ } else if (rawDomain && typeof rawDomain === 'object') {
231
+ domain = [rawDomain.min, rawDomain.max]
232
+ }
233
+ return { data: this._data[col], quantityKind: this._quantityKinds[col], domain }
234
+ }
235
+
236
+ const v = this._raw[col]
237
+ if (v instanceof Float32Array) {
238
+ return { data: v, quantityKind: undefined, domain: undefined }
239
+ }
240
+ if (v && typeof v === 'object') {
241
+ let domain
242
+ if (Array.isArray(v.domain)) {
243
+ domain = [v.domain[0], v.domain[1]]
244
+ } else if (v.domain && typeof v.domain === 'object') {
245
+ domain = [v.domain.min, v.domain.max]
246
+ }
247
+ return { data: v.data, quantityKind: v.quantity_kind, domain }
248
+ }
249
+ return { data: undefined, quantityKind: undefined, domain: undefined }
250
+ }
251
+
252
+ columns() {
253
+ return this._columnar ? Object.keys(this._data) : Object.keys(this._raw)
254
+ }
255
+
256
+ // Returns ArrayColumn (with domain + quantityKind) or null if column not found.
257
+ getData(col) {
258
+ const entry = this._entry(col)
259
+ if (!entry.data) return null
260
+ return new ArrayColumn(entry.data, { domain: entry.domain ?? null, quantityKind: entry.quantityKind ?? null })
261
+ }
262
+
263
+ getQuantityKind(col) {
264
+ return this._entry(col).quantityKind
265
+ }
266
+
267
+ getDomain(col) {
268
+ return this._entry(col).domain
269
+ }
270
+ }
@@ -1,6 +1,9 @@
1
- import { Plot } from "./Plot.js"
2
- import { linkAxes } from "./AxisLink.js"
3
- import "./ColorbarLayer.js"
1
+ import { Plot } from "../core/Plot.js"
2
+ import { getScaleTypeFloat } from "../axes/AxisQuantityKindRegistry.js"
3
+ import { linkAxes } from "../axes/AxisLink.js"
4
+ import "../layers/ColorbarLayer.js"
5
+
6
+ const DRAG_BAR_HEIGHT = 12
4
7
 
5
8
  const DEFAULT_MARGINS = {
6
9
  horizontal: { top: 5, right: 40, bottom: 45, left: 40 },
@@ -34,7 +37,7 @@ export class Colorbar extends Plot {
34
37
 
35
38
  _getScaleTypeFloat(quantityKind) {
36
39
  if (quantityKind === this._colorAxisName && this._targetPlot) {
37
- return this._targetPlot._getScaleTypeFloat(quantityKind)
40
+ return getScaleTypeFloat(quantityKind, this._targetPlot.currentConfig?.axes)
38
41
  }
39
42
  return super._getScaleTypeFloat(quantityKind)
40
43
  }
@@ -50,7 +53,7 @@ export class Colorbar extends Plot {
50
53
  }
51
54
  const colorscale = this._targetPlot.colorAxisRegistry?.getColorscale(this._colorAxisName)
52
55
  if (colorscale) this.colorAxisRegistry.ensureColorAxis(this._colorAxisName, colorscale)
53
- const scaleType = this._targetPlot._getScaleTypeFloat(this._colorAxisName) > 0.5 ? "log" : "linear"
56
+ const scaleType = getScaleTypeFloat(this._colorAxisName, this._targetPlot.currentConfig?.axes) > 0.5 ? "log" : "linear"
54
57
  this.axisRegistry.setScaleType(this._spatialAxis, scaleType)
55
58
  }
56
59
  super.render()
@@ -62,3 +65,14 @@ export class Colorbar extends Plot {
62
65
  super.destroy()
63
66
  }
64
67
  }
68
+
69
+ // Register the colorbar float factory so Plot._syncFloats can create colorbar floats.
70
+ Plot.registerFloatFactory('colorbar', {
71
+ factory: (parentPlot, container, opts) =>
72
+ new Colorbar(container, parentPlot, opts.axisName, { orientation: opts.orientation }),
73
+ defaultSize: (opts) => {
74
+ const h = opts.orientation === 'horizontal' ? 70 + DRAG_BAR_HEIGHT : 220 + DRAG_BAR_HEIGHT
75
+ const w = opts.orientation === 'horizontal' ? 220 : 70
76
+ return { width: w, height: h }
77
+ }
78
+ })
@@ -0,0 +1,77 @@
1
+ import { Plot } from "../core/Plot.js"
2
+ import { getScaleTypeFloat } from "../axes/AxisQuantityKindRegistry.js"
3
+ import { linkAxes } from "../axes/AxisLink.js"
4
+ import "../layers/ColorbarLayer2d.js"
5
+
6
+ const DRAG_BAR_HEIGHT = 12
7
+
8
+ // Margins leave room for tick labels on both spatial axes.
9
+ const DEFAULT_MARGIN = { top: 10, right: 50, bottom: 45, left: 55 }
10
+
11
+ export class Colorbar2d extends Plot {
12
+ constructor(container, targetPlot, xAxis, yAxis, { margin } = {}) {
13
+ super(container, { margin: margin ?? DEFAULT_MARGIN })
14
+
15
+ this._targetPlot = targetPlot
16
+ this._xAxis = xAxis
17
+ this._yAxis = yAxis
18
+
19
+ this.update({
20
+ data: {},
21
+ config: {
22
+ layers: [{ colorbar2d: { xAxis, yAxis } }]
23
+ }
24
+ })
25
+
26
+ // Link the colorbar's spatial axes to the target's color axes so zoom/pan propagates.
27
+ this._xLink = linkAxes(this.axes["xaxis_bottom"], targetPlot.axes[xAxis])
28
+ this._yLink = linkAxes(this.axes["yaxis_left"], targetPlot.axes[yAxis])
29
+
30
+ // Re-render (with sync) whenever the target plot renders.
31
+ this._syncCallback = () => this.render()
32
+ targetPlot._renderCallbacks.add(this._syncCallback)
33
+ }
34
+
35
+ _getScaleTypeFloat(quantityKind) {
36
+ if ((quantityKind === this._xAxis || quantityKind === this._yAxis) && this._targetPlot) {
37
+ return getScaleTypeFloat(quantityKind, this._targetPlot.currentConfig?.axes)
38
+ }
39
+ return super._getScaleTypeFloat(quantityKind)
40
+ }
41
+
42
+ render() {
43
+ // Sync range, colorscale, and scale type for both color axes from the target plot.
44
+ if (this.colorAxisRegistry && this.axisRegistry && this._targetPlot) {
45
+ for (const [colorAxisName, spatialAxisId] of [
46
+ [this._xAxis, "xaxis_bottom"],
47
+ [this._yAxis, "yaxis_left"]
48
+ ]) {
49
+ const range = this._targetPlot.getAxisDomain(colorAxisName)
50
+ if (range) {
51
+ this.setAxisDomain(spatialAxisId, range)
52
+ this.setAxisDomain(colorAxisName, range)
53
+ }
54
+ const colorscale = this._targetPlot.colorAxisRegistry?.getColorscale(colorAxisName)
55
+ if (colorscale) this.colorAxisRegistry.ensureColorAxis(colorAxisName, colorscale)
56
+ const scaleType = getScaleTypeFloat(colorAxisName, this._targetPlot.currentConfig?.axes) > 0.5 ? "log" : "linear"
57
+ this.axisRegistry.setScaleType(spatialAxisId, scaleType)
58
+ }
59
+ }
60
+ super.render()
61
+ }
62
+
63
+ destroy() {
64
+ this._xLink.unlink()
65
+ this._yLink.unlink()
66
+ this._targetPlot._renderCallbacks.delete(this._syncCallback)
67
+ super.destroy()
68
+ }
69
+ }
70
+
71
+ // Register the colorbar2d float factory so Plot._syncFloats can create 2D colorbar floats.
72
+ // Default size is square since both axes carry equal weight.
73
+ Plot.registerFloatFactory('colorbar2d', {
74
+ factory: (parentPlot, container, opts) =>
75
+ new Colorbar2d(container, parentPlot, opts.xAxis, opts.yAxis),
76
+ defaultSize: () => ({ width: 250, height: 250 + DRAG_BAR_HEIGHT })
77
+ })
@@ -1,6 +1,9 @@
1
- import { Plot } from "./Plot.js"
2
- import { linkAxes } from "./AxisLink.js"
3
- import "./FilterbarLayer.js"
1
+ import { Plot } from "../core/Plot.js"
2
+ import { getScaleTypeFloat } from "../axes/AxisQuantityKindRegistry.js"
3
+ import { linkAxes } from "../axes/AxisLink.js"
4
+ import "../layers/FilterbarLayer.js"
5
+
6
+ const DRAG_BAR_HEIGHT = 12
4
7
 
5
8
  const DEFAULT_MARGINS = {
6
9
  horizontal: { top: 5, right: 40, bottom: 45, left: 40 },
@@ -124,7 +127,7 @@ export class Filterbar extends Plot {
124
127
  if (this._maxInput) this._maxInput.checked = range.max === null
125
128
  }
126
129
  }
127
- const scaleType = this._targetPlot._getScaleTypeFloat(this._filterAxisName) > 0.5 ? "log" : "linear"
130
+ const scaleType = getScaleTypeFloat(this._filterAxisName, this._targetPlot.currentConfig?.axes) > 0.5 ? "log" : "linear"
128
131
  this.axisRegistry.setScaleType(this._spatialAxis, scaleType)
129
132
  }
130
133
  super.render()
@@ -136,3 +139,14 @@ export class Filterbar extends Plot {
136
139
  super.destroy()
137
140
  }
138
141
  }
142
+
143
+ // Register the filterbar float factory so Plot._syncFloats can create filterbar floats.
144
+ Plot.registerFloatFactory('filterbar', {
145
+ factory: (parentPlot, container, opts) =>
146
+ new Filterbar(container, parentPlot, opts.axisName, { orientation: opts.orientation }),
147
+ defaultSize: (opts) => {
148
+ const h = opts.orientation === 'horizontal' ? 70 + DRAG_BAR_HEIGHT : 220 + DRAG_BAR_HEIGHT
149
+ const w = opts.orientation === 'horizontal' ? 220 : 80
150
+ return { width: w, height: h }
151
+ }
152
+ })
@@ -1,36 +1,24 @@
1
- import { Filterbar } from "./Filterbar.js"
2
- import { Plot } from "./Plot.js"
3
-
4
1
  const DRAG_BAR_HEIGHT = 12
5
2
  const MIN_WIDTH = 80
6
- const MIN_HEIGHT = DRAG_BAR_HEIGHT + 40
7
-
8
- const DEFAULT_SIZE = {
9
- horizontal: { width: 220, height: 70 + DRAG_BAR_HEIGHT },
10
- vertical: { width: 80, height: 220 + DRAG_BAR_HEIGHT }
11
- }
3
+ const MIN_HEIGHT = DRAG_BAR_HEIGHT + 30
12
4
 
13
- export class FilterbarFloat {
14
- constructor(parentPlot, filterAxisName, {
15
- orientation = "horizontal",
5
+ // Generic draggable, resizable floating container that wraps any Plot-like widget.
6
+ // factory(container) must return an object with a destroy() method.
7
+ export class Float {
8
+ constructor(parentPlot, factory, {
16
9
  x = 10,
17
- y = 100,
18
- width,
19
- height,
20
- margin
10
+ y = 10,
11
+ width = 220,
12
+ height = 82
21
13
  } = {}) {
22
- const defaults = DEFAULT_SIZE[orientation]
23
- const w = width ?? defaults.width
24
- const h = height ?? defaults.height
25
-
26
14
  // Outer floating container
27
15
  this._el = document.createElement('div')
28
16
  Object.assign(this._el.style, {
29
17
  position: 'absolute',
30
18
  left: x + 'px',
31
19
  top: y + 'px',
32
- width: w + 'px',
33
- height: h + 'px',
20
+ width: width + 'px',
21
+ height: height + 'px',
34
22
  zIndex: '10',
35
23
  boxSizing: 'border-box',
36
24
  background: 'rgba(255,255,255,0.88)',
@@ -40,6 +28,7 @@ export class FilterbarFloat {
40
28
  overflow: 'hidden'
41
29
  })
42
30
 
31
+ // Ensure parent is positioned so our absolute child is contained within it
43
32
  const parentEl = parentPlot.container
44
33
  if (getComputedStyle(parentEl).position === 'static') {
45
34
  parentEl.style.position = 'relative'
@@ -76,18 +65,18 @@ export class FilterbarFloat {
76
65
  })
77
66
  this._el.appendChild(this._resizeHandle)
78
67
 
79
- // Sub-container for the filterbar — sits below the drag bar
80
- this._filterbarEl = document.createElement('div')
81
- Object.assign(this._filterbarEl.style, {
68
+ // Content area — sits below the drag bar, fills the rest of the float
69
+ this._contentEl = document.createElement('div')
70
+ Object.assign(this._contentEl.style, {
82
71
  position: 'absolute',
83
72
  top: DRAG_BAR_HEIGHT + 'px',
84
73
  left: '0',
85
74
  right: '0',
86
75
  bottom: '0'
87
76
  })
88
- this._el.appendChild(this._filterbarEl)
77
+ this._el.appendChild(this._contentEl)
89
78
 
90
- this._filterbar = new Filterbar(this._filterbarEl, parentPlot, filterAxisName, { orientation, margin })
79
+ this._widget = factory(this._contentEl)
91
80
 
92
81
  this._setupInteraction()
93
82
  }
@@ -139,19 +128,73 @@ export class FilterbarFloat {
139
128
  document.addEventListener('mousemove', onMouseMove)
140
129
  document.addEventListener('mouseup', onMouseUp)
141
130
 
131
+ // Touch support
132
+ const onDragBarTouchStart = (e) => {
133
+ if (e.touches.length !== 1) return
134
+ e.preventDefault()
135
+ const t = e.touches[0]
136
+ mode = 'drag'
137
+ startX = t.clientX
138
+ startY = t.clientY
139
+ startLeft = parseInt(this._el.style.left, 10)
140
+ startTop = parseInt(this._el.style.top, 10)
141
+ this._dragBar.style.cursor = 'grabbing'
142
+ }
143
+
144
+ const onResizeTouchStart = (e) => {
145
+ if (e.touches.length !== 1) return
146
+ e.preventDefault()
147
+ e.stopPropagation()
148
+ const t = e.touches[0]
149
+ mode = 'resize'
150
+ startX = t.clientX
151
+ startY = t.clientY
152
+ startW = this._el.offsetWidth
153
+ startH = this._el.offsetHeight
154
+ }
155
+
156
+ const onTouchMove = (e) => {
157
+ if (!mode || e.touches.length !== 1) return
158
+ e.preventDefault()
159
+ const t = e.touches[0]
160
+ const dx = t.clientX - startX
161
+ const dy = t.clientY - startY
162
+ if (mode === 'drag') {
163
+ this._el.style.left = (startLeft + dx) + 'px'
164
+ this._el.style.top = (startTop + dy) + 'px'
165
+ } else {
166
+ this._el.style.width = Math.max(MIN_WIDTH, startW + dx) + 'px'
167
+ this._el.style.height = Math.max(MIN_HEIGHT, startH + dy) + 'px'
168
+ }
169
+ }
170
+
171
+ const onTouchEnd = () => {
172
+ if (mode === 'drag') this._dragBar.style.cursor = 'grab'
173
+ mode = null
174
+ }
175
+
176
+ this._dragBar.addEventListener('touchstart', onDragBarTouchStart, { passive: false })
177
+ this._resizeHandle.addEventListener('touchstart', onResizeTouchStart, { passive: false })
178
+ document.addEventListener('touchmove', onTouchMove, { passive: false })
179
+ document.addEventListener('touchend', onTouchEnd)
180
+ document.addEventListener('touchcancel', onTouchEnd)
181
+
142
182
  this._cleanupInteraction = () => {
143
183
  this._dragBar.removeEventListener('mousedown', onDragBarMouseDown)
144
184
  this._resizeHandle.removeEventListener('mousedown', onResizeMouseDown)
145
185
  document.removeEventListener('mousemove', onMouseMove)
146
186
  document.removeEventListener('mouseup', onMouseUp)
187
+ this._dragBar.removeEventListener('touchstart', onDragBarTouchStart)
188
+ this._resizeHandle.removeEventListener('touchstart', onResizeTouchStart)
189
+ document.removeEventListener('touchmove', onTouchMove)
190
+ document.removeEventListener('touchend', onTouchEnd)
191
+ document.removeEventListener('touchcancel', onTouchEnd)
147
192
  }
148
193
  }
149
194
 
150
195
  destroy() {
151
196
  this._cleanupInteraction()
152
- this._filterbar.destroy()
197
+ this._widget.destroy()
153
198
  this._el.remove()
154
199
  }
155
200
  }
156
-
157
- Plot._FilterbarFloatClass = FilterbarFloat
@@ -1,6 +1,6 @@
1
1
  import proj4 from 'proj4'
2
2
  import { byEpsg } from 'projnames'
3
- import { registerAxisQuantityKind } from './AxisQuantityKindRegistry.js'
3
+ import { registerAxisQuantityKind } from '../axes/AxisQuantityKindRegistry.js'
4
4
 
5
5
  /**
6
6
  * Parse an EPSG CRS string or number to a plain integer code.
package/src/index.js CHANGED
@@ -1,24 +1,49 @@
1
- export { LayerType } from "./LayerType.js"
2
- export { Layer } from "./Layer.js"
3
- export { Data } from "./Data.js"
4
- export { AxisRegistry, AXES } from "./AxisRegistry.js"
5
- export { ColorAxisRegistry } from "./ColorAxisRegistry.js"
6
- export { FilterAxisRegistry, buildFilterGlsl } from "./FilterAxisRegistry.js"
7
- export { Plot } from "./Plot.js"
8
- export { scatterLayerType } from "./ScatterLayer.js"
9
- export { registerLayerType, getLayerType, getRegisteredLayerTypes } from "./LayerTypeRegistry.js"
10
- export { registerAxisQuantityKind, getAxisQuantityKind, getRegisteredAxisQuantityKinds } from "./AxisQuantityKindRegistry.js"
11
- export { registerColorscale, getRegisteredColorscales, getColorscaleIndex, buildColorGlsl } from "./ColorscaleRegistry.js"
12
- export { Axis } from "./Axis.js"
13
- export { linkAxes } from "./AxisLink.js"
14
- export { Colorbar } from "./Colorbar.js"
15
- export { colorbarLayerType } from "./ColorbarLayer.js"
16
- export { Float } from "./Float.js"
17
- export { Filterbar } from "./Filterbar.js"
18
- export { filterbarLayerType } from "./FilterbarLayer.js"
19
- export { FilterbarFloat } from "./FilterbarFloat.js"
20
- export { tileLayerType, TileLayerType } from "./TileLayer.js"
21
- export { registerEpsgDef, parseCrsCode, crsToQkX, crsToQkY, qkToEpsgCode, reproject } from "./EpsgUtils.js"
1
+ export { LayerType } from "./core/LayerType.js"
2
+ export { Layer } from "./core/Layer.js"
3
+ export { Data, DataGroup } from "./data/Data.js"
4
+ export { AxisRegistry, AXES, AXES_2D, AXIS_GEOMETRY, axisEndpoints, axisPosAtN } from "./axes/AxisRegistry.js"
5
+ export { Camera } from "./axes/Camera.js"
6
+ export { TickLabelAtlas } from "./axes/TickLabelAtlas.js"
7
+ export { ColorAxisRegistry } from "./axes/ColorAxisRegistry.js"
8
+ export { FilterAxisRegistry, buildFilterGlsl } from "./axes/FilterAxisRegistry.js"
9
+ export { Plot } from "./core/Plot.js"
10
+ export { PlotGroup } from "./core/PlotGroup.js"
11
+ export { ComputePipeline, ComputeOutput } from "./core/ComputePipeline.js"
12
+ export { pointsLayerType } from "./layers/PointsLayer.js"
13
+ export { linesLayerType } from "./layers/LinesLayer.js"
14
+ export { registerLayerType, getLayerType, getRegisteredLayerTypes } from "./core/LayerTypeRegistry.js"
15
+ export { registerAxisQuantityKind, getAxisQuantityKind, getRegisteredAxisQuantityKinds } from "./axes/AxisQuantityKindRegistry.js"
16
+ export { registerColorscale, register2DColorscale, getRegisteredColorscales, getRegistered2DColorscales, getColorscaleIndex, get2DColorscaleIndex, buildColorGlsl } from "./colorscales/ColorscaleRegistry.js"
17
+ export { Axis } from "./axes/Axis.js"
18
+ export { linkAxes } from "./axes/AxisLink.js"
19
+ export { Colorbar } from "./floats/Colorbar.js"
20
+ export { colorbarLayerType } from "./layers/ColorbarLayer.js"
21
+ export { Colorbar2d } from "./floats/Colorbar2d.js"
22
+ export { colorbar2dLayerType } from "./layers/ColorbarLayer2d.js"
23
+ export { Float } from "./floats/Float.js"
24
+ export { Filterbar } from "./floats/Filterbar.js"
25
+ export { filterbarLayerType } from "./layers/FilterbarLayer.js"
26
+ export { tileLayerType, TileLayerType } from "./layers/TileLayer.js"
27
+ export { barsLayerType } from "./layers/BarsLayer.js"
28
+ export { registerEpsgDef, parseCrsCode, crsToQkX, crsToQkY, qkToEpsgCode, reproject } from "./geo/EpsgUtils.js"
29
+ export { Computation, TextureComputation, GlslComputation, ComputedData } from "./data/Computation.js"
30
+ export { EXPRESSION_REF, computationSchema, buildTransformSchema, registerTextureComputation, registerGlslComputation, registerComputedData, getComputedData } from "./compute/ComputationRegistry.js"
31
+ export { ComputedDataNode } from "./data/Data.js"
22
32
 
23
33
  // Register all matplotlib colorscales (side-effect import)
24
- import "./MatplotlibColorscales.js"
34
+ import "./colorscales/MatplotlibColorscales.js"
35
+ import "./colorscales/BivariateColorscales.js"
36
+
37
+ // Register all compute texture computations (side-effect imports)
38
+ import "./compute/filter.js"
39
+ import "./compute/kde.js"
40
+ import "./compute/fft.js"
41
+ import "./compute/conv.js"
42
+ import "./compute/hist.js"
43
+ import "./compute/axisFilter.js"
44
+ import "./compute/elementwise.js"
45
+ import "./compute/util.js"
46
+ import "./compute/scatter2dInterpolate.js"
47
+
48
+ // Register BarsLayer (side-effect import)
49
+ import "./layers/BarsLayer.js"