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.
- package/README.md +1 -0
- package/package.json +16 -8
- package/src/axes/Axis.js +253 -0
- package/src/{AxisQuantityKindRegistry.js → axes/AxisQuantityKindRegistry.js} +7 -0
- package/src/{AxisRegistry.js → axes/AxisRegistry.js} +48 -0
- package/src/axes/ColorAxisRegistry.js +93 -0
- package/src/{FilterAxisRegistry.js → axes/FilterAxisRegistry.js} +63 -0
- package/src/axes/ZoomController.js +141 -0
- package/src/colorscales/BivariateColorscales.js +205 -0
- package/src/colorscales/ColorscaleRegistry.js +124 -0
- package/src/compute/ComputationRegistry.js +237 -0
- package/src/compute/axisFilter.js +47 -0
- package/src/compute/conv.js +230 -0
- package/src/compute/fft.js +292 -0
- package/src/compute/filter.js +227 -0
- package/src/compute/hist.js +180 -0
- package/src/compute/kde.js +102 -0
- package/src/{Layer.js → core/Layer.js} +4 -3
- package/src/{LayerType.js → core/LayerType.js} +72 -7
- package/src/core/Plot.js +735 -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} +17 -30
- package/src/{EpsgUtils.js → geo/EpsgUtils.js} +1 -1
- package/src/index.js +35 -22
- package/src/{ColorbarLayer.js → layers/ColorbarLayer.js} +2 -2
- package/src/layers/ColorbarLayer2d.js +97 -0
- package/src/{FilterbarLayer.js → layers/FilterbarLayer.js} +2 -2
- package/src/layers/HistogramLayer.js +212 -0
- package/src/layers/LinesLayer.js +199 -0
- package/src/layers/PointsLayer.js +114 -0
- package/src/layers/ScatterShared.js +142 -0
- package/src/{TileLayer.js → layers/TileLayer.js} +4 -4
- package/src/Axis.js +0 -48
- package/src/ColorAxisRegistry.js +0 -49
- package/src/ColorscaleRegistry.js +0 -52
- package/src/Float.js +0 -159
- package/src/Plot.js +0 -1068
- package/src/ScatterLayer.js +0 -133
- /package/src/{AxisLink.js → axes/AxisLink.js} +0 -0
- /package/src/{MatplotlibColorscales.js → colorscales/MatplotlibColorscales.js} +0 -0
- /package/src/{Data.js → core/Data.js} +0 -0
- /package/src/{LayerTypeRegistry.js → core/LayerTypeRegistry.js} +0 -0
|
@@ -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
|
}
|
|
@@ -149,9 +138,7 @@ export class FilterbarFloat {
|
|
|
149
138
|
|
|
150
139
|
destroy() {
|
|
151
140
|
this._cleanupInteraction()
|
|
152
|
-
this.
|
|
141
|
+
this._widget.destroy()
|
|
153
142
|
this._el.remove()
|
|
154
143
|
}
|
|
155
144
|
}
|
|
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,37 @@
|
|
|
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 {
|
|
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 } from "./core/Data.js"
|
|
4
|
+
export { AxisRegistry, AXES } from "./axes/AxisRegistry.js"
|
|
5
|
+
export { ColorAxisRegistry } from "./axes/ColorAxisRegistry.js"
|
|
6
|
+
export { FilterAxisRegistry, buildFilterGlsl } from "./axes/FilterAxisRegistry.js"
|
|
7
|
+
export { Plot } from "./core/Plot.js"
|
|
8
|
+
export { pointsLayerType } from "./layers/PointsLayer.js"
|
|
9
|
+
export { linesLayerType } from "./layers/LinesLayer.js"
|
|
10
|
+
export { registerLayerType, getLayerType, getRegisteredLayerTypes } from "./core/LayerTypeRegistry.js"
|
|
11
|
+
export { registerAxisQuantityKind, getAxisQuantityKind, getRegisteredAxisQuantityKinds } from "./axes/AxisQuantityKindRegistry.js"
|
|
12
|
+
export { registerColorscale, register2DColorscale, getRegisteredColorscales, getRegistered2DColorscales, getColorscaleIndex, get2DColorscaleIndex, buildColorGlsl } from "./colorscales/ColorscaleRegistry.js"
|
|
13
|
+
export { Axis } from "./axes/Axis.js"
|
|
14
|
+
export { linkAxes } from "./axes/AxisLink.js"
|
|
15
|
+
export { Colorbar } from "./floats/Colorbar.js"
|
|
16
|
+
export { colorbarLayerType } from "./layers/ColorbarLayer.js"
|
|
17
|
+
export { Colorbar2d } from "./floats/Colorbar2d.js"
|
|
18
|
+
export { colorbar2dLayerType } from "./layers/ColorbarLayer2d.js"
|
|
19
|
+
export { Float } from "./floats/Float.js"
|
|
20
|
+
export { Filterbar } from "./floats/Filterbar.js"
|
|
21
|
+
export { filterbarLayerType } from "./layers/FilterbarLayer.js"
|
|
22
|
+
export { tileLayerType, TileLayerType } from "./layers/TileLayer.js"
|
|
23
|
+
export { histogramLayerType } from "./layers/HistogramLayer.js"
|
|
24
|
+
export { registerEpsgDef, parseCrsCode, crsToQkX, crsToQkY, qkToEpsgCode, reproject } from "./geo/EpsgUtils.js"
|
|
25
|
+
export { Computation, TextureComputation, GlslComputation, EXPRESSION_REF, computationSchema, registerTextureComputation, registerGlslComputation, isTexture } from "./compute/ComputationRegistry.js"
|
|
22
26
|
|
|
23
27
|
// Register all matplotlib colorscales (side-effect import)
|
|
24
|
-
import "./MatplotlibColorscales.js"
|
|
28
|
+
import "./colorscales/MatplotlibColorscales.js"
|
|
29
|
+
import "./colorscales/BivariateColorscales.js"
|
|
30
|
+
|
|
31
|
+
// Register all compute texture computations (side-effect imports)
|
|
32
|
+
import "./compute/filter.js"
|
|
33
|
+
import "./compute/kde.js"
|
|
34
|
+
import "./compute/fft.js"
|
|
35
|
+
import "./compute/conv.js"
|
|
36
|
+
import "./compute/hist.js"
|
|
37
|
+
import "./compute/axisFilter.js"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { LayerType } from "
|
|
2
|
-
import { registerLayerType } from "
|
|
1
|
+
import { LayerType } from "../core/LayerType.js"
|
|
2
|
+
import { registerLayerType } from "../core/LayerTypeRegistry.js"
|
|
3
3
|
|
|
4
4
|
// Four vertices for a triangle-strip quad covering the entire clip space.
|
|
5
5
|
const quadCx = new Float32Array([-1, 1, -1, 1])
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { LayerType } from "../core/LayerType.js"
|
|
2
|
+
import { registerLayerType } from "../core/LayerTypeRegistry.js"
|
|
3
|
+
|
|
4
|
+
// Four vertices for a triangle-strip quad covering the entire clip space.
|
|
5
|
+
const quadCx = new Float32Array([-1, 1, -1, 1])
|
|
6
|
+
const quadCy = new Float32Array([-1, -1, 1, 1])
|
|
7
|
+
|
|
8
|
+
export const colorbar2dLayerType = new LayerType({
|
|
9
|
+
name: "colorbar2d",
|
|
10
|
+
|
|
11
|
+
getAxisConfig: function(parameters) {
|
|
12
|
+
const { xAxis, yAxis } = parameters
|
|
13
|
+
return {
|
|
14
|
+
xAxis: "xaxis_bottom",
|
|
15
|
+
xAxisQuantityKind: xAxis,
|
|
16
|
+
yAxis: "yaxis_left",
|
|
17
|
+
yAxisQuantityKind: yAxis,
|
|
18
|
+
colorAxisQuantityKinds: [xAxis, yAxis],
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
vert: `
|
|
23
|
+
precision mediump float;
|
|
24
|
+
attribute float cx;
|
|
25
|
+
attribute float cy;
|
|
26
|
+
varying float tval_x;
|
|
27
|
+
varying float tval_y;
|
|
28
|
+
void main() {
|
|
29
|
+
gl_Position = vec4(cx, cy, 0.0, 1.0);
|
|
30
|
+
tval_x = (cx + 1.0) / 2.0;
|
|
31
|
+
tval_y = (cy + 1.0) / 2.0;
|
|
32
|
+
}
|
|
33
|
+
`,
|
|
34
|
+
|
|
35
|
+
// tval_x/tval_y are [0,1] positions in the colorbar quad. We convert them to actual data
|
|
36
|
+
// values in each axis's range (undoing the log transform if needed), then pass those raw
|
|
37
|
+
// values to map_color_s_2d which re-applies the scale type internally. The exp() call
|
|
38
|
+
// is the inverse of the log() that map_color_s_2d will apply, so log-scale roundtrips
|
|
39
|
+
// correctly and linear-scale is a no-op (exp(log(v)) == v, but for linear vt == v anyway).
|
|
40
|
+
frag: `
|
|
41
|
+
precision mediump float;
|
|
42
|
+
uniform int colorscale_a;
|
|
43
|
+
uniform vec2 color_range_a;
|
|
44
|
+
uniform float color_scale_type_a;
|
|
45
|
+
uniform int colorscale_b;
|
|
46
|
+
uniform vec2 color_range_b;
|
|
47
|
+
uniform float color_scale_type_b;
|
|
48
|
+
varying float tval_x;
|
|
49
|
+
varying float tval_y;
|
|
50
|
+
void main() {
|
|
51
|
+
float r0_a = color_scale_type_a > 0.5 ? log(color_range_a.x) : color_range_a.x;
|
|
52
|
+
float r1_a = color_scale_type_a > 0.5 ? log(color_range_a.y) : color_range_a.y;
|
|
53
|
+
float vt_a = r0_a + tval_x * (r1_a - r0_a);
|
|
54
|
+
float v_a = color_scale_type_a > 0.5 ? exp(vt_a) : vt_a;
|
|
55
|
+
|
|
56
|
+
float r0_b = color_scale_type_b > 0.5 ? log(color_range_b.x) : color_range_b.x;
|
|
57
|
+
float r1_b = color_scale_type_b > 0.5 ? log(color_range_b.y) : color_range_b.y;
|
|
58
|
+
float vt_b = r0_b + tval_y * (r1_b - r0_b);
|
|
59
|
+
float v_b = color_scale_type_b > 0.5 ? exp(vt_b) : vt_b;
|
|
60
|
+
|
|
61
|
+
gl_FragColor = map_color_s_2d(
|
|
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
|
+
);
|
|
65
|
+
}
|
|
66
|
+
`,
|
|
67
|
+
|
|
68
|
+
schema: () => ({
|
|
69
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
70
|
+
type: "object",
|
|
71
|
+
properties: {
|
|
72
|
+
xAxis: { type: "string", description: "Quantity kind for the x axis (color axis A)" },
|
|
73
|
+
yAxis: { type: "string", description: "Quantity kind for the y axis (color axis B)" }
|
|
74
|
+
},
|
|
75
|
+
required: ["xAxis", "yAxis"]
|
|
76
|
+
}),
|
|
77
|
+
|
|
78
|
+
createLayer: function(parameters) {
|
|
79
|
+
const { xAxis, yAxis } = parameters
|
|
80
|
+
return [{
|
|
81
|
+
attributes: { cx: quadCx, cy: quadCy },
|
|
82
|
+
uniforms: {},
|
|
83
|
+
primitive: "triangle strip",
|
|
84
|
+
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
|
+
}]
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
registerLayerType("colorbar2d", colorbar2dLayerType)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { LayerType } from "
|
|
2
|
-
import { registerLayerType } from "
|
|
1
|
+
import { LayerType } from "../core/LayerType.js"
|
|
2
|
+
import { registerLayerType } from "../core/LayerTypeRegistry.js"
|
|
3
3
|
|
|
4
4
|
export const filterbarLayerType = new LayerType({
|
|
5
5
|
name: "filterbar",
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { LayerType } from "../core/LayerType.js"
|
|
2
|
+
import { Data } from "../core/Data.js"
|
|
3
|
+
import { registerLayerType } from "../core/LayerTypeRegistry.js"
|
|
4
|
+
import { AXES } from "../axes/AxisRegistry.js"
|
|
5
|
+
|
|
6
|
+
// Ensure the 'histogram' and 'filteredHistogram' texture computations are registered.
|
|
7
|
+
import "../compute/hist.js"
|
|
8
|
+
import "../compute/axisFilter.js"
|
|
9
|
+
|
|
10
|
+
// Each bar is a quad drawn as a triangle strip (4 vertices).
|
|
11
|
+
// Per-instance: x_center (bin centre in data space), a_pickId (bin index).
|
|
12
|
+
// Per-vertex: a_corner ∈ {0,1,2,3} — selects which corner of the rectangle.
|
|
13
|
+
// corner 0: bottom-left corner 1: bottom-right
|
|
14
|
+
// corner 2: top-left corner 3: top-right
|
|
15
|
+
// The `count` attribute is resolved via the 'histogram' texture computation so
|
|
16
|
+
// its value equals the bin count sampled at a_pickId.
|
|
17
|
+
|
|
18
|
+
const HIST_VERT = `
|
|
19
|
+
precision mediump float;
|
|
20
|
+
|
|
21
|
+
attribute float a_corner;
|
|
22
|
+
attribute float x_center;
|
|
23
|
+
attribute 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
|
+
|
|
31
|
+
void main() {
|
|
32
|
+
float side = mod(a_corner, 2.0); // 0 = left, 1 = right
|
|
33
|
+
float top = floor(a_corner / 2.0); // 0 = bottom, 1 = top
|
|
34
|
+
|
|
35
|
+
float bx = x_center + (side * 2.0 - 1.0) * u_binHalfWidth;
|
|
36
|
+
float by = top * count;
|
|
37
|
+
|
|
38
|
+
float nx = normalize_axis(bx, xDomain, xScaleType);
|
|
39
|
+
float ny = normalize_axis(by, yDomain, yScaleType);
|
|
40
|
+
gl_Position = vec4(nx * 2.0 - 1.0, ny * 2.0 - 1.0, 0.0, 1.0);
|
|
41
|
+
}
|
|
42
|
+
`
|
|
43
|
+
|
|
44
|
+
const HIST_FRAG = `
|
|
45
|
+
precision mediump float;
|
|
46
|
+
uniform vec4 u_color;
|
|
47
|
+
void main() {
|
|
48
|
+
gl_FragColor = gladly_apply_color(u_color);
|
|
49
|
+
}
|
|
50
|
+
`
|
|
51
|
+
|
|
52
|
+
class HistogramLayerType extends LayerType {
|
|
53
|
+
constructor() {
|
|
54
|
+
super({ name: "histogram", vert: HIST_VERT, frag: HIST_FRAG })
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_getAxisConfig(parameters, data) {
|
|
58
|
+
const d = Data.wrap(data)
|
|
59
|
+
const { vData, xAxis = "xaxis_bottom", yAxis = "yaxis_left", filterColumn } = parameters
|
|
60
|
+
const activeFilter = filterColumn && filterColumn !== "none" ? filterColumn : null
|
|
61
|
+
const filterQK = activeFilter ? (d.getQuantityKind(activeFilter) ?? activeFilter) : null
|
|
62
|
+
return {
|
|
63
|
+
xAxis,
|
|
64
|
+
xAxisQuantityKind: d.getQuantityKind(vData) ?? vData,
|
|
65
|
+
yAxis,
|
|
66
|
+
yAxisQuantityKind: "count",
|
|
67
|
+
...(filterQK ? { filterAxisQuantityKinds: [filterQK] } : {}),
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
schema(data) {
|
|
72
|
+
const dataProperties = Data.wrap(data).columns()
|
|
73
|
+
return {
|
|
74
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
75
|
+
type: "object",
|
|
76
|
+
properties: {
|
|
77
|
+
vData: {
|
|
78
|
+
type: "string",
|
|
79
|
+
enum: dataProperties,
|
|
80
|
+
description: "Data column to histogram"
|
|
81
|
+
},
|
|
82
|
+
filterColumn: {
|
|
83
|
+
type: "string",
|
|
84
|
+
enum: ["none", ...dataProperties],
|
|
85
|
+
description: "Data column used to filter points via its filter axis, or 'none'"
|
|
86
|
+
},
|
|
87
|
+
bins: {
|
|
88
|
+
type: "integer",
|
|
89
|
+
description: "Number of bins (auto-selected by sqrt rule if omitted)"
|
|
90
|
+
},
|
|
91
|
+
color: {
|
|
92
|
+
type: "array",
|
|
93
|
+
items: { type: "number" },
|
|
94
|
+
minItems: 4,
|
|
95
|
+
maxItems: 4,
|
|
96
|
+
default: [0.2, 0.5, 0.8, 1.0],
|
|
97
|
+
description: "Bar colour as [R, G, B, A] in [0, 1]"
|
|
98
|
+
},
|
|
99
|
+
xAxis: {
|
|
100
|
+
type: "string",
|
|
101
|
+
enum: AXES.filter(a => a.includes("x")),
|
|
102
|
+
default: "xaxis_bottom"
|
|
103
|
+
},
|
|
104
|
+
yAxis: {
|
|
105
|
+
type: "string",
|
|
106
|
+
enum: AXES.filter(a => a.includes("y")),
|
|
107
|
+
default: "yaxis_left"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
required: ["vData", "filterColumn"]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_createLayer(parameters, data) {
|
|
115
|
+
const d = Data.wrap(data)
|
|
116
|
+
const {
|
|
117
|
+
vData,
|
|
118
|
+
bins: requestedBins = null,
|
|
119
|
+
color = [0.2, 0.5, 0.8, 1.0],
|
|
120
|
+
filterColumn = "none",
|
|
121
|
+
} = parameters
|
|
122
|
+
|
|
123
|
+
const srcV = d.getData(vData)
|
|
124
|
+
if (!srcV) throw new Error(`Data column '${vData}' not found`)
|
|
125
|
+
const vQK = d.getQuantityKind(vData) ?? vData
|
|
126
|
+
|
|
127
|
+
// --- Optional filter column ---
|
|
128
|
+
const activeFilter = filterColumn && filterColumn !== "none" ? filterColumn : null
|
|
129
|
+
const filterQK = activeFilter ? (d.getQuantityKind(activeFilter) ?? activeFilter) : null
|
|
130
|
+
const srcF = activeFilter ? d.getData(activeFilter) : null
|
|
131
|
+
if (activeFilter && !srcF) throw new Error(`Data column '${activeFilter}' not found`)
|
|
132
|
+
|
|
133
|
+
// --- Compute min/max for normalization ---
|
|
134
|
+
let min = Infinity, max = -Infinity
|
|
135
|
+
for (let i = 0; i < srcV.length; i++) {
|
|
136
|
+
if (srcV[i] < min) min = srcV[i]
|
|
137
|
+
if (srcV[i] > max) max = srcV[i]
|
|
138
|
+
}
|
|
139
|
+
const range = max - min || 1
|
|
140
|
+
|
|
141
|
+
// --- Choose bin count ---
|
|
142
|
+
const bins = requestedBins || Math.max(10, Math.min(200, Math.ceil(Math.sqrt(srcV.length))))
|
|
143
|
+
const binWidth = range / bins
|
|
144
|
+
|
|
145
|
+
// --- Normalize data to [0, 1] for the histogram computation ---
|
|
146
|
+
const normalized = new Float32Array(srcV.length)
|
|
147
|
+
for (let i = 0; i < srcV.length; i++) {
|
|
148
|
+
normalized[i] = (srcV[i] - min) / range
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- CPU histogram for domain (y-axis range) estimation ---
|
|
152
|
+
// Uses unfiltered data so the y-axis scale stays stable while the filter moves.
|
|
153
|
+
const histCpu = new Float32Array(bins)
|
|
154
|
+
for (let i = 0; i < srcV.length; i++) {
|
|
155
|
+
const b = Math.min(Math.floor(normalized[i] * bins), bins - 1)
|
|
156
|
+
histCpu[b] += 1
|
|
157
|
+
}
|
|
158
|
+
const maxCount = Math.max(...histCpu)
|
|
159
|
+
|
|
160
|
+
// --- Per-instance: bin centre positions in data space ---
|
|
161
|
+
const x_center = new Float32Array(bins)
|
|
162
|
+
for (let i = 0; i < bins; i++) {
|
|
163
|
+
x_center[i] = min + (i + 0.5) * binWidth
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// --- Per-vertex: corner indices 0–3 (triangle-strip quad) ---
|
|
167
|
+
const a_corner = new Float32Array([0, 1, 2, 3])
|
|
168
|
+
|
|
169
|
+
// --- Build count attribute expression ---
|
|
170
|
+
// When a filter column is provided, use filteredHistogram so the computation
|
|
171
|
+
// re-runs whenever the filter axis domain changes.
|
|
172
|
+
const countAttr = filterQK
|
|
173
|
+
? { filteredHistogram: { input: normalized, filterValues: srcF, filterAxisId: filterQK, bins } }
|
|
174
|
+
: { histogram: { input: normalized, bins } }
|
|
175
|
+
|
|
176
|
+
// --- Compute filter column extent for the filterbar display range ---
|
|
177
|
+
const filterDomains = {}
|
|
178
|
+
if (filterQK && srcF) {
|
|
179
|
+
let fMin = Infinity, fMax = -Infinity
|
|
180
|
+
for (let i = 0; i < srcF.length; i++) {
|
|
181
|
+
if (srcF[i] < fMin) fMin = srcF[i]
|
|
182
|
+
if (srcF[i] > fMax) fMax = srcF[i]
|
|
183
|
+
}
|
|
184
|
+
filterDomains[filterQK] = [fMin, fMax]
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return [{
|
|
188
|
+
attributes: {
|
|
189
|
+
a_corner, // per-vertex (no divisor)
|
|
190
|
+
x_center, // per-instance (divisor 1)
|
|
191
|
+
// GPU histogram via computed attribute; sampled at a_pickId (= bin index)
|
|
192
|
+
count: countAttr,
|
|
193
|
+
},
|
|
194
|
+
attributeDivisors: { x_center: 1 },
|
|
195
|
+
uniforms: {
|
|
196
|
+
u_binHalfWidth: binWidth / 2,
|
|
197
|
+
u_color: color,
|
|
198
|
+
},
|
|
199
|
+
vertexCount: 4,
|
|
200
|
+
instanceCount: bins,
|
|
201
|
+
primitive: "triangle strip",
|
|
202
|
+
domains: {
|
|
203
|
+
[vQK]: [min, max],
|
|
204
|
+
count: [0, maxCount],
|
|
205
|
+
...filterDomains,
|
|
206
|
+
},
|
|
207
|
+
}]
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export const histogramLayerType = new HistogramLayerType()
|
|
212
|
+
registerLayerType("histogram", histogramLayerType)
|