Sakhanda Wire
NVDA $223.96 +2.27% MSFT $499.99 +0.03% GOOGL $354.30 -0.96% META $592.10 +0.37% AMZN $274.48 +0.82%
← К новостям

Создание масштабируемых интерактивных визуализаций с Reflex XY: композиция, визуализация миллионов точек, потоковая передача, пользовательские маркеры и экспорт

В этом руководстве мы исследуем расширенные возможности визуализации библиотеки Python XY, создавая интерактивные, масштабируемые и расширяемые графики. Мы начнём с композиционной модели XY, в которой объединим несколько элементов, двойные оси, аннотации, всплывающие подсказки, легенды, темы и интерактивные элементы управления в одном объявлении графика. Затем мы поработаем с DataFrame из Pandas, фасетными макетами, связанными областями просмотра и наборами данных из миллиона точек, которые автоматически переключаются на визуализацию на основе плотности для эффективного исследования. Мы также свяжем взаимодействия в браузере с Python через выборки и обратные вызовы, будем динамически обновлять графики посредством потоковой передачи данных, настраивать визуальные компоненты с помощью DOM-слотов и CSS, а также расширим библиотеку повторно используемым пользовательским элементом линии тренда. Кроме того, мы используем интерфейс, совместимый с Matplotlib, и экспортируем визуализации в отдельные файлы HTML, SVG и PNG.

Копировать кодСкопированоИспользовать другой браузер
import subprocess, sys, os
subprocess.run([sys.executable, "-m", "pip", "install", "-q", "xy"], check=True)
WIDGETS_OK = True
try:
   from google.colab import output as _colab_output
   _colab_output.enable_custom_widget_manager()
except Exception:
   WIDGETS_OK = False
import numpy as np
import pandas as pd
import xy
from IPython.display import display, HTML
print("xy", xy.__version__, "| live widgets:", WIDGETS_OK)
def render(chart, note=""):
   if note:
       display(HTML(f"<h3 style='font:600 15px system-ui;margin:18px 0 6px'>{note}</h3>"))
   try:
       display(chart)
   except Exception:
       display(HTML(chart.to_html()))
   return chart
rng = np.random.default_rng(7)
days    = np.arange(180)
trend   = 200 + 0.9 * days + 18 * np.sin(days / 9.0)
revenue = trend + rng.normal(0, 12, days.size)
sigma   = 10 + 6 * np.abs(np.sin(days / 15.0))
conv    = 0.06 + 0.02 * np.sin(days / 21.0) + rng.normal(0, 0.003, days.size)
peak    = int(np.argmax(revenue))
layered = xy.chart(
   xy.error_band(days, revenue - 1.96 * sigma, revenue + 1.96 * sigma,
                 name="95% band", color="#7c3aed", opacity=0.16),
   xy.line(days, revenue, name="Revenue", color="#7c3aed", width=2.5,
           curve="smooth"),
   xy.scatter(days[::12], revenue[::12], name="Weekly check", color="#7c3aed",
              size=7, stroke="#ffffff", stroke_width=1.5),
   xy.line(days, conv, name="Conversion", color="#f59e0b", width=2,
           dash="dashed", y_axis="y2"),
   xy.x_axis(label="Day", grid=True),
   xy.y_axis(label="Revenue (k)", grid=True, format=",.0f"),
   xy.y_axis(id="y2", label="Conversion", side="right", grid=False, format=".1%"),
   xy.x_band(120, 150, text="Campaign", color="#22c55e", opacity=0.10),
   xy.hline(float(revenue.mean()), text="mean", color="#94a3b8"),
   xy.callout(float(days[peak]), float(revenue[peak]), "peak", dx=-60, dy=-40),
   xy.legend(loc="upper 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 · dual axes · annotations",
   width=900, height=440, crosshair=True,
)
render(layered, "1 · Composition model")

Мы устанавливаем и инициализируем библиотеку XY в Google Colab, включив поддержку интерактивных виджетов. Мы определяем повторно используемую функцию визуализации, которая отображает интерактивные графики и переключается на автономный HTML, если поддержка виджетов недоступна. Затем мы создаём многослойную визуализацию с несколькими элементами, двойными осями, аннотациями, всплывающими подсказками, легендами, темами и интерактивными элементами навигации.

Копировать кодСкопированоИспользовать другой браузер
n = 4000
df = pd.DataFrame({
   "x":      rng.normal(0, 1, n),
   "noise":  rng.normal(0, 1, n),
   "region": rng.choice(["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", color="mag", colormap="plasma",
              size=5, opacity=0.7, color_domain=(0, 6)),
   xy.colorbar(title="|y|"),
   xy.x_axis(label="x"), xy.y_axis(label="y"),
   data=df, title="Columns resolved by name", width=760, height=420,
), "2 · DataFrame-driven channels")
render(xy.facet_chart(
   xy.scatter("x", "y", color="#0ea5e9", size=4, opacity=0.6),
   by="region", data=df, cols=2,
   share_x=True, share_y=True, link=True, link_select=True,
   width=760, height=220, gap=12, title="Faceted by region",
), "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.normal(0, 0.05, N)
big = xy.scatter_chart(
   xy.scatter(r * np.cos(theta), r * np.sin(theta),
              color=np.exp(-r / 2.2), colormap="magma_r",
              density=True,
              size=2.5, opacity=0.85,
              zoom_size_factor=2.6, zoom_opacity=0.95),
   xy.colorbar(title="density"),
   title=f"{N:,} points · drag to pan, scroll to zoom",
   width=760, height=520, zoom=True, pan=True, wheel_zoom=True,
)
render(big, "4 · Million-point density surface")
mem = big.memory_report()
print(f"canonical f64 held in Python : {mem['canonical_bytes']/1e6:.1f} MB")
print(f"bytes sent for first paint   : {mem['transport_bytes_first_paint']/1e6:.2f} MB "
     f"({mem['transport_bytes_per_point']:.3f} B/point)")
print(f"compute backend              : {mem['backend']}")

Мы создаём структурированный DataFrame Pandas и напрямую используем имена столбцов в качестве каналов визуализации. Мы генерируем точечный график с кодированием цвета, делим набор данных на связанные региональные фасеты и сохраняем общие настройки осей для всех панелей. Мы также визуализируем 1,5 миллиона точек с помощью системы XY для отображения плотности и проверяем использование памяти и эффективность передачи данных.

Копировать кодСкопированоИспользовать другой браузер
sel = big.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.shape}")
print("first rows:", sel.rows(limit=2))
print("pick(trace=1, index=10):", layered.pick(1, 10))
def on_select(selection):
   xs, ys = selection.xy(0)
   print(f"[callback] {len(selection):,} rows selected, mean y = {ys.mean():.3f}")
def on_view_change(payload):
   print("[callback] viewport:", payload)
render(xy.scatter_chart(
   xy.scatter("x", "y", color="#ef4444", size=5, opacity=0.7),
   data=df, select=True, on_select=on_select, on_view_change=on_view_change,
   title="Shift-drag a box → payload lands in Python",
   width=760, height=380,
), "5 · Selections routed back to the kernel")
stream = xy.line_chart(
   xy.line([0.0], [0.0], color="#10b981", width=2, name="live"),
   xy.x_axis(label="t"), xy.y_axis(label="value", domain=(-3, 3)),
   title="Streaming via chart.append()", width=760, height=320,
)
render(stream, "6 · Streaming")
import time
for k in range(1, 60):
   t = k / 3.0
   stream.append(0, [t], [float(np.sin(t) + rng.normal(0, 0.08))])
   time.sleep(0.03)

Мы выбираем отдельные точки данных из большой визуализации и напрямую получаем их исходные значения строк из Python. Мы определяем функции обратного вызова, которые получают выборки и изменения области просмотра из браузера, сохраняя исходные данные внутри ядра. Мы также создаём потоковый линейный график и непрерывно добавляем новые наблюдения, чтобы обновлять визуализацию в реальном времени.

Копировать кодСкопированоИспользовать другой браузер
print("\navailable slots:", ", ".join(sorted(xy.CHART_DOM_SLOTS)))
CSS = """
.xy-card {background:#fafaf9;border:1px solid #e7e5e4;border-radius:16px;padding:10px}
.xy-title{font:600 16px/1.2 ui-sans-serif;letter-spacing:-.01em;color:#1c1917}
.xy-tip  {border-radius:10px;background:#1c1917;color:#fafaf9}
"""
display(HTML(f"<style>{CSS}</style>"))
styled = xy.line_chart(
   xy.line(days, revenue, color="#111827", width=2,
           animation=xy.animation(duration=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"},
   styles={"canvas": {"border-radius": "12px"}},
   width=760, height=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]
   fit   = a + b * xs
   resid = float(np.std(y - (a + b * x)))
   return {"x": xs, "y": y[order], "fit": fit,
           "lo": fit - 1.96 * resid, "hi": fit + 1.96 * resid}
def _build(ctx):
   color = ctx.options.get("color", "#2563eb")
   c, nm = ctx.columns, (ctx.name or "trend")
   return [
       xy.error_band(c["x"], c["lo"], c["hi"], color=color, opacity=0.18, name=f"{nm} CI"),
       xy.line(c["x"], c["fit"], color=color, width=2.5, name=nm),
   ]
if "trendline" not in xy.registered_marks():
   xy.register_mark(xy.MarkPlugin(name="trendline", build=_build,
                                  columns=("x", "y"), calc=_fit,
                                  doc="OLS fit with a 95% band."))
render(xy.chart(
   xy.scatter("x", "y", color="#94a3b8", size=4, opacity=0.5, name="observations"),
   xy.mark("trendline", x="x", y="y", color="#e11d48", name="OLS"),
   xy.legend(loc="upper left"),
   data=df, title="Third-party mark kind", width=760, height=400,
), "8 · Custom mark plugin")

Мы настраиваем компоненты графика с помощью стабильных DOM-слотов, CSS-классов, встроенных стилей и анимаций на основе пружинной модели. Мы определяем расчёт методом обычных наименьших квадратов, который строит линию тренда и 95%-ный доверительный интервал на основе предоставленных данных. Затем мы регистрируем этот расчёт как повторно используемый пользовательский элемент XY и объединяем его со встроенными компонентами точечного и линейного графиков, доверительного диапазона и легенды.

Копировать кодСкопированоИспользовать другой браузер
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.show()
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 complete")

Мы используем слой совместимости xy.pyplot для создания графиков с помощью привычных команд в стиле Matplotlib. Мы строим кривые синуса и косинуса, настраиваем подписи, заголовки, легенды и параметры сетки, а затем отображаем получившийся рисунок. Наконец, мы экспортируем многослойный график в отдельные файлы HTML, SVG и PNG высокого разрешения и проверяем размер каждого созданного файла.

В заключение мы сформировали комплексное представление о том, как XY поддерживает современные процессы интерактивной визуализации непосредственно из Python. Мы создали многослойные и фасетные графики, эффективно проанализировали большие наборы данных, получили точные выбранные строки из ядра, передавали новые наблюдения в работающие визуализации и настраивали внешний вид графиков с помощью тем, анимаций, CSS-классов и стабильных DOM-слотов. Мы также показали, как расширить XY собственным плагином статистического элемента и повторно использовать привычные команды построения графиков в стиле Matplotlib через мост xy.pyplot. В завершение мы экспортировали графики в переносимые форматы, что позволяет делиться ими за пределами ноутбука, сохраняя интерактивность или качество графики, готовой к публикации.


Ознакомьтесь с ПОЛНЫМ КОДОМ здесь.Также подписывайтесь на нас в Twitter и не забудьте присоединиться к нашему сабреддиту по машинному обучению с более чем 150 тыс. участников и подписаться на нашу рассылку. Стоп! Вы есть в Telegram? теперь вы также можете присоединиться к нам в Telegram.

Хотите сотрудничать с нами для продвижения своего репозитория GitHub, страницы Hugging Face, релиза продукта, вебинара и т. д.? Свяжитесь с нами

Переведено автоматически с английского. Оригинал статьи — по ссылке ниже.

Впервые опубликовано изданием MarkTechPost

Читать оригинал на MarkTechPost ↗

Текст и изображения принадлежат MarkTechPost и приводятся здесь с указанием авторства и ссылкой на оригинальную публикацию.

Ещё новости

Все последние новости