gladly-plot 0.0.5 → 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 +320 -172
- package/src/axes/AxisLink.js +6 -2
- package/src/axes/AxisRegistry.js +116 -39
- package/src/axes/Camera.js +47 -0
- package/src/axes/ColorAxisRegistry.js +10 -2
- package/src/axes/FilterAxisRegistry.js +1 -1
- package/src/axes/TickLabelAtlas.js +99 -0
- package/src/axes/ZoomController.js +446 -124
- package/src/colorscales/ColorscaleRegistry.js +30 -10
- package/src/compute/ComputationRegistry.js +126 -184
- package/src/compute/axisFilter.js +21 -9
- package/src/compute/conv.js +64 -8
- package/src/compute/elementwise.js +72 -0
- package/src/compute/fft.js +106 -20
- package/src/compute/filter.js +105 -103
- package/src/compute/hist.js +247 -142
- package/src/compute/kde.js +64 -46
- 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 +22 -8
- package/src/core/LayerType.js +251 -92
- package/src/core/Plot.js +630 -152
- 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/floats/Float.js +56 -0
- package/src/index.js +16 -4
- package/src/layers/BarsLayer.js +168 -0
- package/src/layers/ColorbarLayer.js +10 -14
- package/src/layers/ColorbarLayer2d.js +13 -24
- package/src/layers/FilterbarLayer.js +4 -3
- package/src/layers/LinesLayer.js +108 -122
- package/src/layers/PointsLayer.js +73 -69
- package/src/layers/ScatterShared.js +62 -106
- package/src/layers/TileLayer.js +20 -16
- package/src/math/mat4.js +100 -0
- package/src/core/Data.js +0 -67
- package/src/layers/HistogramLayer.js +0 -212
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
|
+
}
|
package/src/floats/Float.js
CHANGED
|
@@ -128,11 +128,67 @@ export class Float {
|
|
|
128
128
|
document.addEventListener('mousemove', onMouseMove)
|
|
129
129
|
document.addEventListener('mouseup', onMouseUp)
|
|
130
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
|
+
|
|
131
182
|
this._cleanupInteraction = () => {
|
|
132
183
|
this._dragBar.removeEventListener('mousedown', onDragBarMouseDown)
|
|
133
184
|
this._resizeHandle.removeEventListener('mousedown', onResizeMouseDown)
|
|
134
185
|
document.removeEventListener('mousemove', onMouseMove)
|
|
135
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)
|
|
136
192
|
}
|
|
137
193
|
}
|
|
138
194
|
|
package/src/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
export { LayerType } from "./core/LayerType.js"
|
|
2
2
|
export { Layer } from "./core/Layer.js"
|
|
3
|
-
export { Data } from "./
|
|
4
|
-
export { AxisRegistry, AXES } from "./axes/AxisRegistry.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"
|
|
5
7
|
export { ColorAxisRegistry } from "./axes/ColorAxisRegistry.js"
|
|
6
8
|
export { FilterAxisRegistry, buildFilterGlsl } from "./axes/FilterAxisRegistry.js"
|
|
7
9
|
export { Plot } from "./core/Plot.js"
|
|
10
|
+
export { PlotGroup } from "./core/PlotGroup.js"
|
|
11
|
+
export { ComputePipeline, ComputeOutput } from "./core/ComputePipeline.js"
|
|
8
12
|
export { pointsLayerType } from "./layers/PointsLayer.js"
|
|
9
13
|
export { linesLayerType } from "./layers/LinesLayer.js"
|
|
10
14
|
export { registerLayerType, getLayerType, getRegisteredLayerTypes } from "./core/LayerTypeRegistry.js"
|
|
@@ -20,9 +24,11 @@ export { Float } from "./floats/Float.js"
|
|
|
20
24
|
export { Filterbar } from "./floats/Filterbar.js"
|
|
21
25
|
export { filterbarLayerType } from "./layers/FilterbarLayer.js"
|
|
22
26
|
export { tileLayerType, TileLayerType } from "./layers/TileLayer.js"
|
|
23
|
-
export {
|
|
27
|
+
export { barsLayerType } from "./layers/BarsLayer.js"
|
|
24
28
|
export { registerEpsgDef, parseCrsCode, crsToQkX, crsToQkY, qkToEpsgCode, reproject } from "./geo/EpsgUtils.js"
|
|
25
|
-
export { Computation, TextureComputation, GlslComputation,
|
|
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"
|
|
26
32
|
|
|
27
33
|
// Register all matplotlib colorscales (side-effect import)
|
|
28
34
|
import "./colorscales/MatplotlibColorscales.js"
|
|
@@ -35,3 +41,9 @@ import "./compute/fft.js"
|
|
|
35
41
|
import "./compute/conv.js"
|
|
36
42
|
import "./compute/hist.js"
|
|
37
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"
|
|
@@ -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)
|
|
@@ -7,6 +7,7 @@ const quadCy = new Float32Array([-1, -1, 1, 1])
|
|
|
7
7
|
|
|
8
8
|
export const colorbarLayerType = new LayerType({
|
|
9
9
|
name: "colorbar",
|
|
10
|
+
suppressWarnings: true,
|
|
10
11
|
|
|
11
12
|
getAxisConfig: function(parameters) {
|
|
12
13
|
const { colorAxis, orientation = "horizontal" } = parameters
|
|
@@ -15,33 +16,33 @@ export const colorbarLayerType = new LayerType({
|
|
|
15
16
|
xAxisQuantityKind: orientation === "horizontal" ? colorAxis : undefined,
|
|
16
17
|
yAxis: orientation === "vertical" ? "yaxis_left" : null,
|
|
17
18
|
yAxisQuantityKind: orientation === "vertical" ? colorAxis : undefined,
|
|
18
|
-
colorAxisQuantityKinds:
|
|
19
|
+
colorAxisQuantityKinds: { '': colorAxis },
|
|
19
20
|
}
|
|
20
21
|
},
|
|
21
22
|
|
|
22
|
-
vert:
|
|
23
|
+
vert: `#version 300 es
|
|
23
24
|
precision mediump float;
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
in float cx;
|
|
26
|
+
in float cy;
|
|
26
27
|
uniform int horizontal;
|
|
27
|
-
|
|
28
|
+
out float tval;
|
|
28
29
|
void main() {
|
|
29
30
|
gl_Position = vec4(cx, cy, 0.0, 1.0);
|
|
30
31
|
tval = horizontal == 1 ? (cx + 1.0) / 2.0 : (cy + 1.0) / 2.0;
|
|
31
32
|
}
|
|
32
33
|
`,
|
|
33
34
|
|
|
34
|
-
frag:
|
|
35
|
+
frag: `#version 300 es
|
|
35
36
|
precision mediump float;
|
|
36
37
|
uniform int colorscale;
|
|
37
38
|
uniform vec2 color_range;
|
|
38
39
|
uniform float color_scale_type;
|
|
39
|
-
|
|
40
|
+
in float tval;
|
|
40
41
|
void main() {
|
|
41
42
|
float r0 = color_scale_type > 0.5 ? log(color_range.x) : color_range.x;
|
|
42
43
|
float r1 = color_scale_type > 0.5 ? log(color_range.y) : color_range.y;
|
|
43
44
|
float v = r0 + tval * (r1 - r0);
|
|
44
|
-
|
|
45
|
+
fragColor = gladly_apply_color(map_color(colorscale, vec2(r0, r1), v));
|
|
45
46
|
}
|
|
46
47
|
`,
|
|
47
48
|
|
|
@@ -55,18 +56,13 @@ export const colorbarLayerType = new LayerType({
|
|
|
55
56
|
required: ["colorAxis"]
|
|
56
57
|
}),
|
|
57
58
|
|
|
58
|
-
createLayer: function(parameters) {
|
|
59
|
+
createLayer: function(regl, parameters) {
|
|
59
60
|
const { colorAxis, orientation = "horizontal" } = parameters
|
|
60
61
|
return [{
|
|
61
62
|
attributes: { cx: quadCx, cy: quadCy },
|
|
62
63
|
uniforms: { horizontal: orientation === "horizontal" ? 1 : 0 },
|
|
63
64
|
primitive: "triangle strip",
|
|
64
65
|
vertexCount: 4,
|
|
65
|
-
nameMap: {
|
|
66
|
-
[`colorscale_${colorAxis}`]: 'colorscale',
|
|
67
|
-
[`color_range_${colorAxis}`]: 'color_range',
|
|
68
|
-
[`color_scale_type_${colorAxis}`]: 'color_scale_type',
|
|
69
|
-
},
|
|
70
66
|
}]
|
|
71
67
|
}
|
|
72
68
|
})
|
|
@@ -7,6 +7,7 @@ const quadCy = new Float32Array([-1, -1, 1, 1])
|
|
|
7
7
|
|
|
8
8
|
export const colorbar2dLayerType = new LayerType({
|
|
9
9
|
name: "colorbar2d",
|
|
10
|
+
suppressWarnings: true,
|
|
10
11
|
|
|
11
12
|
getAxisConfig: function(parameters) {
|
|
12
13
|
const { xAxis, yAxis } = parameters
|
|
@@ -15,16 +16,17 @@ export const colorbar2dLayerType = new LayerType({
|
|
|
15
16
|
xAxisQuantityKind: xAxis,
|
|
16
17
|
yAxis: "yaxis_left",
|
|
17
18
|
yAxisQuantityKind: yAxis,
|
|
18
|
-
colorAxisQuantityKinds:
|
|
19
|
+
colorAxisQuantityKinds: { '_a': xAxis, '_b': yAxis },
|
|
20
|
+
colorAxis2dQuantityKinds: { '': ['_a', '_b'] },
|
|
19
21
|
}
|
|
20
22
|
},
|
|
21
23
|
|
|
22
|
-
vert:
|
|
24
|
+
vert: `#version 300 es
|
|
23
25
|
precision mediump float;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
in float cx;
|
|
27
|
+
in float cy;
|
|
28
|
+
out float tval_x;
|
|
29
|
+
out float tval_y;
|
|
28
30
|
void main() {
|
|
29
31
|
gl_Position = vec4(cx, cy, 0.0, 1.0);
|
|
30
32
|
tval_x = (cx + 1.0) / 2.0;
|
|
@@ -37,16 +39,14 @@ export const colorbar2dLayerType = new LayerType({
|
|
|
37
39
|
// values to map_color_s_2d which re-applies the scale type internally. The exp() call
|
|
38
40
|
// is the inverse of the log() that map_color_s_2d will apply, so log-scale roundtrips
|
|
39
41
|
// correctly and linear-scale is a no-op (exp(log(v)) == v, but for linear vt == v anyway).
|
|
40
|
-
frag:
|
|
42
|
+
frag: `#version 300 es
|
|
41
43
|
precision mediump float;
|
|
42
|
-
uniform int colorscale_a;
|
|
43
44
|
uniform vec2 color_range_a;
|
|
44
45
|
uniform float color_scale_type_a;
|
|
45
|
-
uniform int colorscale_b;
|
|
46
46
|
uniform vec2 color_range_b;
|
|
47
47
|
uniform float color_scale_type_b;
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
in float tval_x;
|
|
49
|
+
in float tval_y;
|
|
50
50
|
void main() {
|
|
51
51
|
float r0_a = color_scale_type_a > 0.5 ? log(color_range_a.x) : color_range_a.x;
|
|
52
52
|
float r1_a = color_scale_type_a > 0.5 ? log(color_range_a.y) : color_range_a.y;
|
|
@@ -58,10 +58,7 @@ export const colorbar2dLayerType = new LayerType({
|
|
|
58
58
|
float vt_b = r0_b + tval_y * (r1_b - r0_b);
|
|
59
59
|
float v_b = color_scale_type_b > 0.5 ? exp(vt_b) : vt_b;
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
colorscale_a, color_range_a, v_a, color_scale_type_a,
|
|
63
|
-
colorscale_b, color_range_b, v_b, color_scale_type_b
|
|
64
|
-
);
|
|
61
|
+
fragColor = map_color_2d_(vec2(v_a, v_b));
|
|
65
62
|
}
|
|
66
63
|
`,
|
|
67
64
|
|
|
@@ -75,21 +72,13 @@ export const colorbar2dLayerType = new LayerType({
|
|
|
75
72
|
required: ["xAxis", "yAxis"]
|
|
76
73
|
}),
|
|
77
74
|
|
|
78
|
-
createLayer: function(parameters) {
|
|
75
|
+
createLayer: function(regl, parameters) {
|
|
79
76
|
const { xAxis, yAxis } = parameters
|
|
80
77
|
return [{
|
|
81
78
|
attributes: { cx: quadCx, cy: quadCy },
|
|
82
79
|
uniforms: {},
|
|
83
80
|
primitive: "triangle strip",
|
|
84
81
|
vertexCount: 4,
|
|
85
|
-
nameMap: {
|
|
86
|
-
[`colorscale_${xAxis}`]: 'colorscale_a',
|
|
87
|
-
[`color_range_${xAxis}`]: 'color_range_a',
|
|
88
|
-
[`color_scale_type_${xAxis}`]: 'color_scale_type_a',
|
|
89
|
-
[`colorscale_${yAxis}`]: 'colorscale_b',
|
|
90
|
-
[`color_range_${yAxis}`]: 'color_range_b',
|
|
91
|
-
[`color_scale_type_${yAxis}`]: 'color_scale_type_b',
|
|
92
|
-
},
|
|
93
82
|
}]
|
|
94
83
|
}
|
|
95
84
|
})
|