|

Designing Scalable Interactive Visualizations with Reflex XY: Composition, Million-Point Rendering, Streaming, Custom Marks, and Export

✅

In this tutorial, we discover the superior visualization capabilities of the XY Python library by constructing interactive, scalable, and extensible charts. We start with XY’s composition mannequin, the place we mix a number of marks, twin axes, annotations, tooltips, legends, themes, and interactive controls inside a single chart declaration. We then work with Pandas DataFrames, faceted layouts, linked viewports, and million-point datasets that routinely swap to density-based rendering for environment friendly exploration. We additionally join browser interactions again to Python via choices and callbacks, replace charts dynamically via streaming, customise visible elements with DOM slots and CSS, and prolong the library with a reusable customized trendline mark. Also, we use the Matplotlib-compatible interface and export our visualizations as standalone HTML, SVG, and PNG recordsdata.

import subprocess, sys, os
subprocess.run([sys.executable, "-m", "pip", "install", "-q", "xy"], verify=True)
WIDGETS_OK = True
attempt:
   from google.colab import output as _colab_output
   _colab_output.enable_custom_widget_manager()
besides Exception:
   WIDGETS_OK = False
import numpy as np
import pandas as pd
import xy
from IPython.show import show, HTML
print("xy", xy.__version__, "| stay widgets:", WIDGETS_OK)
def render(chart, notice=""):
   if notice:
       show(HTML(f"<h3 fashion='font:600 15px system-ui;margin:18px 0 6px'>{notice}</h3>"))
   attempt:
       show(chart)
   besides Exception:
       show(HTML(chart.to_html()))
   return chart
rng = np.random.default_rng(7)
days    = np.arange(180)
development   = 200 + 0.9 * days + 18 * np.sin(days / 9.0)
income = development + rng.regular(0, 12, days.measurement)
sigma   = 10 + 6 * np.abs(np.sin(days / 15.0))
conv    = 0.06 + 0.02 * np.sin(days / 21.0) + rng.regular(0, 0.003, days.measurement)
peak    = int(np.argmax(income))
layered = xy.chart(
   xy.error_band(days, income - 1.96 * sigma, income + 1.96 * sigma,
                 title="95% band", coloration="#7c3aed", opacity=0.16),
   xy.line(days, income, title="Revenue", coloration="#7c3aed", width=2.5,
           curve="clean"),
   xy.scatter(days[::12], income[::12], title="Weekly verify", coloration="#7c3aed",
              measurement=7, stroke="#ffffff", stroke_width=1.5),
   xy.line(days, conv, title="Conversion", coloration="#f59e0b", width=2,
           sprint="dashed", y_axis="y2"),
   xy.x_axis(label="Day", grid=True),
   xy.y_axis(label="Revenue (ok)", grid=True, format=",.0f"),
   xy.y_axis(id="y2", label="Conversion", aspect="proper", grid=False, format=".1%"),
   xy.x_band(120, 150, textual content="Campaign", coloration="#22c55e", opacity=0.10),
   xy.hline(float(income.imply()), textual content="imply", coloration="#94a3b8"),
   xy.callout(float(days[peak]), float(income[peak]), "peak", dx=-60, dy=-40),
   xy.legend(loc="higher left", ncols=2, toggle=True),
   xy.tooltip(title="Day", format={"y": ",.1f"}),
   xy.modebar(True),
   xy.theme(palette=["#7c3aed", "#f59e0b"], grid_color="#e6e6ef"),
   title="Layered composition · twin axes · annotations",
   width=900, top=440, crosshair=True,
)
render(layered, "1 · Composition mannequin")

We set up and initialize the XY library in Google Colab whereas enabling help for interactive widgets. We outline a reusable rendering perform that shows stay charts and falls again to standalone HTML when widget help is unavailable. We then construct a layered visualization with a number of marks, twin axes, annotations, tooltips, legends, themes, and interactive navigation controls.

n = 4000
df = pd.DataBody({
   "x":      rng.regular(0, 1, n),
   "noise":  rng.regular(0, 1, n),
   "area": rng.alternative(["North", "South", "East", "West"], n),
})
df["y"]   = 2.1 * df["x"] + df["noise"] * 0.9
df["mag"] = np.abs(df["y"])
render(xy.scatter_chart(
   xy.scatter("x", "y", coloration="magazine", colormap="plasma",
              measurement=5, opacity=0.7, color_domain=(0, 6)),
   xy.colorbar(title="|y|"),
   xy.x_axis(label="x"), xy.y_axis(label="y"),
   information=df, title="Columns resolved by title", width=760, top=420,
), "2 · DataBody-driven channels")
render(xy.facet_chart(
   xy.scatter("x", "y", coloration="#0ea5e9", measurement=4, opacity=0.6),
   by="area", information=df, cols=2,
   share_x=True, share_y=True, hyperlink=True, link_select=True,
   width=760, top=220, hole=12, title="Faceted by area",
), "3 · Facets with linked axes")
N = 1_500_000
r     = 6.0 * rng.beta(1.2, 3.0, N)
theta = 2.9 * np.log1p(r) + rng.integers(0, 4, N) * (np.pi / 2) + rng.regular(0, 0.05, N)
huge = xy.scatter_chart(
   xy.scatter(r * np.cos(theta), r * np.sin(theta),
              coloration=np.exp(-r / 2.2), colormap="magma_r",
              density=True,
              measurement=2.5, opacity=0.85,
              zoom_size_factor=2.6, zoom_opacity=0.95),
   xy.colorbar(title="density"),
   title=f"{N:,} factors · drag to pan, scroll to zoom",
   width=760, top=520, zoom=True, pan=True, wheel_zoom=True,
)
render(huge, "4 · Million-point density floor")
mem = huge.memory_report()
print(f"canonical f64 held in Python : {mem['canonical_bytes']/1e6:.1f} MB")
print(f"bytes despatched for first paint   : {mem['transport_bytes_first_paint']/1e6:.2f} MB "
     f"({mem['transport_bytes_per_point']:.3f} B/level)")
print(f"compute backend              : {mem['backend']}")

We create a structured Pandas DataBody and use column names immediately as visualization channels. We generate a color-encoded scatter plot, divide the dataset into linked regional sides, and protect shared axis conduct throughout panels. We additionally visualize 1.5 million factors via XY’s density rendering system and examine its reminiscence utilization and data-transfer effectivity.

sel = huge.select_range(-1.0, 1.0, -1.0, 1.0)
sx, sy = sel.xy(0)
print(f"nselect_range hit {len(sel):,} rows; x array {sx.form}")
print("first rows:", sel.rows(restrict=2))
print("choose(hint=1, index=10):", layered.choose(1, 10))
def on_select(choice):
   xs, ys = choice.xy(0)
   print(f"[callback] {len(choice):,} rows chosen, imply y = {ys.imply():.3f}")
def on_view_change(payload):
   print("[callback] viewport:", payload)
render(xy.scatter_chart(
   xy.scatter("x", "y", coloration="#ef4444", measurement=5, opacity=0.7),
   information=df, choose=True, on_select=on_select, on_view_change=on_view_change,
   title="Shift-drag a field → payload lands in Python",
   width=760, top=380,
), "5 · Selections routed again to the kernel")
stream = xy.line_chart(
   xy.line([0.0], [0.0], coloration="#10b981", width=2, title="stay"),
   xy.x_axis(label="t"), xy.y_axis(label="worth", area=(-3, 3)),
   title="Streaming by way of chart.append()", width=760, top=320,
)
render(stream, "6 · Streaming")
import time
for ok in vary(1, 60):
   t = ok / 3.0
   stream.append(0, [t], [float(np.sin(t) + rng.normal(0, 0.08))])
   time.sleep(0.03)

We choose actual information factors from the massive visualization and retrieve their unique row values immediately from Python. We outline callback features that obtain browser-side choices and viewport modifications whereas protecting the underlying information contained in the kernel. We additionally create a streaming line chart and constantly append new observations to replace the visualization in actual time.

print("navailable slots:", ", ".be part of(sorted(xy.CHART_DOM_SLOTS)))
CSS = """
.xy-card {background:#fafaf9;border:1px stable #e7e5e4;border-radius:16px;padding:10px}
.xy-title{font:600 16px/1.2 ui-sans-serif;letter-spacing:-.01em;coloration:#1c1917}
.xy-tip  {border-radius:10px;background:#1c1917;coloration:#fafaf9}
"""
show(HTML(f"<fashion>{CSS}</fashion>"))
styled = xy.line_chart(
   xy.line(days, income, coloration="#111827", width=2,
           animation=xy.animation(period=700,
                                  easing=xy.spring(stiffness=180, damping=22))),
   xy.x_axis(label="Day"), xy.y_axis(label="Revenue"),
   title="Slot-addressed styling",
   class_name="xy-card",
   class_names={"title": "xy-title", "tooltip": "xy-tip"},
   types={"canvas": {"border-radius": "12px"}},
   width=760, top=360,
)
render(styled, "7 · CSS slots, tokens, spring animation")
def _fit(cols):
   x = np.asarray(cols["x"], float); y = np.asarray(cols["y"], float)
   b, a = np.polyfit(x, y, 1)
   order = np.argsort(x); xs = x[order]
   match   = a + b * xs
   resid = float(np.std(y - (a + b * x)))
   return {"x": xs, "y": y[order], "match": match,
           "lo": match - 1.96 * resid, "hello": match + 1.96 * resid}
def _build(ctx):
   coloration = ctx.choices.get("coloration", "#2563eb")
   c, nm = ctx.columns, (ctx.title or "development")
   return [
       xy.error_band(c["x"], c["lo"], c["hi"], coloration=coloration, opacity=0.18, title=f"{nm} CI"),
       xy.line(c["x"], c["fit"], coloration=coloration, width=2.5, title=nm),
   ]
if "trendline" not in xy.registered_marks():
   xy.register_mark(xy.MarkPlugin(title="trendline", construct=_build,
                                  columns=("x", "y"), calc=_fit,
                                  doc="OLS match with a 95% band."))
render(xy.chart(
   xy.scatter("x", "y", coloration="#94a3b8", measurement=4, opacity=0.5, title="observations"),
   xy.mark("trendline", x="x", y="y", coloration="#e11d48", title="OLS"),
   xy.legend(loc="higher left"),
   information=df, title="Third-party mark variety", width=760, top=400,
), "8 · Custom mark plugin")

We customise chart elements via steady DOM slots, CSS lessons, inline types, and spring-based animations. We outline an atypical least-squares calculation that produces a fitted trendline and a 95% confidence band from the provided information. We then register this calculation as a reusable customized XY mark and mix it with built-in scatter, line, error-band, and legend elements.

import xy.pyplot as plt
t = np.linspace(0, 10, 400)
fig, ax = plt.subplots(figsize=(8, 3.5))
ax.plot(t, np.sin(t), "r--", label="sin")
ax.plot(t, np.cos(t), label="cos")
ax.set_xlabel("t"); ax.set_ylabel("amplitude"); ax.set_title("xy.pyplot compatibility")
ax.legend(); ax.grid(True, alpha=0.3)
plt.present()
os.makedirs("out", exist_ok=True)
layered.to_html("out/chart.html")
layered.to_svg("out/chart.svg")
layered.to_png("out/chart.png", scale=2)
for f in ("chart.html", "chart.svg", "chart.png"):
   print(f"out/{f}: {os.path.getsize('out/'+f)/1024:.0f} KB")
print("n✅ tutorial full")

We use the xy.pyplot compatibility layer to create plots with acquainted Matplotlib-style instructions. We generate sine and cosine curves, configure labels, titles, legends, and grid settings, and show the ensuing determine. We lastly export the layered chart as standalone HTML, SVG, and high-resolution PNG recordsdata and confirm the scale of every generated artifact.

In conclusion, we constructed a complete understanding of how XY helps trendy interactive visualization workflows immediately from Python. We created layered and faceted charts, analyzed massive datasets effectively, retrieved actual chosen rows from the kernel, streamed new observations into stay visualizations, and custom-made chart look via themes, animations, CSS lessons, and steady DOM slots. We additionally demonstrated how we prolong XY with our personal statistical mark plugin and reuse acquainted Matplotlib-style plotting instructions via the xy.pyplot bridge. We completed with moveable exports that permit us to share charts outdoors the pocket book whereas preserving both interactivity or publication-ready graphical high quality.


Check out the FULL CODES here. Also, be at liberty to observe us on Twitter and don’t overlook to hitch our 150k+ML SubReddit and Subscribe to our Newsletter. Wait! are you on telegram? now you can join us on telegram as well.

Need to companion with us for selling your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar and many others.? Connect with us

The put up Designing Scalable Interactive Visualizations with Reflex XY: Composition, Million-Point Rendering, Streaming, Custom Marks, and Export appeared first on MarkTechPost.

Similar Posts