Skip to content

FacetChart notebook display leaks global CSS into the page (no widget path; _repr_html_ returns a standalone document) #90

Description

@Darksinian

Summary

Displaying a FacetChart in Jupyter breaks the notebook's layout: the facet grid spills across cell boundaries and the surrounding cells look wrong. Regular Charts display fine.

Repro

import numpy as np
import xy

rng = np.random.default_rng(42)
g = np.array(["A", "B", "C", "D"])[rng.integers(0, 4, 8000)]
x = rng.normal(0, 1, 8000)
y = x * np.array([0.2, 0.8, -0.5, 1.5])[np.searchsorted(["A", "B", "C", "D"], g)] + rng.normal(0, 0.4, 8000)

xy.facet_chart(
    xy.scatter(x="x", y="y", size=3.0, opacity=0.5),
    by="g", cols=2,
    data={"x": x, "y": y, "g": g},
    width=900, height=500,
)

Run in a notebook → the output renders but leaks outside its cell, visually corrupting neighboring cells.

Root cause

  1. No widget display path. Chart displays via _ipython_display_ → the anywidget FigureWidget, contained in the output area. FacetChart only implements _repr_html_ (components.py:3293), which delegates to FacetGrid.to_html() (facets.py:170) — a full standalone HTML document (<!doctype html><html><head>…). Jupyter injects that markup inline into the output div, so its document-level stylesheet applies to the notebook page:

    html,body{margin:0;width:100%;min-height:100%;font-family:system-ui,sans-serif;background:#fff;}

    plus full-width grid styles — hence the spill.

  2. Compounding: height is per-panel for facets. FacetGrid.panel_height returns self.height verbatim (facets.py:154) while width is total and divided across columns (panel_width, facets.py:150). height=500 with 2 rows quietly composes a ~1012px grid. The width/height asymmetry is easy to hit and isn't documented in facet_chart's docstring.

Suggested fix

  • Give FacetChart a real notebook path: _ipython_display_ that either composes panel FigureWidgets in a container widget, or at minimum wraps the standalone doc in an <iframe srcdoc=…> so its CSS stays sandboxed. (The iframe wrapper is a working user-side workaround today.)
  • Scope the standalone doc's CSS to a root class instead of html,body so inline injection degrades gracefully.
  • Either make facet height mean total height (divided across rows, symmetric with width) or call out the per-panel semantics in the facet_chart docstring.

Environment: Jupyter (ipywidgets/anywidget transport), Linux, xy editable install of main @ 541a7da, Python 3.14.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions