Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
364 changes: 363 additions & 1 deletion examples/reflex/xy_reflex_demo/xy_reflex_demo.py

Large diffs are not rendered by default.

38 changes: 33 additions & 5 deletions python/reflex_xy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,23 @@ def index() -> rx.Component:
"chart": ".factories",
"area_chart": ".factories",
"bar_chart": ".factories",
"box_chart": ".factories",
"column_chart": ".factories",
"contour_chart": ".factories",
"ecdf_chart": ".factories",
"error_band_chart": ".factories",
"errorbar_chart": ".factories",
"heatmap_chart": ".factories",
"hexbin_chart": ".factories",
"histogram_chart": ".factories",
"line_chart": ".factories",
"scatter_chart": ".factories",
"segments_chart": ".factories",
"stairs_chart": ".factories",
"stem_chart": ".factories",
"step_chart": ".factories",
"triangle_mesh_chart": ".factories",
"violin_chart": ".factories",
"AsyncDataVar": ".data_vars",
"DataVar": ".data_vars",
"data": ".data_vars",
Expand Down Expand Up @@ -111,11 +119,12 @@ def index() -> rx.Component:
#: `xy.scatter`, so composed data-bound charts read uniformly
#: (`reflex_xy.chart(reflex_xy.scatter("x", "y"), data=...)`) and a
#: hallucinated constructor dies at import against this explicit map
#: instead of surviving to hydrate. Marks whose validators need data
#: (box, violin, hexbin, …) are still listed — the plan probe refuses them
#: with the recorded Phase 3 guidance rather than a misleading
#: AttributeError. Chart factories are deliberately absent: the flat
#: `*_chart` names above are reflex-native factories, not xy's.
#: instead of surviving to hydrate. Every standalone mark kind is plan-
#: compatible: the aggregating ones (box, violin, hexbin, …) probe
#: zero-row under the core's structural_probe() mode — config validates,
#: aggregation never runs on invented values.
#: Chart factories are deliberately absent: the flat `*_chart` names above
#: are reflex-native factories, not xy's.
_XY_REEXPORTS = frozenset(
{
# marks
Expand All @@ -137,6 +146,7 @@ def index() -> rx.Component:
"hexbin",
"contour",
"heatmap",
"triangle_mesh",
# annotations
"vline",
"hline",
Expand Down Expand Up @@ -195,23 +205,28 @@ def index() -> rx.Component:
"bar",
"bar_chart",
"box",
"box_chart",
"callout",
"chart",
"clear_selection",
"colorbar",
"column",
"column_chart",
"contour",
"contour_chart",
"data",
"ecdf",
"ecdf_chart",
"error_band",
"error_band_chart",
"errorbar",
"errorbar_chart",
"export_config",
"figure",
"heatmap",
"heatmap_chart",
"hexbin",
"hexbin_chart",
"histogram",
"histogram_chart",
"hline",
Expand All @@ -238,6 +253,7 @@ def index() -> rx.Component:
"setup",
"spring",
"stairs",
"stairs_chart",
"stem",
"stem_chart",
"step",
Expand All @@ -248,7 +264,10 @@ def index() -> rx.Component:
"threshold",
"threshold_zone",
"tooltip",
"triangle_mesh",
"triangle_mesh_chart",
"violin",
"violin_chart",
"vline",
"x_axis",
"x_band",
Expand Down Expand Up @@ -421,6 +440,7 @@ def release(token: "str | FigureHandle") -> None:
threshold,
threshold_zone,
tooltip,
triangle_mesh,
violin,
vline,
x_axis,
Expand All @@ -446,16 +466,24 @@ def release(token: "str | FigureHandle") -> None:
from .factories import (
area_chart,
bar_chart,
box_chart,
chart,
column_chart,
contour_chart,
ecdf_chart,
error_band_chart,
errorbar_chart,
heatmap_chart,
hexbin_chart,
histogram_chart,
line_chart,
scatter_chart,
segments_chart,
stairs_chart,
stem_chart,
step_chart,
triangle_mesh_chart,
violin_chart,
)
from .handles import DataHandle, FigureHandle
from .namespace import XY_NAMESPACE, XYNamespace
Expand Down
24 changes: 13 additions & 11 deletions python/reflex_xy/data_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,18 @@ def _deps(self, objclass: Any, obj: Any = None) -> dict[str, set[str]]:


def validate_columns(columns: Any, *, source: str) -> dict[str, Any]:
"""The only checks that need real data: a mapping of named, equal-length
array-like columns. Everything structural was validated at compile by the
plan's zero-row probe; dtype/shape details stay with figure compilation."""
"""The only check that needs real data: a mapping of named array-like
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
columns. Columns may differ in length and dimensionality — one var can
carry a scatter's rows next to a stairs mark's ``len+1`` edges or a
heatmap's 2-D grid. Coupled-length and shape contracts (x vs y, edges
vs values, z's 2-D-ness) belong to the mark validators when a plan
binds, where the errors name the mark and channels involved."""
if not isinstance(columns, Mapping):
raise TypeError(
f"{source} must return a mapping of column name -> values "
f"(e.g. a TypedDict of arrays), got {type(columns).__name__}"
)
validated: dict[str, Any] = {}
lengths: dict[str, int] = {}
for key, values in columns.items():
if not isinstance(key, str):
raise TypeError(f"{source} column names must be strings, got {key!r}")
Expand All @@ -72,16 +74,13 @@ def validate_columns(columns: Any, *, source: str) -> dict[str, Any]:
f"got {type(values).__name__}"
)
try:
lengths[key] = len(values)
len(values)
except TypeError as exc:
raise TypeError(
f"{source} column {key!r} must be an array-like with a length, "
f"got {type(values).__name__}"
) from exc
validated[key] = values
if len(set(lengths.values())) > 1:
detail = ", ".join(f"{key}={length}" for key, length in lengths.items())
raise ValueError(f"{source} columns must share one length, got {detail}")
return validated


Expand Down Expand Up @@ -199,9 +198,12 @@ def cloud(self) -> CloudData:
# in the page:
# reflex_xy.scatter_chart(data=Dash.cloud, x="x", y="y", color="mag")

The method must return a mapping of column name -> equal-length
array-likes, or ``None`` for "no data right now" (which releases the
registered columns and yields the empty handle). ``async def`` methods
The method must return a mapping of column name -> array-likes — mixed
lengths and dimensionalities are fine (a scatter's rows can sit next to
a stairs mark's ``len+1`` edges or a heatmap's 2-D grid; coupled-shape
contracts belong to the mark validators when a plan binds) — or
``None`` for "no data right now" (which releases the registered columns
and yields the empty handle). ``async def`` methods
become ``AsyncDataVar``s (same dispatch rule as ``rx.var``); keyword
arguments pass through to reflex's computed var (``deps=``,
``interval=``, ...).
Expand Down
64 changes: 62 additions & 2 deletions python/reflex_xy/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
height="460px",
)

Both compile the real xy tree at page evaluation, run the zero-row probe
(`plan.py` — the full mark/config validation gate, X1/X2), check channel
Both compile the real xy tree at page evaluation, run the zero-row plan
probe (`plan.py` — the mark/config validation gate, X1/X2, under the
core's `structural_probe()` mode: no synthetic data), check channel
names against the data var's TypedDict schema (R7), and mount the private
component with a `plan` digest plus the `data` handle var. Marks and chrome
are plain xy dataclass nodes — they never enter the Reflex tree
Expand Down Expand Up @@ -73,16 +74,24 @@
__all__ = [
"area_chart",
"bar_chart",
"box_chart",
"chart",
"column_chart",
"contour_chart",
"ecdf_chart",
"error_band_chart",
"errorbar_chart",
"heatmap_chart",
"hexbin_chart",
"histogram_chart",
"line_chart",
"scatter_chart",
"segments_chart",
"stairs_chart",
"stem_chart",
"step_chart",
"triangle_mesh_chart",
"violin_chart",
]

#: Event triggers of the private XYChart component. Pinned against the
Expand Down Expand Up @@ -227,6 +236,17 @@ def _flat_kind(chart_kind: str, mark_factory: Any) -> _FlatKind:
_flat_kind("errorbar_chart", _xy.errorbar),
_flat_kind("error_band_chart", _xy.error_band),
_flat_kind("segments_chart", _xy.segments),
# Aggregating kinds: zero-row like everything else — under the
# core's structural_probe() mode they validate config and skip
# aggregation, so no synthetic data is ever invented for them.
_flat_kind("box_chart", _xy.box),
_flat_kind("violin_chart", _xy.violin),
_flat_kind("ecdf_chart", _xy.ecdf),
_flat_kind("hexbin_chart", _xy.hexbin),
_flat_kind("contour_chart", _xy.contour),
_flat_kind("heatmap_chart", _xy.heatmap),
_flat_kind("stairs_chart", _xy.stairs),
_flat_kind("triangle_mesh_chart", _xy.triangle_mesh),
)
}

Expand Down Expand Up @@ -455,6 +475,46 @@ def segments_chart(*, data: Any = None, **kwargs: Any) -> Any:
return _flat_chart(FLAT_KINDS["segments_chart"], data, kwargs)


def box_chart(*, data: Any = None, **kwargs: Any) -> Any:
"""A data-bound box plot (flat form; see module doc)."""
return _flat_chart(FLAT_KINDS["box_chart"], data, kwargs)


def violin_chart(*, data: Any = None, **kwargs: Any) -> Any:
"""A data-bound violin plot (flat form; see module doc)."""
return _flat_chart(FLAT_KINDS["violin_chart"], data, kwargs)


def ecdf_chart(*, data: Any = None, **kwargs: Any) -> Any:
"""A data-bound ECDF chart (flat form; see module doc)."""
return _flat_chart(FLAT_KINDS["ecdf_chart"], data, kwargs)


def hexbin_chart(*, data: Any = None, **kwargs: Any) -> Any:
"""A data-bound hexbin chart (flat form; see module doc)."""
return _flat_chart(FLAT_KINDS["hexbin_chart"], data, kwargs)


def contour_chart(*, data: Any = None, **kwargs: Any) -> Any:
"""A data-bound contour chart (flat form; see module doc)."""
return _flat_chart(FLAT_KINDS["contour_chart"], data, kwargs)


def heatmap_chart(*, data: Any = None, **kwargs: Any) -> Any:
"""A data-bound heatmap (flat form; see module doc)."""
return _flat_chart(FLAT_KINDS["heatmap_chart"], data, kwargs)


def stairs_chart(*, data: Any = None, **kwargs: Any) -> Any:
"""A data-bound stairs chart (flat form; see module doc)."""
return _flat_chart(FLAT_KINDS["stairs_chart"], data, kwargs)


def triangle_mesh_chart(*, data: Any = None, **kwargs: Any) -> Any:
"""A data-bound triangle-mesh chart (flat form; see module doc)."""
return _flat_chart(FLAT_KINDS["triangle_mesh_chart"], data, kwargs)


def chart(*sources: Any, data: Any = None, **kwargs: Any) -> Any:
"""Place a chart: composed xy nodes (data-bound), or the component tiers.

Expand Down
Loading
Loading