Skip to content
Merged
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
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,31 @@ in the README).

## [Unreleased]

### Added
- Funnel charts joined the core declarative API (protocol v13):
`xy.funnel_chart(stages, values)` / `xy.funnel(...)` draw one centered
segment per stage in declared order — never sorted — with explicit
`geometry="area"|"bar"` modes, `neck="rect"|"taper"`, per-geometry segment
gaps, and a `min_width` floor that keeps zero/tiny stages visible without
touching their reported values. Conversion arithmetic (value, prior, overall
share, previous-stage conversion, drop-off; `None` over zero denominators)
rides labels with a documented inside/outside/hidden collision ladder,
hover tooltips, click events, and ordered keyboard traversal with
screen-reader announcements. Per-stage colors are a categorical channel over
the stage names (theme `palette={...}` mappings pin by stage name; legend
rows opt in via `xy.legend(...)`), and per-trace `stroke`/`stroke-width`/
opacity style compiles to all three renderers. The client draws
antialiased quads through a dedicated funnel program sharing the ribbon
fragment stage; SVG/PNG/PDF exports emit the same `_scene.funnel_quad`
geometry, pinned by golden tests.

### Changed
- Native raster exports now resolve categorical palettes through the same
indexed fallback as SVG and density rendering. Browser-only or otherwise
unresolvable entries in hand-authored payloads emit a `RuntimeWarning` and
fall back to the built-in color at the same index, preserving distinct
categories instead of silently collapsing them onto one shared color.

## [0.0.5] - 2026-07-31

### Added
Expand Down
2 changes: 1 addition & 1 deletion docs/app/scripts/check_html_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
ROUTES_ROOT = APP_ROOT / ".web" / "app" / "routes"
LIVE_PREVIEW_MARKERS = ("python demo exec", "python demo-only exec")
INLINE_SVG_PREVIEW_ROUTES = {"/overview/gallery/"}
INLINE_SVG_PREVIEW_COUNT = 34
INLINE_SVG_PREVIEW_COUNT = 35
XY_PAYLOAD_PATTERN = re.compile(r'["\'](?P<url>/docs/xy/xy/[a-f0-9]+\.xyf)["\']')
XY_PAYLOAD_MAGIC = b"XYBF"
LLMS_DIRECTIVE = "For AI agents: the complete XY documentation index is at"
Expand Down
35 changes: 18 additions & 17 deletions docs/app/tests/test_docs_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -1560,18 +1560,18 @@ def test_chart_gallery_grid_renders_every_type_as_inline_svg(
chart_section = next(
leaves for title, _landing_route, _icon, leaves in DOCS_SECTIONS if title == "Chart Gallery"
)
assert len(chart_section) == 20
assert len(chart_section) == 21
assert "XYChart" not in rendered
assert rendered.count("dangerouslySetInnerHTML") == 34
assert rendered.count("dangerouslySetInnerHTML") == 35
assert rendered.count('id:"xy-chart-gallery"') == 1
assert rendered.count("main:has(#xy-chart-gallery) > div:has(#toc-navigation)") == 1
assert rendered.count("main:has(#xy-chart-gallery) > div:has(article #xy-chart-gallery)") == 1
assert rendered.count("display: none") == 1
assert rendered.count("max-width: 88rem") == 1
assert rendered.count("2xl:grid-cols-3") == 9
assert rendered.count("aspect-[320/232]") == 34
assert rendered.count("shadow-large") == 34
assert rendered.count("transition-bg") == 34
assert rendered.count("aspect-[320/232]") == 35
assert rendered.count("shadow-large") == 35
assert rendered.count("transition-bg") == 35
Comment thread
coderabbitai[bot] marked this conversation as resolved.
assert "--gallery-preview-surface: #fff" in rendered
assert "--gallery-preview-fill: #efeaff" in rendered
assert "--gallery-preview-soft: #dccfff" in rendered
Expand All @@ -1582,9 +1582,10 @@ def test_chart_gallery_grid_renders_every_type_as_inline_svg(
assert "object-contain" not in rendered
assert "object-center" not in rendered
assert "xy-tailwind-bridge" not in rendered
assert rendered.count("size:14") == 34
assert rendered.count("size:14") == 35
assert "size:6" not in rendered
for chart_type in (
"Funnel",
"Line",
"Area",
"Step",
Expand Down Expand Up @@ -1653,7 +1654,7 @@ def test_chart_gallery_inline_svgs_share_the_component_preview_style() -> None:
for item in group.items
}

assert len(previews) == 34
assert len(previews) == 35
for svg in previews.values():
assert 'viewBox="0 0 320 232"' in svg
assert '<rect x="52" y="62" width="216" height="108" rx="12"' in svg
Expand Down Expand Up @@ -1819,7 +1820,7 @@ def test_chart_gallery_combines_only_the_requested_related_tiles() -> None:
titles = {item.title for group in _GALLERY_GROUPS for item in group.items}
section_titles = [group.title for group in _GALLERY_GROUPS]

assert len(titles) == 34
assert len(titles) == 35
assert section_titles[:3] == [
"Line and Area",
"Distributions",
Expand Down Expand Up @@ -1847,6 +1848,7 @@ def test_chart_gallery_combines_only_the_requested_related_tiles() -> None:
("Stem", "/charts/stem-plot/"),
("Segments", "/charts/segments/"),
("Sankey", "/charts/sankey/"),
("Funnel", "/charts/funnel-chart/"),
Comment thread
Alek99 marked this conversation as resolved.
("Triangle Mesh", "/components/triangle-mesh/"),
]
assert {"Step + Stairs", "Bar + Column"} <= titles
Expand Down Expand Up @@ -2109,18 +2111,17 @@ def test_inline_svg_gallery_validator_requires_every_styled_preview(tmp_path: Pa
"""Accept only the complete code-native gallery in the production route."""
module_path = tmp_path / "route.jsx"
preview = 'viewBox=\\"0 0 320 232\\"'
module_path.write_text(
preview * 34 + "gallery-preview-surface aspect-[320/232] shadow-large",
encoding="utf-8",
)
surface = "gallery-preview-surface aspect-[320/232] shadow-large"
expected = check_html_routes.INLINE_SVG_PREVIEW_COUNT
module_path.write_text(preview * expected + surface, encoding="utf-8")

check_html_routes.validate_inline_svg_gallery("/overview/gallery/", module_path)

module_path.write_text(
preview * 33 + "gallery-preview-surface aspect-[320/232] shadow-large",
encoding="utf-8",
)
with pytest.raises(RuntimeError, match="33 previews, expected 34"):
module_path.write_text(preview * (expected - 1) + surface, encoding="utf-8")
with pytest.raises(
RuntimeError,
match=rf"{expected - 1} previews, expected {expected}",
):
check_html_routes.validate_inline_svg_gallery("/overview/gallery/", module_path)


Expand Down
2 changes: 2 additions & 0 deletions docs/app/xy_docs/api_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
xy.stem_chart,
xy.segments_chart,
xy.sankey_chart,
xy.funnel_chart,
xy.triangle_mesh_chart,
),
),
Expand Down Expand Up @@ -114,6 +115,7 @@
xy.segments,
xy.ribbon,
xy.sankey,
xy.funnel,
xy.triangle_mesh,
)

Expand Down
1 change: 1 addition & 0 deletions docs/app/xy_docs/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
("Stem", "/charts/stem-plot/"),
("Segments", "/charts/segments/"),
("Sankey", "/charts/sankey/"),
("Funnel", "/charts/funnel-chart/"),
),
),
(
Expand Down
4 changes: 4 additions & 0 deletions docs/app/xy_docs/gallery.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ class GalleryGroup:
GalleryItem("Stem", route="/charts/stem-plot/"),
GalleryItem("Segments", route="/charts/segments/"),
GalleryItem("Sankey", route="/charts/sankey/"),
GalleryItem("Funnel", route="/charts/funnel-chart/"),
GalleryItem("Triangle Mesh", route="/components/triangle-mesh/"),
),
),
Expand Down Expand Up @@ -275,6 +276,9 @@ class GalleryGroup:
""",
"Sankey": """
<path d="M85 82C122 82 124 91 157 91V108C124 108 122 99 85 99Z" class="preview-fill"/><path d="M85 102C123 102 124 130 157 130V151C124 151 122 123 85 123Z" class="preview-fill-soft"/><path d="M174 92C207 92 208 76 239 76V97C208 97 207 113 174 113Z" class="preview-fill"/><path d="M174 118C207 118 208 125 239 125V148C208 148 207 141 174 141Z" class="preview-fill-soft"/><rect x="72" y="78" width="13" height="49" rx="3" class="preview-fill-strong"/><rect x="157" y="87" width="17" height="68" rx="3" class="preview-fill-strong"/><rect x="239" y="72" width="13" height="29" rx="3" class="preview-fill-strong"/><rect x="239" y="121" width="13" height="31" rx="3" class="preview-fill-strong"/>
""",
"Funnel": """
<path d="M76 74H244L216 98H104Z" class="preview-fill"/><path d="M106 102H214L192 126H128Z" class="preview-fill-strong"/><path d="M130 130H190L178 154H142Z" class="preview-fill-soft"/><path d="M148 158H172V174H148Z" class="preview-fill"/><path d="M138 86H182M144 112H176M150 140H170" class="preview-label-line"/>
""",
"Triangle Mesh": """
<path d="M72 147L105 87L143 139L175 73L214 121L249 82L262 151H72Z" class="preview-fill"/><path d="M72 147L105 87L143 139L175 73L214 121L249 82L262 151M72 147L143 139L214 121L262 151M105 87L175 73L249 82M105 87L143 139L175 73L214 121L249 82" class="preview-line preview-line-soft"/>
Expand Down
2 changes: 1 addition & 1 deletion docs/app/xy_docs/sidebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"shapes",
tuple(
(title, _chart_gallery_routes[title])
for title in ("Uncertainty", "Stem", "Segments", "Sankey")
for title in ("Uncertainty", "Stem", "Segments", "Sankey", "Funnel")
),
),
)
Expand Down
Loading
Loading