Skip to content

[Feature]: Plot a result with result.plot(), behind a pluggable renderer #32

Description

@fedonman

Feature Summary

There is no way to plot a result. Add result.plot(), with the drawing behind a renderer so matplotlib is one implementation rather than the only one, and a theme a user can override.

Motivation / Use Case

QProgramResult hands back a bare DataArray and the docs tell you to write matplotlib. xarray's own .plot() does not fill the gap, because a measurement array carries a trailing IQ dimension: a 1-D sweep looks two-dimensional and comes back as a heatmap with IQ on the x axis, and a 2-D sweep comes back as a histogram. The guide's da.sel(IQ="I").plot() works only because it peels that dimension off first, and it stops working the moment you want I and Q on the same axes.

Across the ten example pages there are 17 lines of hand-rolled matplotlib, covering four of the six shapes those pages use. The other six pages embed a figure and show no code at all, including the four-series overlay and the two-panel stack. The script that renders the documentation figures already implements every one of them with a light and a dark theme, and it does not ship in the wheel, so everyone reinvents it.

Depends on #31 for anything better than a bare variable id on an axis.

Proposed Solution

result = qp.simulate(program)

result.plot(m0)                                   # a line per quadrature, kind inferred
result.plot(m0, channels="magnitude")             # hypot(I, Q), which two examples hand-roll
result.plot(m0, x="freq", style=Style(theme=dark))

Build a backend-neutral description of the figure first, from numpy and xarray only, then let a renderer draw it. That seam is what makes a second renderer possible, and it is the same move explain already makes by reusing the .qp writer's serializers instead of writing its own. Register renderers the way register_sweep_source registers sweep sources.

kind is inferred from the shape once the trailing IQ and time dims are set aside: one remaining dim gives lines, two give a heatmap. Scatter stays explicit, since I against Q is a choice no shape inspection yields.

Two things to settle. A composed sweep dim should require x=, because coords["gain|freq"] does not raise, it returns a plain integer range, so guessing gives a wrong axis that looks entirely plausible. And whether QProgramResult gets a _repr_html_ the way waveforms have one.

Worth keeping out of the first cut: the two bespoke example layouts, the shared-x stack and the equal-aspect scatter with a threshold from the model. Nothing on the result says those belong in one figure in that arrangement, so returning composable axes and letting those pages keep their own code is the better answer.

Surfaces It Touches

The reference executor's results only. Nothing in .qp, the grammar or the capability protocol.

matplotlib has to stay behind the viz extra, so the model and theme layers must import numpy and xarray only, and pyplot stays inside the renderer. For the same reason themes and styles want to be plain frozen dataclasses.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    executorTouches the reference executor and results

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions