High-Performance Charting — Technical Deep Dive

research findings from xy (reflex.dev) for letsplot optimization
Context: Grammar of Graphics & ggplot2 Bridge

The Challenge

Lets-Plot is a multiplatform plotting library built on the principles of the Grammar of Graphics, bringing the ggplot2 experience to Python. However, most charting libraries (including those using ggplot2 paradigms) stop being usable somewhere in the hundreds of thousands of points. Working at scale normally means downsampling your data first or using sampling.

The open-source library xy by Reflex.dev renders a scatter plot of 100 million points that you can pan and zoom through in real time — without any pre-downsampling. Performance metrics: 0.071s at 10k and 0.081s at 100M, flat across four orders of magnitude.

Core Architecture — How XY Achieves This

1. Rust Core Engine (src/)

2. WebGL2 Renderer (js/src/)

The Tier Ladder System
Tier Name Representation Cost model
0 Direct every visible mark, exact O(visible) verts
1 Shape-preserving reduction per-pixel-column aggregate (M4 for lines) O(px) verts
2 Density / aggregate surface mean-point-color texture composited at points' own alpha O(screen) texels
3 Out-of-core tiles Tier-2 pyramid where not all tiles are resident O(visible tiles)
Python LOD Logic & Thresholds

Key Configuration Constants (python/xy/config.py)

# Lines longer than this ship M4-decimated (Tier 1)
DECIMATION_THRESHOLD = 10_000

# Scatter above this many points switches to Tier-2 density aggregation
SCATTER_DENSITY_THRESHOLD = 200_000

# Absolute direct-draw ceiling; above this, density is forced even with per-point channels
DIRECT_SOFT_CEILING = 2_000_000

# Hysteresis on the drill boundary: once drilled to points, stay until count clearly exceeds budget again
DRILL_EXIT_FACTOR = 1.15

Drill Decision Logic (python/xy/lod.py)

The render tier is a function of the visible point count, hysteresis-guarded:

def drill_decision(visible: int, budget: float, in_drill: bool) -> bool:
    """Once drilled down to real points, stay until the count clearly exceeds the budget again."""
    return visible <= budget * (DRILL_EXIT_FACTOR if in_drill else 1.0)
Key Insight for LetsPlot: The secret is that XY never ships O(N) data to the browser for large datasets. Above 200k points, it computes a screen-bounded density surface (512x384 grid max) in Rust, and pan/zoom triggers re-binning in the Rust core — not in JavaScript or Python on the main thread. The WebGL shader composites colors using physical alpha blending: 1 − (1 − a_pt)^k for k points with per-point alpha a_pt.
What This Means for LetsPlot

Recommended Architecture Upgrades

  1. Move aggregation to a native core: Implement Rust or C++ kernels for 2D binning with mean-color compositing. Python NumPy is too slow for O(N) zoom steps at 10M+ rows.
  2. Implement multi-resolution pyramids: Pre-compute 4→1 count pyramids at build time so zoom-out queries are O(visible cells), not O(N).
  3. Use offset-encoded f32 geometry: Store geometry as relative f32 coordinates (v - offset) * scale so large-magnitude domains keep the digits that matter.
  4. Uniform-only view transformations: Pan/zoom should only update two vec2 uniforms per mark in the WebGL shader, never re-upload geometry.
  5. Hysteresis-guarded LOD transitions: Prevent thrashing between density and point modes when the visible count hovers near the threshold.

Mean-Color Compositing Formula

For a cell with k points, each with per-point alpha a_pt, the displayed alpha is the physical compositing:

display_alpha = 1 − (1 − a_pt)^k

This saturates after a few points exactly like real overplotted marks do. The color is the alpha-weighted mean of resolved colors in linear light (integer pipeline: checked-in sRGB⇄linear-u16 tables, u64 sums — bitwise deterministic).