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
package/src/data/Data.js
ADDED
|
@@ -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 "
|
|
2
|
-
import {
|
|
3
|
-
import "
|
|
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.
|
|
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.
|
|
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 "
|
|
2
|
-
import {
|
|
3
|
-
import "
|
|
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.
|
|
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 +
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 =
|
|
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:
|
|
33
|
-
height:
|
|
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
|
-
//
|
|
80
|
-
this.
|
|
81
|
-
Object.assign(this.
|
|
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.
|
|
77
|
+
this._el.appendChild(this._contentEl)
|
|
89
78
|
|
|
90
|
-
this.
|
|
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.
|
|
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 '
|
|
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 {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export {
|
|
21
|
-
export {
|
|
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"
|