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.
Files changed (63) hide show
  1. glplot-0.1.0/LICENSE +0 -0
  2. glplot-0.1.0/PKG-INFO +75 -0
  3. glplot-0.1.0/README.md +55 -0
  4. glplot-0.1.0/examples/ex_density.py +34 -0
  5. glplot-0.1.0/examples/ex_density_gain.py +18 -0
  6. glplot-0.1.0/examples/ex_expert_performance.py +40 -0
  7. glplot-0.1.0/examples/ex_full_showcase.py +85 -0
  8. glplot-0.1.0/examples/ex_mpl_bridge.py +65 -0
  9. glplot-0.1.0/examples/ex_scatter.py +39 -0
  10. glplot-0.1.0/examples/ex_simple.py +19 -0
  11. glplot-0.1.0/examples/ex_v2_layers.py +56 -0
  12. glplot-0.1.0/examples/ex_viewports.py +44 -0
  13. glplot-0.1.0/examples/verify_polyline_cmap.py +32 -0
  14. glplot-0.1.0/glplot/__init__.py +6 -0
  15. glplot-0.1.0/glplot/backend.py +15 -0
  16. glplot-0.1.0/glplot/controllers.py +69 -0
  17. glplot-0.1.0/glplot/core/__init__.py +0 -0
  18. glplot-0.1.0/glplot/core/context.py +50 -0
  19. glplot-0.1.0/glplot/core/layers.py +163 -0
  20. glplot-0.1.0/glplot/core/legacy.py +98 -0
  21. glplot-0.1.0/glplot/engine.py +1270 -0
  22. glplot-0.1.0/glplot/managers/__init__.py +0 -0
  23. glplot-0.1.0/glplot/managers/axis.py +66 -0
  24. glplot-0.1.0/glplot/managers/effects.py +343 -0
  25. glplot-0.1.0/glplot/managers/hud.py +510 -0
  26. glplot-0.1.0/glplot/managers/hud_state.py +95 -0
  27. glplot-0.1.0/glplot/managers/picking.py +174 -0
  28. glplot-0.1.0/glplot/managers/renderer_manager.py +158 -0
  29. glplot-0.1.0/glplot/options.py +120 -0
  30. glplot-0.1.0/glplot/policy.py +108 -0
  31. glplot-0.1.0/glplot/pyplot.py +735 -0
  32. glplot-0.1.0/glplot/renderers/__init__.py +0 -0
  33. glplot-0.1.0/glplot/renderers/axis.py +126 -0
  34. glplot-0.1.0/glplot/renderers/base.py +40 -0
  35. glplot-0.1.0/glplot/renderers/density.py +120 -0
  36. glplot-0.1.0/glplot/renderers/exact.py +215 -0
  37. glplot-0.1.0/glplot/renderers/interaction.py +77 -0
  38. glplot-0.1.0/glplot/renderers/line_family.py +250 -0
  39. glplot-0.1.0/glplot/renderers/patch.py +149 -0
  40. glplot-0.1.0/glplot/renderers/polyline.py +230 -0
  41. glplot-0.1.0/glplot/renderers/scatter.py +185 -0
  42. glplot-0.1.0/glplot/renderers/text.py +72 -0
  43. glplot-0.1.0/glplot/scratch/__init__.py +0 -0
  44. glplot-0.1.0/glplot/utils/__init__.py +0 -0
  45. glplot-0.1.0/glplot/utils/export.py +112 -0
  46. glplot-0.1.0/glplot/utils/gl_utils.py +32 -0
  47. glplot-0.1.0/glplot/utils/mpl_bridge.py +60 -0
  48. glplot-0.1.0/glplot/utils/shaders.py +889 -0
  49. glplot-0.1.0/imgui.ini +55 -0
  50. glplot-0.1.0/old/backend.py +1615 -0
  51. glplot-0.1.0/pyproject.toml +36 -0
  52. glplot-0.1.0/scratch/check_gl_limits.py +34 -0
  53. glplot-0.1.0/scratch/check_imgui.py +6 -0
  54. glplot-0.1.0/scratch/diag_view.py +54 -0
  55. glplot-0.1.0/scratch/smoke_test.py +21 -0
  56. glplot-0.1.0/scratch/test_modular_export.py +31 -0
  57. glplot-0.1.0/tests/test_backend.py +134 -0
  58. glplot-0.1.0/tests/test_pyplot.py +84 -0
  59. glplot-0.1.0/tests/verify_blending_extension.py +44 -0
  60. glplot-0.1.0/tests/verify_density.py +26 -0
  61. glplot-0.1.0/tests/verify_headless.py +26 -0
  62. glplot-0.1.0/tests/verify_phase4.py +31 -0
  63. 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,6 @@
1
+ from .engine import GPULinePlot
2
+ from .options import EngineOptions, RenderMode, BlendMode
3
+
4
+ __version__ = "0.1.0"
5
+
6
+ __all__ = ["GPULinePlot", "EngineOptions", "RenderMode", "BlendMode"]
@@ -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