Skip to content

Commit f341a9c

Browse files
committed
feat: add lifecycle handles for field plots
1 parent ecc8a4c commit f341a9c

7 files changed

Lines changed: 335 additions & 256 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ All notable changes to SkigenPlot will be documented in this file.
1919
operations.
2020
- Composite stem plots return one stable handle for updating, styling, hiding,
2121
or removing their stems and markers together.
22+
- Heatmap, contour, filled-contour, and hexbin plots return independent stable
23+
handles for styling, visibility, and removal, allowing field plots to compose
24+
without replacing earlier field geometry.
2225
- Theme-aware legends for labelled 2D series with automatic or fixed-corner
2326
placement.
2427
- Independent x/y axis limits with getters, autoscale reset, validation, and

‎README.md‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,13 @@ int main(int argc, char* argv[]) {
4343
```
4444
4545
`plot()`, `scatter()`, `hist()`, `bar()`, `barh()`, `fillBetween()`, `step()`,
46-
`errorbar()`, `stem()`, `boxplot()`, `violinplot()`, `quiver()`, and `pie()`
47-
return stable `SeriesHandle` values.
46+
`errorbar()`, `stem()`, `boxplot()`, `violinplot()`, `quiver()`, `pie()`,
47+
`imshow()`, `contour()`, `contourf()`, and `hexbin()` return stable
48+
`SeriesHandle` values.
4849
Use `updateSeriesData()`, `setSeriesStyle()`, `setSeriesVisible()`, and
4950
`removeSeries()` to modify one series without clearing the rest of the view.
51+
Field-plot data is immutable in v1, so `updateSeriesData()` returns `false`
52+
for those handles; style, visibility, and removal remain supported.
5053
Visible labelled series can be shown in a theme-aware legend with automatic
5154
or explicit corner placement.
5255

‎doc/dev-notes/v1.0.0_requirements.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ because they affect ordinary scientific workflows or public API stability.
104104

105105
| Capability | Status | v1.0 requirement | Matplotlib analogue |
106106
|---|---|---|---|
107-
| Series lifecycle | **Partial** | Plot-creation methods return a stable `SeriesHandle`; data, style, visibility, and removal can be updated without clearing unrelated series. Invalidated handles fail predictably. | `Artist`, `Line2D.set_data`, `Artist.remove` |
107+
| Series lifecycle | **Implemented** | Plot-creation methods return a stable `SeriesHandle`; supported data/style updates, visibility, and removal do not clear unrelated series. Field data updates are unsupported in v1 and return `false`. Invalidated handles fail predictably. | `Artist`, `Line2D.set_data`, `Artist.remove` |
108108
| Legend | **Implemented** | Render labels from `PlotStyle::label`; support show/hide, automatic placement, and four corner placements. Unlabelled and hidden series are omitted. | `legend` |
109109
| Axis limits | **Implemented** | Get/set/reset x and y limits independently. Explicit limits disable autoscaling for that axis until reset. Reversed limits intentionally invert an axis. Non-finite and equal endpoints are rejected. | `xlim`, `ylim`, `autoscale`, axis inversion |
110110
| Axis scales | **Implemented** | Independent linear and base-10 logarithmic x/y scales. Limits remain in data units, reversed positive limits preserve inversion, pan/zoom operate in transformed space, and non-positive samples are omitted from logarithmic bounds and rendering. | `xscale`, `yscale`, `semilogx`, `semilogy`, `loglog` |
@@ -248,7 +248,7 @@ not block v1.0.0.
248248
or gallery coverage.
249249
- [x] Scrolling telemetry uses fixed-capacity circular sample storage and the
250250
existing dynamic line-rendering pipeline.
251-
- [ ] The essential v1.0 usability surface above is implemented and documented:
251+
- [x] The essential v1.0 usability surface above is implemented and documented:
252252
series handles, legends, axis limits, linear/log scales, line styles,
253253
markers, and deterministic input validation.
254254
- [x] Multiple line/scatter/fill series compose in one view without changing

‎doc/website/docs/api/plotview.mdx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,13 @@ public:
8686
`PlotView` is the central class of SkigenPlot. It accepts Eigen matrix expressions directly — no manual data conversion needed. Data is interleaved into GPU vertex buffers and rendered using Qt's RHI abstraction layer.
8787
8888
Line, scatter, histogram, bar, horizontal-bar, filled-area, step, error-bar,
89-
stem, box, violin, quiver, and pie creation returns an opaque `SeriesHandle`.
89+
stem, box, violin, quiver, pie, heatmap, contour, filled-contour, and hexbin
90+
creation returns an opaque `SeriesHandle`.
9091
Handles remain stable when other series are removed. Mutating or removing an
9192
invalid, stale, or foreign handle returns `false` and leaves the view unchanged.
9293
Creation with insufficient input returns an invalid handle.
94+
Field geometry is immutable in v1, so `updateSeriesData()` returns `false` for
95+
field handles; style, visibility, containment, and removal are supported.
9396
9497
X and y axes independently support `AxisScale::Linear` and
9598
`AxisScale::Log10`. Public limits remain in data units and may be reversed.

‎include/skigen/plot/plotview.h‎

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -249,14 +249,14 @@ class SKIGENPLOT_EXPORT PlotView : public QRhiWidget {
249249
/// to [min, max] across finite cells unless @p vmin < @p vmax is given.
250250
/// Non-finite cells are omitted; all-invalid input leaves the view unchanged.
251251
template <typename Derived>
252-
void imshow(const Eigen::MatrixBase<Derived>& m,
252+
auto imshow(const Eigen::MatrixBase<Derived>& m,
253253
Colormap cmap = Colormap::Viridis,
254-
float vmin = 0.0f, float vmax = 0.0f)
254+
float vmin = 0.0f, float vmax = 0.0f) -> SeriesHandle
255255
{
256256
Eigen::MatrixXf mf = m.derived().template cast<float>().eval();
257-
imshowImpl({mf.data(), static_cast<std::size_t>(mf.size())},
258-
static_cast<int>(mf.rows()), static_cast<int>(mf.cols()),
259-
cmap, vmin, vmax);
257+
return imshowImpl({mf.data(), static_cast<std::size_t>(mf.size())},
258+
static_cast<int>(mf.rows()), static_cast<int>(mf.cols()),
259+
cmap, vmin, vmax);
260260
}
261261

262262
// ── 2D contour lines / filled contour ───────────────────────────
@@ -266,26 +266,26 @@ class SKIGENPLOT_EXPORT PlotView : public QRhiWidget {
266266
/// maps to grid coordinate (c, rows-1-r), matching imshow(). Cells with
267267
/// non-finite corners are omitted. Values of @p levels below 1 use 1.
268268
template <typename Derived>
269-
void contour(const Eigen::MatrixBase<Derived>& z, int levels = 8,
270-
const PlotStyle& style = {})
269+
auto contour(const Eigen::MatrixBase<Derived>& z, int levels = 8,
270+
const PlotStyle& style = {}) -> SeriesHandle
271271
{
272272
Eigen::MatrixXf zf = z.derived().template cast<float>().eval();
273-
contourImpl({zf.data(), static_cast<std::size_t>(zf.size())},
274-
static_cast<int>(zf.rows()), static_cast<int>(zf.cols()),
275-
levels, style);
273+
return contourImpl({zf.data(), static_cast<std::size_t>(zf.size())},
274+
static_cast<int>(zf.rows()), static_cast<int>(zf.cols()),
275+
levels, style);
276276
}
277277

278278
/// @brief Filled contour: colour each finite cell by its value band using
279279
/// @p cmap (cell-level quantisation). Companion to contour(). Values of
280280
/// @p levels below 2 use 2.
281281
template <typename Derived>
282-
void contourf(const Eigen::MatrixBase<Derived>& z, int levels = 10,
283-
Colormap cmap = Colormap::Viridis)
282+
auto contourf(const Eigen::MatrixBase<Derived>& z, int levels = 10,
283+
Colormap cmap = Colormap::Viridis) -> SeriesHandle
284284
{
285285
Eigen::MatrixXf zf = z.derived().template cast<float>().eval();
286-
contourfImpl({zf.data(), static_cast<std::size_t>(zf.size())},
287-
static_cast<int>(zf.rows()), static_cast<int>(zf.cols()),
288-
levels, cmap);
286+
return contourfImpl({zf.data(), static_cast<std::size_t>(zf.size())},
287+
static_cast<int>(zf.rows()), static_cast<int>(zf.cols()),
288+
levels, cmap);
289289
}
290290

291291
// ── 2D violin plot (KDE density per group) ──────────────────────
@@ -324,14 +324,15 @@ class SKIGENPLOT_EXPORT PlotView : public QRhiWidget {
324324
/// are omitted; all-invalid input leaves the view unchanged. Values of
325325
/// @p gridsize below 2 use 2.
326326
template <typename DX, typename DY>
327-
void hexbin(const Eigen::MatrixBase<DX>& x, const Eigen::MatrixBase<DY>& y,
327+
auto hexbin(const Eigen::MatrixBase<DX>& x, const Eigen::MatrixBase<DY>& y,
328328
int gridsize = 20, Colormap cmap = Colormap::Viridis)
329+
-> SeriesHandle
329330
{
330331
Eigen::VectorXf xf = x.derived().template cast<float>().eval();
331332
Eigen::VectorXf yf = y.derived().template cast<float>().eval();
332-
hexbinImpl({xf.data(), static_cast<std::size_t>(xf.size())},
333-
{yf.data(), static_cast<std::size_t>(yf.size())},
334-
gridsize, cmap);
333+
return hexbinImpl({xf.data(), static_cast<std::size_t>(xf.size())},
334+
{yf.data(), static_cast<std::size_t>(yf.size())},
335+
gridsize, cmap);
335336
}
336337

337338
// ── 2D pie chart ────────────────────────────────────────────────
@@ -494,6 +495,13 @@ class SKIGENPLOT_EXPORT PlotView : public QRhiWidget {
494495
const PlotStyle& style,
495496
SeriesHandle groupHandle = {},
496497
bool supportsDataUpdate = false) -> SeriesHandle;
498+
auto addFieldSeries(std::vector<float> vertices, bool coloredTriangles,
499+
const BoundingBox2D& bounds,
500+
const PlotStyle& style = {},
501+
std::optional<Colormap> colormap = std::nullopt,
502+
float vmin = 0.0f, float vmax = 1.0f)
503+
-> SeriesHandle;
504+
void refreshColorbar();
497505

498506
auto histImpl(std::span<const float> values, int bins, bool density,
499507
const PlotStyle& style) -> SeriesHandle;
@@ -510,17 +518,17 @@ class SKIGENPLOT_EXPORT PlotView : public QRhiWidget {
510518
auto errorbarImpl(std::span<const float> x, std::span<const float> y,
511519
std::span<const float> yerr, const PlotStyle& style)
512520
-> SeriesHandle;
513-
void imshowImpl(std::span<const float> data, int rows, int cols,
514-
Colormap cmap, float vmin, float vmax);
515-
void contourImpl(std::span<const float> data, int rows, int cols,
516-
int levels, const PlotStyle& style);
517-
void contourfImpl(std::span<const float> data, int rows, int cols,
518-
int levels, Colormap cmap);
521+
auto imshowImpl(std::span<const float> data, int rows, int cols,
522+
Colormap cmap, float vmin, float vmax) -> SeriesHandle;
523+
auto contourImpl(std::span<const float> data, int rows, int cols,
524+
int levels, const PlotStyle& style) -> SeriesHandle;
525+
auto contourfImpl(std::span<const float> data, int rows, int cols,
526+
int levels, Colormap cmap) -> SeriesHandle;
519527
auto quiverImpl(std::span<const float> x, std::span<const float> y,
520528
std::span<const float> u, std::span<const float> v,
521529
const PlotStyle& style) -> SeriesHandle;
522-
void hexbinImpl(std::span<const float> x, std::span<const float> y,
523-
int gridsize, Colormap cmap);
530+
auto hexbinImpl(std::span<const float> x, std::span<const float> y,
531+
int gridsize, Colormap cmap) -> SeriesHandle;
524532
auto pieImpl(std::span<const float> values) -> SeriesHandle;
525533

526534
void setPointCloudData(std::span<const float> data, int vertexCount,

0 commit comments

Comments
 (0)