Skip to content

Conversation

@max-models
Copy link
Owner

@max-models max-models commented Oct 2, 2025

Summary by Sourcery

Refactor Canvas and subfigure APIs for clearer usage, introduce direct line plotting, unify method signatures, and update tutorials accordingly

New Features:

  • Add Canvas.add_line method to plot line data without explicitly managing subplots
  • Introduce Canvas.show method for interactive display without save logic
  • Expose Canvas class at package root via init.py

Enhancements:

  • Rename internal subplots storage to _subplots with public property accessor
  • Simplify and standardize add_subplot and add_tikzfigure signatures with explicit parameters
  • Overhaul LinePlot and TikzFigure constructors to accept defined arguments instead of generic kwargs
  • Remove redundant getters/setters and unused parameters across plotting methods
  • Consolidate plot, savefig, and backend handling by removing deprecated show/save arguments and adding error checks

Documentation:

  • Update tutorial notebooks to import Canvas directly, use add_line and show methods, and bump notebook metadata versions

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Oct 2, 2025

Reviewer's Guide

This PR streamlines the Canvas API by overhauling subplot management, introducing direct line-adding and rendering methods, cleaning up function signatures, exposing Canvas at the package root, and updating tutorials to reflect the new workflow.

Class diagram for refactored Canvas and LinePlot classes

classDiagram
    class Canvas {
        - _subplots: dict
        - _num_subplots: int
        - _subplot_matrix: list
        + subplots: dict (property)
        + layers: list (property)
        + add_line(x_data, y_data, layer, subplot, row, col, plot_type, **kwargs)
        + add_tikzfigure(col, row, label, **kwargs)
        + add_subplot(col, row, figsize, title, caption, description, label, grid, legend, xmin, xmax, ymin, ymax, xlabel, ylabel, xscale, yscale, xshift, yshift)
        + savefig(filename, layer_by_layer, verbose)
        + plot(backend, savefig, layers)
        + show(backend)
        + plot_matplotlib(savefig, layers, usetex)
        + plot_plotly(savefig)
    }
    class LinePlot {
        - _title: str
        - _grid: bool
        - _legend: bool
        - _xmin: float|int
        - _xmax: float|int
        - _ymin: float|int
        - _ymax: float|int
        - _xlabel: str
        - _ylabel: str
        - _xscale: float|int
        - _yscale: float|int
        - _xshift: float|int
        - _yshift: float|int
        - line_data: list
        - layered_line_data: dict
        - nodes: list
        - paths: list
        - _node_counter: int
        + add_caption(caption)
        + add_line(x_data, y_data, layer, plot_type, **kwargs)
        + plot_matplotlib(ax, layers)
        + plot_plotly()
    }
    Canvas "1" o-- "*" LinePlot : contains subplots
Loading

File-Level Changes

Change Details Files
Add direct add_line and show methods, unifying the plotting API
  • Implemented Canvas.add_line to auto-create or reuse subplots and append lines
  • Removed show flag from plot() and savefig(), decoupling figure generation from display
  • Added Canvas.show() to render existing figures without saving
src/maxplotlib/canvas/canvas.py
Refactor subplot storage and simplify add_subplot/add_tikzfigure signatures
  • Moved public subplots dict to private _subplots with a read-only property
  • Replaced generic **kwargs parsing with explicit parameters for add_subplot
  • Updated add_tikzfigure to use TikzFigure import and explicit args
src/maxplotlib/canvas/canvas.py
Revamp LinePlot constructor and internal state management
  • Changed init to explicit parameters instead of **kwargs
  • Replaced kwargs.get logic with direct attribute assignments
  • Removed obsolete getters/setters for figsize, caption, description, label
src/maxplotlib/subfigure/line_plot.py
Expose Canvas in the package root for simpler imports
  • Created src/maxplotlib/init.py exporting Canvas in all
src/maxplotlib/__init__.py
Align tutorials with the new Canvas workflow
  • Updated imports to 'from maxplotlib import Canvas'
  • Replaced c.plot()/c.savefig() calls with c.add_line() and c.show()
  • Revised notebook metadata (cell IDs and Python versions)
tutorials/tutorial_01.ipynb
tutorials/tutorial_02.ipynb
tutorials/tutorial_03.ipynb
tutorials/tutorial_04.ipynb
tutorials/tutorial_05.ipynb
tutorials/tutorial_06.ipynb
Minor formatting and style cleanups in utilities and TikZ figure code
  • Added trailing commas to multi-arg calls for consistency
  • Aligned argument lists and fixed minor lint issues
src/maxplotlib/backends/matplotlib/utils_old.py
src/maxplotlib/subfigure/tikz_figure.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `src/maxplotlib/canvas/canvas.py:73-81` </location>
<code_context>
         assert col is not None, "Not enough columns!"
         return row, col

+    def add_line(
+        self,
+        x_data,
+        y_data,
+        layer=0,
+        subplot: LinePlot | None = None,
+        row: int | None = None,
+        col: int | None = None,
+        plot_type="plot",
+        **kwargs,
+    ):
</code_context>

<issue_to_address>
**issue (bug_risk):** The add_line method's error handling for subplot indexing may not catch IndexError.

The try/except block should catch IndexError instead of KeyError, or validate row and col before accessing self._subplot_matrix.
</issue_to_address>

### Comment 2
<location> `src/maxplotlib/canvas/canvas.py:209-212` </location>
<code_context>
+        else:
+            raise ValueError("Invalid backend")
+
+    def show(self, backend="matplotlib"):
         if backend == "matplotlib":
-            return self.plot_matplotlib(show=show, savefig=savefig, layers=layers)
+            self.plot(backend="matplotlib", savefig=False, layers=None)
+            self._matplotlib_fig.show()
         elif backend == "plotly":
-            self.plot_plotly(show=show, savefig=savefig)
</code_context>

<issue_to_address>
**issue (bug_risk):** The show method for the plotly backend does not display the figure.

For the plotly backend, add plot.show() to display the figure, matching the behavior of the matplotlib backend.
</issue_to_address>

### Comment 3
<location> `src/maxplotlib/canvas/canvas.py:145-152` </location>
<code_context>
         return tikz_figure

-    def add_subplot(self, **kwargs):
+    def add_subplot(
+        self,
+        col: int | None = None,
</code_context>

<issue_to_address>
**nitpick:** add_subplot now passes col, row, and label explicitly to LinePlot.

Document or enforce which values take precedence when col, row, or label are provided both as explicit arguments and in kwargs.
</issue_to_address>

### Comment 4
<location> `src/maxplotlib/canvas/canvas.py:87-88` </location>
<code_context>
    def add_line(
        self,
        x_data,
        y_data,
        layer=0,
        subplot: LinePlot | None = None,
        row: int | None = None,
        col: int | None = None,
        plot_type="plot",
        **kwargs,
    ):
        if row is not None and col is not None:
            try:
                subplot = self._subplot_matrix[row][col]
            except KeyError:
                raise ValueError("Invalid subplot position.")
        else:
            row, col = 0, 0
            subplot = self._subplot_matrix[row][col]

        if subplot is None:
            row, col = self.generate_new_rowcol(row, col)
            subplot = self.add_subplot(col=col, row=row)

        subplot.add_line(
            x_data=x_data,
            y_data=y_data,
            layer=layer,
            plot_type=plot_type,
            **kwargs,
        )

</code_context>

<issue_to_address>
**suggestion (code-quality):** Explicitly raise from a previous error ([`raise-from-previous-error`](https://docs.sourcery.ai/Reference/Default-Rules/suggestions/raise-from-previous-error/))

```suggestion
            except KeyError as e:
                raise ValueError("Invalid subplot position.") from e
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@max-models
Copy link
Owner Author

@sourcery-ai review

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `src/maxplotlib/canvas/canvas.py:85-88` </location>
<code_context>
         return row, col

-    def add_tikzfigure(self, **kwargs):
+    def add_line(
+        self,
+        x_data,
+        y_data,
+        layer=0,
+        subplot: LinePlot | None = None,
+        row: int | None = None,
+        col: int | None = None,
+        plot_type="plot",
+        **kwargs,
+    ):
+        if row is not None and col is not None:
</code_context>

<issue_to_address>
**suggestion:** Consider handling IndexError instead of KeyError for matrix access.

Catching IndexError will ensure that out-of-bounds access to self._subplot_matrix[row][col] is handled correctly, improving error clarity for invalid indices.

```suggestion
            try:
                subplot = self._subplot_matrix[row][col]
            except IndexError:
                raise ValueError("Invalid subplot position: row or column index out of bounds.")
```
</issue_to_address>

### Comment 2
<location> `src/maxplotlib/canvas/canvas.py:246-247` </location>
<code_context>
                     print(f"Saved {full_filepath}")

-    def plot(self, backend="matplotlib", show=True, savefig=False, layers=None):
+    def plot(self, backend="matplotlib", savefig=False, layers=None):
         if backend == "matplotlib":
-            return self.plot_matplotlib(show=show, savefig=savefig, layers=layers)
+            return self.plot_matplotlib(savefig=savefig, layers=layers)
         elif backend == "plotly":
-            self.plot_plotly(show=show, savefig=savefig)
+            return self.plot_plotly(savefig=savefig)
+        else:
+            raise ValueError("Invalid backend")

</code_context>

<issue_to_address>
**suggestion:** Raise ValueError for unsupported backend in plot method.

Consider including the backend value in the ValueError message to aid debugging.

```suggestion
+        else:
+            raise ValueError(f"Invalid backend: {backend}")
```
</issue_to_address>

### Comment 3
<location> `src/maxplotlib/canvas/canvas.py:249-256` </location>
<code_context>
+            raise ValueError("Invalid backend")

-    def plot_matplotlib(self, show=True, savefig=False, layers=None, usetex=False):
+    def show(self, backend="matplotlib"):
+        if backend == "matplotlib":
+            self.plot(backend="matplotlib", savefig=False, layers=None)
+            self._matplotlib_fig.show()
+        elif backend == "plotly":
+            plot = self.plot_plotly(savefig=False)
+        else:
+            raise ValueError("Invalid backend")
+
</code_context>

<issue_to_address>
**suggestion:** show() method does not return the plotly figure.

For the plotly backend, please return the figure or call fig.show() to align with the matplotlib behavior.

```suggestion
    def show(self, backend="matplotlib"):
        if backend == "matplotlib":
            self.plot(backend="matplotlib", savefig=False, layers=None)
            self._matplotlib_fig.show()
        elif backend == "plotly":
            fig = self.plot_plotly(savefig=False)
            fig.show()
            return fig
        else:
            raise ValueError("Invalid backend")
```
</issue_to_address>

### Comment 4
<location> `src/maxplotlib/canvas/canvas.py:87-88` </location>
<code_context>
    def add_line(
        self,
        x_data,
        y_data,
        layer=0,
        subplot: LinePlot | None = None,
        row: int | None = None,
        col: int | None = None,
        plot_type="plot",
        **kwargs,
    ):
        if row is not None and col is not None:
            try:
                subplot = self._subplot_matrix[row][col]
            except KeyError:
                raise ValueError("Invalid subplot position.")
        else:
            row, col = 0, 0
            subplot = self._subplot_matrix[row][col]

        if subplot is None:
            row, col = self.generate_new_rowcol(row, col)
            subplot = self.add_subplot(col=col, row=row)

        subplot.add_line(
            x_data=x_data,
            y_data=y_data,
            layer=layer,
            plot_type=plot_type,
            **kwargs,
        )

</code_context>

<issue_to_address>
**suggestion (code-quality):** Explicitly raise from a previous error ([`raise-from-previous-error`](https://docs.sourcery.ai/Reference/Default-Rules/suggestions/raise-from-previous-error/))

```suggestion
            except KeyError as e:
                raise ValueError("Invalid subplot position.") from e
```
</issue_to_address>

### Comment 5
<location> `src/maxplotlib/subfigure/line_plot.py:21-29` </location>
<code_context>
    def __init__(
        self,
        nodes,
        path_actions=[],
        cycle=False,
        label="",
        layer=0,
        **kwargs,
    ):
        self.nodes = nodes
        self.path_actions = path_actions
        self.cycle = cycle
        self.layer = layer
        self.label = label
        self.options = kwargs

</code_context>

<issue_to_address>
**suggestion (code-quality):** Replace mutable default arguments with None ([`default-mutable-arg`](https://docs.sourcery.ai/Reference/Default-Rules/suggestions/default-mutable-arg/))

```suggestion
    def __init__(self, nodes, path_actions=None, cycle=False, label="", layer=0, **kwargs):
        if path_actions is None:
            path_actions = []
```
</issue_to_address>

### Comment 6
<location> `src/maxplotlib/subfigure/tikz_figure.py:86` </location>
<code_context>
    def __init__(
        self,
        nodes,
        path_actions=[],
        cycle=False,
        label="",
        layer=0,
        **kwargs,
    ):
        """
        Represents a path (line) connecting multiple nodes.

        Parameters:
        - nodes (list of str): List of node names to connect.
        - **kwargs: Additional TikZ path options (e.g., style, color).
        """
        self.nodes = nodes
        self.path_actions = path_actions
        self.cycle = cycle
        self.layer = layer
        self.label = label
        self.options = kwargs

</code_context>

<issue_to_address>
**issue (code-quality):** Replace mutable default arguments with None ([`default-mutable-arg`](https://docs.sourcery.ai/Reference/Default-Rules/suggestions/default-mutable-arg/))
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

max-models and others added 2 commits October 2, 2025 17:43
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
@max-models max-models merged commit 94a1426 into devel Oct 2, 2025
3 checks passed
@max-models max-models deleted the fix-show-plot branch October 2, 2025 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants