glplot 0.1.0__tar.gz
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.
- glplot-0.1.0/LICENSE +0 -0
- glplot-0.1.0/PKG-INFO +75 -0
- glplot-0.1.0/README.md +55 -0
- glplot-0.1.0/examples/ex_density.py +34 -0
- glplot-0.1.0/examples/ex_density_gain.py +18 -0
- glplot-0.1.0/examples/ex_expert_performance.py +40 -0
- glplot-0.1.0/examples/ex_full_showcase.py +85 -0
- glplot-0.1.0/examples/ex_mpl_bridge.py +65 -0
- glplot-0.1.0/examples/ex_scatter.py +39 -0
- glplot-0.1.0/examples/ex_simple.py +19 -0
- glplot-0.1.0/examples/ex_v2_layers.py +56 -0
- glplot-0.1.0/examples/ex_viewports.py +44 -0
- glplot-0.1.0/examples/verify_polyline_cmap.py +32 -0
- glplot-0.1.0/glplot/__init__.py +6 -0
- glplot-0.1.0/glplot/backend.py +15 -0
- glplot-0.1.0/glplot/controllers.py +69 -0
- glplot-0.1.0/glplot/core/__init__.py +0 -0
- glplot-0.1.0/glplot/core/context.py +50 -0
- glplot-0.1.0/glplot/core/layers.py +163 -0
- glplot-0.1.0/glplot/core/legacy.py +98 -0
- glplot-0.1.0/glplot/engine.py +1270 -0
- glplot-0.1.0/glplot/managers/__init__.py +0 -0
- glplot-0.1.0/glplot/managers/axis.py +66 -0
- glplot-0.1.0/glplot/managers/effects.py +343 -0
- glplot-0.1.0/glplot/managers/hud.py +510 -0
- glplot-0.1.0/glplot/managers/hud_state.py +95 -0
- glplot-0.1.0/glplot/managers/picking.py +174 -0
- glplot-0.1.0/glplot/managers/renderer_manager.py +158 -0
- glplot-0.1.0/glplot/options.py +120 -0
- glplot-0.1.0/glplot/policy.py +108 -0
- glplot-0.1.0/glplot/pyplot.py +735 -0
- glplot-0.1.0/glplot/renderers/__init__.py +0 -0
- glplot-0.1.0/glplot/renderers/axis.py +126 -0
- glplot-0.1.0/glplot/renderers/base.py +40 -0
- glplot-0.1.0/glplot/renderers/density.py +120 -0
- glplot-0.1.0/glplot/renderers/exact.py +215 -0
- glplot-0.1.0/glplot/renderers/interaction.py +77 -0
- glplot-0.1.0/glplot/renderers/line_family.py +250 -0
- glplot-0.1.0/glplot/renderers/patch.py +149 -0
- glplot-0.1.0/glplot/renderers/polyline.py +230 -0
- glplot-0.1.0/glplot/renderers/scatter.py +185 -0
- glplot-0.1.0/glplot/renderers/text.py +72 -0
- glplot-0.1.0/glplot/scratch/__init__.py +0 -0
- glplot-0.1.0/glplot/utils/__init__.py +0 -0
- glplot-0.1.0/glplot/utils/export.py +112 -0
- glplot-0.1.0/glplot/utils/gl_utils.py +32 -0
- glplot-0.1.0/glplot/utils/mpl_bridge.py +60 -0
- glplot-0.1.0/glplot/utils/shaders.py +889 -0
- glplot-0.1.0/imgui.ini +55 -0
- glplot-0.1.0/old/backend.py +1615 -0
- glplot-0.1.0/pyproject.toml +36 -0
- glplot-0.1.0/scratch/check_gl_limits.py +34 -0
- glplot-0.1.0/scratch/check_imgui.py +6 -0
- glplot-0.1.0/scratch/diag_view.py +54 -0
- glplot-0.1.0/scratch/smoke_test.py +21 -0
- glplot-0.1.0/scratch/test_modular_export.py +31 -0
- glplot-0.1.0/tests/test_backend.py +134 -0
- glplot-0.1.0/tests/test_pyplot.py +84 -0
- glplot-0.1.0/tests/verify_blending_extension.py +44 -0
- glplot-0.1.0/tests/verify_density.py +26 -0
- glplot-0.1.0/tests/verify_headless.py +26 -0
- glplot-0.1.0/tests/verify_phase4.py +31 -0
- glplot-0.1.0/tests/verify_phase5_density.py +41 -0
glplot-0.1.0/LICENSE
ADDED
|
File without changes
|
glplot-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: glplot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: High-performance OpenGL plotting library, similar to Matplotlib
|
|
5
|
+
Author-email: Juan Manuel Lombardi <lombardi@fhi-berlin.mpg.de>
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Requires-Dist: glfw
|
|
9
|
+
Requires-Dist: imgui
|
|
10
|
+
Requires-Dist: imgui[glfw]
|
|
11
|
+
Requires-Dist: matplotlib
|
|
12
|
+
Requires-Dist: numpy
|
|
13
|
+
Requires-Dist: pyopengl
|
|
14
|
+
Requires-Dist: scipy
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: pytest; extra == 'test'
|
|
17
|
+
Requires-Dist: pytest-cov; extra == 'test'
|
|
18
|
+
Requires-Dist: pytest-mock; extra == 'test'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# GLPlot
|
|
22
|
+
|
|
23
|
+
High-performance, GPU-accelerated plotting library in Python, designed to handle **millions** of lines effortlessly. It provides an API similar to Matplotlib but runs natively over an OpenGL/GLFW backend, performing instanced rendering directly on the GPU.
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
- **Matplotlib API Compatibility**: Familiar `plot(x, y)`, `show()`, `savefig()`.
|
|
27
|
+
- **Phase Diagram Optimized (`plot_lines`)**: Explicitly supports passing millions of line parameters $(a, b)$ to calculate functions $y = ax + b$ securely bounded to bounds using shader math.
|
|
28
|
+
- **Logarithmic Density Heaps**: By displaying overlaps, `density=True` handles millions of parallel curves seamlessly for heatmaps.
|
|
29
|
+
- **Dynamic Camera**: Drag to pan, scroll to zoom with on-the-fly resolution subsampling.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
You can install this locally:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install .
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Requirements: `numpy`, `glfw`, `PyOpenGL`, `scipy`, `matplotlib`.
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
**Traditional Polylines**
|
|
44
|
+
```python
|
|
45
|
+
import numpy as np
|
|
46
|
+
import glplot.pyplot as gplt
|
|
47
|
+
|
|
48
|
+
x = np.linspace(0, 10, 100)
|
|
49
|
+
y = np.sin(x)
|
|
50
|
+
|
|
51
|
+
gplt.figure("Sine Wave")
|
|
52
|
+
gplt.plot(x, y, color=(1.0, 0.0, 0.0, 1.0))
|
|
53
|
+
gplt.show()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Bulk Lines (Density Map)**
|
|
57
|
+
```python
|
|
58
|
+
import numpy as np
|
|
59
|
+
import glplot.pyplot as gplt
|
|
60
|
+
|
|
61
|
+
N = 1000000
|
|
62
|
+
a = np.random.randn(N)
|
|
63
|
+
b = np.random.randn(N)
|
|
64
|
+
|
|
65
|
+
gplt.figure("Density")
|
|
66
|
+
gplt.plot_lines(a, b, x_range=(-2, 2))
|
|
67
|
+
gplt.show(density=True)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Testing
|
|
71
|
+
|
|
72
|
+
Uses `pytest` covering 100% of the internal application logic without popping visible windows:
|
|
73
|
+
```bash
|
|
74
|
+
pytest
|
|
75
|
+
```
|
glplot-0.1.0/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# GLPlot
|
|
2
|
+
|
|
3
|
+
High-performance, GPU-accelerated plotting library in Python, designed to handle **millions** of lines effortlessly. It provides an API similar to Matplotlib but runs natively over an OpenGL/GLFW backend, performing instanced rendering directly on the GPU.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- **Matplotlib API Compatibility**: Familiar `plot(x, y)`, `show()`, `savefig()`.
|
|
7
|
+
- **Phase Diagram Optimized (`plot_lines`)**: Explicitly supports passing millions of line parameters $(a, b)$ to calculate functions $y = ax + b$ securely bounded to bounds using shader math.
|
|
8
|
+
- **Logarithmic Density Heaps**: By displaying overlaps, `density=True` handles millions of parallel curves seamlessly for heatmaps.
|
|
9
|
+
- **Dynamic Camera**: Drag to pan, scroll to zoom with on-the-fly resolution subsampling.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
You can install this locally:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install .
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requirements: `numpy`, `glfw`, `PyOpenGL`, `scipy`, `matplotlib`.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
**Traditional Polylines**
|
|
24
|
+
```python
|
|
25
|
+
import numpy as np
|
|
26
|
+
import glplot.pyplot as gplt
|
|
27
|
+
|
|
28
|
+
x = np.linspace(0, 10, 100)
|
|
29
|
+
y = np.sin(x)
|
|
30
|
+
|
|
31
|
+
gplt.figure("Sine Wave")
|
|
32
|
+
gplt.plot(x, y, color=(1.0, 0.0, 0.0, 1.0))
|
|
33
|
+
gplt.show()
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Bulk Lines (Density Map)**
|
|
37
|
+
```python
|
|
38
|
+
import numpy as np
|
|
39
|
+
import glplot.pyplot as gplt
|
|
40
|
+
|
|
41
|
+
N = 1000000
|
|
42
|
+
a = np.random.randn(N)
|
|
43
|
+
b = np.random.randn(N)
|
|
44
|
+
|
|
45
|
+
gplt.figure("Density")
|
|
46
|
+
gplt.plot_lines(a, b, x_range=(-2, 2))
|
|
47
|
+
gplt.show(density=True)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Testing
|
|
51
|
+
|
|
52
|
+
Uses `pytest` covering 100% of the internal application logic without popping visible windows:
|
|
53
|
+
```bash
|
|
54
|
+
pytest
|
|
55
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import glplot.pyplot as gplt
|
|
3
|
+
|
|
4
|
+
# Generate 10 million lines y = a*x + b
|
|
5
|
+
N = 1_000_000
|
|
6
|
+
print(f"Generating {N} lines. This tests memory throughput and GPU geometry performance.")
|
|
7
|
+
a = np.random.randn(N) * 2.0
|
|
8
|
+
b = np.random.randn(N) * 0.5
|
|
9
|
+
|
|
10
|
+
# Assign a random color with transparency
|
|
11
|
+
colors = np.random.rand(N, 4)
|
|
12
|
+
colors[:, 3] = 0.05 # alpha
|
|
13
|
+
|
|
14
|
+
# Configure figure with optimizations
|
|
15
|
+
gplt.figure("Massive Density Map (10M Lines)",
|
|
16
|
+
width=1280, height=800,
|
|
17
|
+
density=True,
|
|
18
|
+
lod=True,
|
|
19
|
+
budget=200, # High budget for dense detail
|
|
20
|
+
clipping=True, hud=True)
|
|
21
|
+
|
|
22
|
+
# Performance Tuning: Render density at 0.5x resolution for massive speedup
|
|
23
|
+
# This is ideal when N is very large or GPU is limited.
|
|
24
|
+
gplt.options(density_resolution_scale=0.5, density_gain=1.5)
|
|
25
|
+
|
|
26
|
+
gplt.plot_lines(a, b, x_range=(-5, 5), colors=colors)
|
|
27
|
+
|
|
28
|
+
gplt.text(-4.5, 4.0, "10,000,000 Lines", fontsize=32, color="white")
|
|
29
|
+
gplt.text(-4.5, 3.5, "Optimized: 0.5x Resolution Scale", fontsize=20, color="gray")
|
|
30
|
+
|
|
31
|
+
print("\nControls:")
|
|
32
|
+
print(" [ D ] : Toggle Density/Exact view.")
|
|
33
|
+
print(" [ UP/DOWN ] : Adjust density gain.")
|
|
34
|
+
gplt.show()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import glplot.pyplot as gplt
|
|
3
|
+
|
|
4
|
+
def demo_density_gain():
|
|
5
|
+
n = 1000000
|
|
6
|
+
a = np.random.randn(n) * 0.1
|
|
7
|
+
b = np.random.randn(n) * 0.1
|
|
8
|
+
|
|
9
|
+
# Pre-set some gain
|
|
10
|
+
gplt.density_gain(15.0)
|
|
11
|
+
gplt.figure("Density Gain Test", density=True, hud=True)
|
|
12
|
+
gplt.lines(a, b, x_range=(-1, 1), color='yellow')
|
|
13
|
+
|
|
14
|
+
print("Check if the 'Density Factor' slider appears in the HUD and affects the heatmap.")
|
|
15
|
+
gplt.show()
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
demo_density_gain()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import glplot.pyplot as gplt
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
# Stress Test: 50,000,000 lines
|
|
6
|
+
N = 50_000_000
|
|
7
|
+
print(f"STRESS TEST: Generating {N:,} lines. This will consume ~400MB of GPU memory.")
|
|
8
|
+
|
|
9
|
+
# Generate data clusters
|
|
10
|
+
a = np.random.normal(0, 1.0, N)
|
|
11
|
+
b = np.random.normal(0, 1.0, N)
|
|
12
|
+
|
|
13
|
+
# Optimization Setup
|
|
14
|
+
# We enable:
|
|
15
|
+
# - HUD: To monitor performance (FPS, budgets)
|
|
16
|
+
# - Budgeting: To ensure interactivity even during massive draw calls
|
|
17
|
+
# - Clipping: To avoid drawing lines outside the view
|
|
18
|
+
gplt.figure("Expert Performance Stress Test (50M Lines)",
|
|
19
|
+
hud=True,
|
|
20
|
+
budget=5, # Very aggressive budget for fluid interaction
|
|
21
|
+
clipping=True,
|
|
22
|
+
cache=True)
|
|
23
|
+
|
|
24
|
+
# Advanced Tuning: Fine-tune the Hybrid Cache
|
|
25
|
+
# - Increase refresh rate for more up-to-date impostors
|
|
26
|
+
# - Increase padding to reduce refresh frequency during slow pans
|
|
27
|
+
gplt.options(
|
|
28
|
+
cache_refresh_hz=60.0,
|
|
29
|
+
cache_padding=4.0,
|
|
30
|
+
default_global_alpha=0.1
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
gplt.plot_lines(a, b, x_range=(-5, 5))
|
|
34
|
+
|
|
35
|
+
print("\n--- PERFORMANCE GUIDE ---")
|
|
36
|
+
print("1. Interactive Path: Drag the mouse. The engine uses a reprojected cache.")
|
|
37
|
+
print("2. Exact Path: Release the mouse. The engine draws the exact geometry (LOD).")
|
|
38
|
+
print("3. HUD: Observe the FPS and Line Count in the top-left overlay.")
|
|
39
|
+
|
|
40
|
+
gplt.show()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import time
|
|
3
|
+
import math
|
|
4
|
+
import glplot.pyplot as gplt
|
|
5
|
+
|
|
6
|
+
def generate_flow_field(n=200_000):
|
|
7
|
+
"""Generate a vector field of lines."""
|
|
8
|
+
ab = np.zeros((n, 2), dtype=np.float32)
|
|
9
|
+
# y = ax + b
|
|
10
|
+
a = np.random.uniform(-0.1, 0.1, n).astype(np.float32)
|
|
11
|
+
b = np.random.normal(0, 1.5, n).astype(np.float32)
|
|
12
|
+
xr = (-5.0, 5.0)
|
|
13
|
+
|
|
14
|
+
# Colors based on slope/intensity
|
|
15
|
+
cols = np.ones((n, 4), dtype=np.float32)
|
|
16
|
+
cols[:, 0] = 0.2 + 0.8 * (a + 0.1) / 0.2 # R based on slope
|
|
17
|
+
cols[:, 1] = 0.7 + 0.3 * np.random.rand(n) # G
|
|
18
|
+
cols[:, 2] = 0.9 # B
|
|
19
|
+
cols[:, 3] = 0.15 # Low alpha for layering
|
|
20
|
+
|
|
21
|
+
return a, b, xr, cols
|
|
22
|
+
|
|
23
|
+
def generate_particles(m=10_000):
|
|
24
|
+
"""Generate random stars/particles."""
|
|
25
|
+
pts = np.random.normal(0, 2.5, (m, 2)).astype(np.float32)
|
|
26
|
+
# Subtle color variations
|
|
27
|
+
cols = np.ones((m, 4), dtype=np.float32)
|
|
28
|
+
cols[:, 0] = 1.0 # White/Redish
|
|
29
|
+
cols[:, 1] = 0.9
|
|
30
|
+
cols[:, 2] = 1.0
|
|
31
|
+
return pts, cols
|
|
32
|
+
|
|
33
|
+
def generate_spiral(k=2000):
|
|
34
|
+
"""Generate a high-res spiral trajectory."""
|
|
35
|
+
t = np.linspace(0, 20 * math.pi, k)
|
|
36
|
+
x = t * np.cos(t) / 10.0
|
|
37
|
+
y = t * np.sin(t) / 10.0
|
|
38
|
+
return x.astype(np.float32), y.astype(np.float32)
|
|
39
|
+
|
|
40
|
+
def showcase_demo():
|
|
41
|
+
print("Preparing GLPlot Full Showcase Demo...")
|
|
42
|
+
|
|
43
|
+
# 1. Background Flow Field (Millions of Lines)
|
|
44
|
+
a, b, xr, cols = generate_flow_field(250_000)
|
|
45
|
+
# Use a higher alpha since we have dark background
|
|
46
|
+
cols[:, 3] = 0.4 # Higher base alpha
|
|
47
|
+
gplt.plot_lines(a, b, xr, colors=cols)
|
|
48
|
+
|
|
49
|
+
# 2. Particle Cloud (Scatter)
|
|
50
|
+
pts, pcols = generate_particles(20_000)
|
|
51
|
+
gplt.scatter(pts[:, 0], pts[:, 1], size=6.0, color=(1, 1, 1, 0.9))
|
|
52
|
+
|
|
53
|
+
# 3. Primary Trajectories (Polylines)
|
|
54
|
+
sx, sy = generate_spiral(2000)
|
|
55
|
+
gplt.plot(sx, sy, color=(0.4, 1.0, 0.6, 1.0)) # Neon Green
|
|
56
|
+
gplt.plot(sx * 1.2, sy * 1.2, color=(1.0, 0.4, 0.8, 0.8)) # Pink
|
|
57
|
+
|
|
58
|
+
# 4. Global Settings & Aesthetics
|
|
59
|
+
gplt.title("GLPlot Core V1 Showcase")
|
|
60
|
+
|
|
61
|
+
# Set premium visuals
|
|
62
|
+
plot = gplt._get_or_create_plot()
|
|
63
|
+
plot.autoscale() # Ensure we see the data
|
|
64
|
+
|
|
65
|
+
# Disable aggressive alpha scaling for this showcase to keep it bold
|
|
66
|
+
plot.options.default_global_alpha = 1.0
|
|
67
|
+
|
|
68
|
+
v = plot.options.visual
|
|
69
|
+
|
|
70
|
+
# Neon Bloom - Lower threshold to capture the transparent lines
|
|
71
|
+
#v.glow.enabled = True
|
|
72
|
+
#v.glow.intensity = 2.0
|
|
73
|
+
#v.glow.threshold = 0.05 # Lower to see more glow from faint lines
|
|
74
|
+
#v.glow.radius_px = 6.0
|
|
75
|
+
|
|
76
|
+
# Deep Dark Nebula Gradient (Slightly brighter than before)
|
|
77
|
+
v.gradient_background.enabled = True
|
|
78
|
+
v.gradient_background.top_color = (0.08, 0.08, 0.25)
|
|
79
|
+
v.gradient_background.bottom_color = (0.02, 0.02, 0.05)
|
|
80
|
+
|
|
81
|
+
print("Showcase Ready (High Visibility Mode). Opening Window...")
|
|
82
|
+
gplt.show()
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
showcase_demo()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import glplot.pyplot as gplt
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
|
|
5
|
+
def run_bridge_example():
|
|
6
|
+
# 1. Generate some interesting data (Sine wave with noise)
|
|
7
|
+
n_lines = 100
|
|
8
|
+
x = np.linspace(-10, 10, 1000)
|
|
9
|
+
|
|
10
|
+
# Create multiple lines with varying amplitude and phase
|
|
11
|
+
for i in range(n_lines):
|
|
12
|
+
phase = i * 0.1
|
|
13
|
+
amp = 1.0 + np.sin(i * 0.2)
|
|
14
|
+
y = amp * np.sin(x + phase) + np.random.normal(0, 0.05, len(x))
|
|
15
|
+
gplt.plot(x, y, label=f"Line {i}")
|
|
16
|
+
|
|
17
|
+
print("\n--- GLPlot with Matplotlib Bridge ---")
|
|
18
|
+
print("Instructions:")
|
|
19
|
+
print("1. An interactive GLPlot window will open.")
|
|
20
|
+
print("2. Adjust the view (zoom/pan) to your liking.")
|
|
21
|
+
print("3. Press 'M' on your keyboard to 'Teleport' the view to Matplotlib.")
|
|
22
|
+
print("4. Or, see the code below for programmatic transfer.")
|
|
23
|
+
|
|
24
|
+
# 2. Get the engine instance
|
|
25
|
+
plot = gplt.get_engine()
|
|
26
|
+
|
|
27
|
+
# You can configure where 'M' sends the data if you want:
|
|
28
|
+
# plot.set_matplotlib_transfer_target(ax=some_existing_ax)
|
|
29
|
+
|
|
30
|
+
# 3. Show the interactive plot
|
|
31
|
+
gplt.show()
|
|
32
|
+
|
|
33
|
+
# --- Programmatic Transfer Example ---
|
|
34
|
+
# After you close the GLPlot window, or if running in a script:
|
|
35
|
+
print("\nProgrammatically transferring current view to Matplotlib...")
|
|
36
|
+
|
|
37
|
+
# We create a Matplotlib figure with subplots
|
|
38
|
+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
|
|
39
|
+
|
|
40
|
+
# Transfer view to the first subplot
|
|
41
|
+
plot.to_matplotlib(
|
|
42
|
+
ax=ax1,
|
|
43
|
+
scale=2.0, # High-res capture
|
|
44
|
+
transparent=True,
|
|
45
|
+
include_axes=False # We want Matplotlib to handle the axes
|
|
46
|
+
)
|
|
47
|
+
ax1.set_title("Transferred Raster (High Res)")
|
|
48
|
+
ax1.set_xlabel("Time (s)")
|
|
49
|
+
ax1.set_ylabel("Amplitude")
|
|
50
|
+
ax1.grid(True, alpha=0.3)
|
|
51
|
+
|
|
52
|
+
# We can even add Matplotlib geometry ON TOP of the GLPlot raster
|
|
53
|
+
x_mark = np.linspace(-10, 10, 10)
|
|
54
|
+
ax2.plot(x_mark, np.cos(x_mark), 'ro--', label="MPL Overlay")
|
|
55
|
+
|
|
56
|
+
# Transfer the same view to the second subplot as a background
|
|
57
|
+
plot.to_matplotlib(ax=ax2, scale=1.0, alpha=0.5) # Semi-transparent background
|
|
58
|
+
ax2.set_title("With Matplotlib Overlays")
|
|
59
|
+
ax2.legend()
|
|
60
|
+
|
|
61
|
+
plt.tight_layout()
|
|
62
|
+
plt.show()
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
run_bridge_example()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import glplot.pyplot as gplt
|
|
3
|
+
|
|
4
|
+
# Generate 5 million points
|
|
5
|
+
N = 5_000_000
|
|
6
|
+
print(f"Generating {N} points cloud...")
|
|
7
|
+
|
|
8
|
+
# Clusters
|
|
9
|
+
x = np.concatenate([
|
|
10
|
+
np.random.normal(-2, 1.0, N//4),
|
|
11
|
+
np.random.normal(2, 0.5, N//4),
|
|
12
|
+
np.random.normal(0, 3.0, N//2) # Noise
|
|
13
|
+
])
|
|
14
|
+
y = np.concatenate([
|
|
15
|
+
np.random.normal(1, 0.5, N//4),
|
|
16
|
+
np.random.normal(-2, 1.0, N//4),
|
|
17
|
+
np.random.normal(0, 3.0, N//2)
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
# Per-point RGBA colors
|
|
21
|
+
colors = np.random.rand(N, 4).astype(np.float32)
|
|
22
|
+
colors[:, 3] = 0.1 # Very transparent for blending
|
|
23
|
+
|
|
24
|
+
# Figure with multi-sampling (AA) and HUD
|
|
25
|
+
gplt.figure("Optimized Scatter (5M Points)",
|
|
26
|
+
width=1280, height=800,
|
|
27
|
+
multisample=True,
|
|
28
|
+
hud=True,
|
|
29
|
+
lod=True,
|
|
30
|
+
budget=100)
|
|
31
|
+
|
|
32
|
+
gplt.scatter(x, y, color=colors, size=2.0)
|
|
33
|
+
|
|
34
|
+
gplt.text(-4, 4, "5,000,000 Points", fontsize=28, color="black")
|
|
35
|
+
gplt.text(-4, 3.5, "SDF Mathematical Primitives", fontsize=18, color="blue")
|
|
36
|
+
|
|
37
|
+
print("\nNotice how panning remains locked at 60 FPS.")
|
|
38
|
+
print("The Hybrid Cache (on by default) allows fluid movement through millions of points.")
|
|
39
|
+
gplt.show()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import glplot.pyplot as gplt
|
|
3
|
+
|
|
4
|
+
# Simple sequence example
|
|
5
|
+
x = np.linspace(0, 10, 100)
|
|
6
|
+
y = np.sin(x)
|
|
7
|
+
|
|
8
|
+
gplt.figure("Simple Plot Demo")
|
|
9
|
+
|
|
10
|
+
# Activate adaptive subsampling for massive plots in standard sequences
|
|
11
|
+
gplt.set_lod(enabled=True, max_lines_per_px=200)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
for n in range(10000):
|
|
15
|
+
gplt.plot(x, y + float(n)**2/100000.0, color=(1.0, 0.0, 0.0, 1.0)) # Red line
|
|
16
|
+
gplt.scatter(x, y+ float(n)**2/100000.0, color=(0.0, 0.0, 1.0, 1.0), size=12.0) # Blue points
|
|
17
|
+
|
|
18
|
+
gplt.set_global_alpha(1.0)
|
|
19
|
+
gplt.show()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from glplot import GPULinePlot
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
def run_verification():
|
|
6
|
+
plot = GPULinePlot(title="GLPlot V2: Layer Architecture Verification")
|
|
7
|
+
|
|
8
|
+
# 1. Background Filled Area (PatchLayer)
|
|
9
|
+
# Sinusoidal band
|
|
10
|
+
x = np.linspace(0, 10, 200)
|
|
11
|
+
y1 = np.sin(x) - 0.5
|
|
12
|
+
y2 = np.sin(x) + 0.5
|
|
13
|
+
|
|
14
|
+
# Pre-calculate triangle strip vertices for fill_between
|
|
15
|
+
vertices = []
|
|
16
|
+
for i in range(len(x)):
|
|
17
|
+
vertices.append([x[i], y1[i]])
|
|
18
|
+
vertices.append([x[i], y2[i]])
|
|
19
|
+
vertices = np.array(vertices, dtype=np.float32)
|
|
20
|
+
|
|
21
|
+
plot.add_patch(vertices, mode="strip", face_color=(0.2, 0.4, 0.8, 0.3), label="Sinusoidal Band")
|
|
22
|
+
|
|
23
|
+
# 2. Scatter with Outlines
|
|
24
|
+
n_scat = 100
|
|
25
|
+
xs = np.random.uniform(0, 10, n_scat)
|
|
26
|
+
ys = np.random.uniform(-2, 2, n_scat)
|
|
27
|
+
colors = np.ones((n_scat, 4), dtype=np.float32)
|
|
28
|
+
colors[:, 0] = np.random.rand(n_scat) # Random red
|
|
29
|
+
colors[:, 2] = np.random.rand(n_scat) # Random blue
|
|
30
|
+
|
|
31
|
+
plot.add_scatter(xs, ys, colors, size=12.0)
|
|
32
|
+
# Note: We can fine-tune outlines via the layer object afterward
|
|
33
|
+
scat_layer = plot.scene.layers[-1]
|
|
34
|
+
scat_layer.label = "Research Points"
|
|
35
|
+
scat_layer.style.point_outline_enabled = True
|
|
36
|
+
scat_layer.style.point_outline_color = (0, 0, 0, 1)
|
|
37
|
+
scat_layer.style.point_outline_width = 1.5
|
|
38
|
+
|
|
39
|
+
# 3. Polyline (Thick curve)
|
|
40
|
+
xp = np.linspace(0, 10, 100)
|
|
41
|
+
yp = np.cos(xp) * 1.5
|
|
42
|
+
plot.add_line_strip(xp, yp, color=(1, 0.2, 0.1, 1), width=4.0)
|
|
43
|
+
plot.scene.layers[-1].label = "Main Signal"
|
|
44
|
+
|
|
45
|
+
# 4. Text Labels
|
|
46
|
+
plot.add_text(5, 1.8, "Upper Bound", fontsize=15, color=(1,1,1,1))
|
|
47
|
+
plot.add_text(2, -1.8, "Minima reached", fontsize=12, color=(0.8, 0.1, 0.1, 1))
|
|
48
|
+
|
|
49
|
+
# 5. Global Overrides Testing
|
|
50
|
+
# We can set default overrides here
|
|
51
|
+
plot.options.visual.overrides.alpha_multiplier = 0.9
|
|
52
|
+
|
|
53
|
+
plot.run()
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
run_verification()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import glplot.pyplot as gplt
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
def demo_viewports_and_performance():
|
|
6
|
+
"""
|
|
7
|
+
Verification script for deterministic viewports and mass-scale performance.
|
|
8
|
+
"""
|
|
9
|
+
print("--- GLPlot Performance & Viewport Test ---")
|
|
10
|
+
|
|
11
|
+
# 1. Dataset: 500,000 Polylines (Total 2.5M segments)
|
|
12
|
+
# This specifically tests the new GPU Instanced PolylineRenderer
|
|
13
|
+
n_curves = 100
|
|
14
|
+
n_points = 5000
|
|
15
|
+
x = np.linspace(-10, 10, n_points)
|
|
16
|
+
|
|
17
|
+
print(f"Loading {n_curves} high-density polylines ({n_curves * n_points} points)...")
|
|
18
|
+
for i in range(n_curves):
|
|
19
|
+
offset = i * 0.1
|
|
20
|
+
y = np.sin(x + offset) + np.random.normal(0, 0.05, n_points)
|
|
21
|
+
color = (0.2, 0.5, 0.8, 0.4)
|
|
22
|
+
gplt.plot(x, y, color=color, width=1.5, label=f"Curve {i}")
|
|
23
|
+
|
|
24
|
+
# 2. Dataset: 1,000,000 Line Family (High-performance path)
|
|
25
|
+
print("Loading 1,000,000 instanced lines...")
|
|
26
|
+
n_lines = 1000000
|
|
27
|
+
ab = np.zeros((n_lines, 2), dtype=np.float32)
|
|
28
|
+
ab[:, 0] = np.random.normal(0, 0.5, n_lines) # slopes
|
|
29
|
+
ab[:, 1] = np.random.normal(0, 1.0, n_lines) # intercepts
|
|
30
|
+
gplt.lines(ab[:, 0], ab[:, 1], x_range=(-10, 10), alpha=0.01, width=1.0, label="Massive Dataset")
|
|
31
|
+
|
|
32
|
+
# 3. Deterministic Viewport
|
|
33
|
+
# We want to start zoomed into a specific feature
|
|
34
|
+
print("Setting deterministic initial view: X[-2, 2], Y[-1.5, 1.5]")
|
|
35
|
+
gplt.xlim(-2, 2)
|
|
36
|
+
gplt.ylim(-1.5, 1.5)
|
|
37
|
+
|
|
38
|
+
gplt.title("Deterministic Viewport & 1.5M Primitives")
|
|
39
|
+
|
|
40
|
+
print("Launching engine...")
|
|
41
|
+
gplt.show()
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
demo_viewports_and_performance()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import glplot.pyplot as gplt
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
def test_polyline_colormapping():
|
|
6
|
+
# Generate multiple overlapping polylines
|
|
7
|
+
n_curves = 10
|
|
8
|
+
x = np.linspace(-5, 5, 200)
|
|
9
|
+
|
|
10
|
+
print(f"Creating {n_curves} polylines...")
|
|
11
|
+
for i in range(n_curves):
|
|
12
|
+
# Different frequencies and phases
|
|
13
|
+
y = np.sin(x * (1 + i * 0.1) + i * 0.5) + i * 0.2
|
|
14
|
+
label = f"Curve {i}"
|
|
15
|
+
|
|
16
|
+
# We plot with a default color (black/gray),
|
|
17
|
+
# but we expect it to change when 'Line Colormap' is toggled.
|
|
18
|
+
gplt.plot(x, y, color=(0.2, 0.2, 0.2, 1.0), width=2.0, label=label)
|
|
19
|
+
|
|
20
|
+
print("\nControls:")
|
|
21
|
+
print(" - Press 'C' to toggle Line Colormap (once implemented, or use HUD)")
|
|
22
|
+
print(" - Press 'D' to toggle Density/Exact view")
|
|
23
|
+
|
|
24
|
+
# Enable colormap by default for this test
|
|
25
|
+
plot = gplt.get_engine()
|
|
26
|
+
plot.options.line_colormap_enabled = True
|
|
27
|
+
plot.options.density_scheme_index = 0 # Inferno (usually)
|
|
28
|
+
|
|
29
|
+
gplt.show()
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
test_polyline_colormapping()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backward compatibility shim for glplot.backend.
|
|
3
|
+
New code should import from glplot or specific modules.
|
|
4
|
+
"""
|
|
5
|
+
import warnings
|
|
6
|
+
from .engine import GPULinePlot
|
|
7
|
+
from .options import EngineOptions, RenderMode, BlendMode
|
|
8
|
+
|
|
9
|
+
warnings.warn(
|
|
10
|
+
"Importing from glplot.backend is deprecated. "
|
|
11
|
+
"Please import from 'glplot' directly.",
|
|
12
|
+
DeprecationWarning, stacklevel=2
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = ["GPULinePlot", "EngineOptions", "RenderMode", "BlendMode"]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Tuple, Optional, TYPE_CHECKING
|
|
3
|
+
import numpy as np
|
|
4
|
+
from .utils.gl_utils import ortho
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from .core.legacy import CameraState
|
|
8
|
+
from .options import EngineOptions
|
|
9
|
+
|
|
10
|
+
class CameraController:
|
|
11
|
+
def __init__(self, camera: CameraState, options: EngineOptions):
|
|
12
|
+
self.camera = camera
|
|
13
|
+
self.options = options
|
|
14
|
+
|
|
15
|
+
def world_window(self, width: int, height: int, padding: float = 1.0) -> Tuple[float, float, float, float]:
|
|
16
|
+
aspect = max(width, 1) / max(height, 1)
|
|
17
|
+
half_h = padding / self.camera.zoom
|
|
18
|
+
half_w = half_h * aspect
|
|
19
|
+
l = self.camera.cx - half_w
|
|
20
|
+
r = self.camera.cx + half_w
|
|
21
|
+
b = self.camera.cy - half_h
|
|
22
|
+
t = self.camera.cy + half_h
|
|
23
|
+
return l, r, b, t
|
|
24
|
+
|
|
25
|
+
def mvp(self, width: int, height: int, window: Optional[Tuple[float, float, float, float]] = None) -> np.ndarray:
|
|
26
|
+
l, r, b, t = window if window is not None else self.world_window(width, height)
|
|
27
|
+
return ortho(l, r, b, t)
|
|
28
|
+
|
|
29
|
+
def screen_to_world(self, sx: float, sy: float, width: int, height: int) -> Tuple[float, float]:
|
|
30
|
+
l, r, b, t = self.world_window(width, height)
|
|
31
|
+
x = l + (sx / width) * (r - l)
|
|
32
|
+
y = b + ((height - sy) / height) * (t - b)
|
|
33
|
+
return x, y
|
|
34
|
+
|
|
35
|
+
def apply_zoom_at_cursor(self, factor: float, mx: float, my: float, width: int, height: int) -> None:
|
|
36
|
+
wx0, wy0 = self.screen_to_world(mx, my, width, height)
|
|
37
|
+
self.camera.zoom = float(np.clip(self.camera.zoom * factor, self.camera.zoom_min, self.camera.zoom_max))
|
|
38
|
+
wx1, wy1 = self.screen_to_world(mx, my, width, height)
|
|
39
|
+
self.camera.cx += (wx0 - wx1)
|
|
40
|
+
self.camera.cy += (wy0 - wy1)
|
|
41
|
+
|
|
42
|
+
def fit_bounds(self, xmin: float, xmax: float, ymin: float, ymax: float, width: int, height: int) -> None:
|
|
43
|
+
"""Calculate best cx, cy, and zoom to fit the given bounds into the current viewport."""
|
|
44
|
+
self.camera.cx = 0.5 * (xmin + xmax)
|
|
45
|
+
self.camera.cy = 0.5 * (ymin + ymax)
|
|
46
|
+
|
|
47
|
+
span_x = max(1e-9, xmax - xmin)
|
|
48
|
+
span_y = max(1e-9, ymax - ymin)
|
|
49
|
+
|
|
50
|
+
aspect = max(width, 1) / max(height, 1)
|
|
51
|
+
|
|
52
|
+
# Calculate zooms required for each axis
|
|
53
|
+
# Zoom = 1.0 / half_h
|
|
54
|
+
# half_h_required = span_y / 2.0
|
|
55
|
+
# half_w_required = span_x / 2.0
|
|
56
|
+
|
|
57
|
+
# In our world_window logic: half_w = (1.0 / zoom) * aspect
|
|
58
|
+
# So zoom_x = aspect / half_w_req = (2.0 * aspect) / span_x
|
|
59
|
+
# zoom_y = 1.0 / half_h_req = 2.0 / span_y
|
|
60
|
+
|
|
61
|
+
zx = (2.0 * aspect) / span_x
|
|
62
|
+
zy = 2.0 / span_y
|
|
63
|
+
|
|
64
|
+
self.camera.zoom = float(np.clip(min(zx, zy), self.camera.zoom_min, self.camera.zoom_max))
|
|
65
|
+
|
|
66
|
+
def reset_view(self) -> None:
|
|
67
|
+
self.camera.cx = 0.0
|
|
68
|
+
self.camera.cy = 0.0
|
|
69
|
+
self.camera.zoom = 1.0
|
|
File without changes
|