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
78 changes: 53 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,33 @@ canvas.show()

![](README_files/figure-commonmark/cell-3-output-1.png)

Use `canvas.plot(x, y)` to add data directly to a canvas. When you want
to explicitly render an already-built canvas, use `canvas.render(...)`.
The older `canvas.plot(backend=...)` spelling is still supported for
compatibility, but emits a `FutureWarning`.

Add several lines at once with shared styling:

``` python
canvas.plot_many(
[(x, np.sin(x)), (x, np.cos(x))],
labels=["sin(x)", "cos(x)"],
linewidth=2,
)
```

Common figure and axis settings can be grouped with `configure()`:

``` python
canvas.configure(
title="Trigonometry",
xlabel="Angle",
ylabel="Value",
grid=True,
facecolor="whitesmoke",
)
```

For Matplotlib-specific customization, pass method calls declaratively.
Figure methods run once and axes methods run for every subplot,
providing access to any Matplotlib API without requiring a maxplotlib
Expand Down Expand Up @@ -149,37 +176,38 @@ parts of a mixed canvas, explicitly opt into skipping unsupported
primitives:

``` python
plotly_canvas.plot(backend="plotly", allow_unsupported=True)
plotly_canvas.render(backend="plotly", allow_unsupported=True)
```

Render the same line graph directly in the terminal with the `plotext`
backend:

``` python
terminal_fig = canvas.plot(backend="plotext")
terminal_fig = canvas.render(backend="plotext")
print(terminal_fig.build(keep_colors=False))
```

Trigonometry
Runtime
┌─────────────────────────────────────────────────────────────────────────
1.00▗▄▞▀▀▀▀▀▙▄▖
▄▀▘ ▝▀▄
▗▞ ▀▄
0.67┤ ▟▀ ▀▄ │
▄▛ ▚▖
0.33┤ ▗▞ ▝▄ │
▄▀ ▚▖
│▗▞▘ ▀▄
0.00┤▀ ▝▚▖ ▞│
▀▄ ▗▞▘│
▝▚ ▄▀
-0.33┤ ▀▖ ▞▘ │
▝▚ ▟▀
-0.67┤ ▀▄ ▄▛ │
▀▄ ▗▞▘
▀▄▖ ▗▄▀▘
-1.00┤ ▝▀▜▄▄▄▄▄▞▀▘
────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────
1.00┼ ▞▞ sin(x) ──▗▄▞▀▀▀▀▀▙▄▖────────────┼─────────────────┼─────────────▄▄▀▀▀┤
▞▞ cos(x) ▄▀▘ ▝▀▄ ▄▞▀
▜▄ ▀▄ ▄▛
0.67┼┼──────▟▀─▀▄──────┼─────────▀▄──────┼─────────────────┼──────▄▀─────────┼┤
▄▛ ▝▚▖ ▚▖ ▗▞▘
0.33┼┼──▗▞────────▀▖───┼────────────▝▄───┼─────────────────┼───▗▀────────────┼┤
▄▀ ▝▚ │ ▚▖ │ ▞▘
│▗▞▘ ▀▖│ ▀▄│ │▗▀
0.00┼▞────────────────▝▙────────────────▝▚▖────────────────▟▘────────────────▄┤
│▜▖ ▀▄ ▗▛│ ▗▞▘│
│ ▝▙ ▝▚ ▟▘ │ ▄▀
-0.33┼┼─────────────────┼───▚▖────────────┼───▀▖───────▗▞───┼─────────────▞▘──┼┤
▝▄▖ ▝▚ ▗▄▘ ▟▀
-0.67┼┼─────────────────┼──────▀▖─────────┼──────▀▄─▗▀──────┼─────────▄▛──────┼┤
▝▜▄ ▄▛▘ ▗▞▘
▀▄▖ ▗▄▀ ▀▄▖ ▗▄▀▘
-1.00┼┼─────────────────┼────────────▝▀▚▄▄▄▄▄▞▀▘───────▝▀▜▄▄▄▄▄▞▀▘────────────┼┤
────────────────────────────────────────────────────────────────────
0.0 1.6 3.1 4.7 6.3
Duration Time

Expand All @@ -189,7 +217,7 @@ Or plot with the TikZ backend:
canvas.show(backend="tikzfigure")
```

![](README_files/figure-commonmark/cell-12-output-1.png)
![](README_files/figure-commonmark/cell-14-output-1.png)

### Horizontal Subplots with TikZ Backend

Expand Down Expand Up @@ -270,7 +298,7 @@ canvas.show(backend="plotext")
1.0 1.8 3.2 5.6 10.0
y x

<maxplotlib.backends.plotext.figure.PlotextFigure at 0x110a30550>
<maxplotlib.backends.plotext.figure.PlotextFigure at 0x1102a0690>

### Layers

Expand Down Expand Up @@ -306,7 +334,7 @@ Show layer 0 only, then layers 0 and 1, then everything:
canvas.show(layers=[0])
```

![](README_files/figure-commonmark/cell-16-output-1.png)
![](README_files/figure-commonmark/cell-18-output-1.png)

(<Figure size 590.551x324.803 with 1 Axes>,
array([[<Axes: xlabel='x'>]], dtype=object))
Expand All @@ -317,7 +345,7 @@ Show all layers:
canvas.show()
```

![](README_files/figure-commonmark/cell-17-output-1.png)
![](README_files/figure-commonmark/cell-19-output-1.png)

(<Figure size 590.551x324.803 with 1 Axes>,
array([[<Axes: xlabel='x'>]], dtype=object))
33 changes: 31 additions & 2 deletions README.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,35 @@ Plot the figure with the default (matplotlib) backend:
canvas.show()
```

Use `canvas.plot(x, y)` to add data directly to a canvas. When you want to
explicitly render an already-built canvas, use `canvas.render(...)`. The older
`canvas.plot(backend=...)` spelling is still supported for compatibility, but
emits a `FutureWarning`.

Add several lines at once with shared styling:

```{python}
#| output: false
canvas.plot_many(
[(x, np.sin(x)), (x, np.cos(x))],
labels=["sin(x)", "cos(x)"],
linewidth=2,
)
```

Common figure and axis settings can be grouped with `configure()`:

```{python}
#| output: false
canvas.configure(
title="Trigonometry",
xlabel="Angle",
ylabel="Value",
grid=True,
facecolor="whitesmoke",
)
```

For Matplotlib-specific customization, pass method calls declaratively. Figure
methods run once and axes methods run for every subplot, providing access to
any Matplotlib API without requiring a maxplotlib wrapper:
Expand Down Expand Up @@ -149,13 +178,13 @@ of a mixed canvas, explicitly opt into skipping unsupported primitives:

```{python}
#| output: false
plotly_canvas.plot(backend="plotly", allow_unsupported=True)
plotly_canvas.render(backend="plotly", allow_unsupported=True)
```

Render the same line graph directly in the terminal with the `plotext` backend:

```{python}
terminal_fig = canvas.plot(backend="plotext")
terminal_fig = canvas.render(backend="plotext")
print(terminal_fig.build(keep_colors=False))
```

Expand Down
Binary file modified README_files/figure-commonmark/cell-14-output-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 3 additions & 4 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,10 @@ def setup(app):
"sphinx.ext.autodoc",
]

# Execute every tutorial notebook when building the documentation.
nbsphinx_execute = "always"

templates_path = ["_templates"]
exclude_patterns = [
# tutorial_07_tikz.ipynb requires pdflatex to render — skip during docs build
"tutorials/tutorial_07_tikz.ipynb",
]


# -- Options for HTML output -------------------------------------------------
Expand Down
6 changes: 5 additions & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ documentation for details.
tutorials/tutorial_07_tikz
tutorials/tutorial_08_plotly
tutorials/tutorial_09_plotext
tutorials/tutorial_10_matplotlib_nxm_spacing
tutorials/tutorial_11_gantt_charts
tutorials/tutorial_12_flame_charts
tutorials/tutorial_13_advanced_matplotlib
tutorials/tutorial_14_axis_and_layout_controls
tutorials/tutorial_tikzfigure_subplots
tutorials/tutorial_15_tikzfigure_subplots
tutorials/tutorial_16_plotext_advanced
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "maxplotlibx"
version = "0.1.7"
version = "0.1.8"
description = "A reproducible plotting module with various backends and export options."
readme = "README.md"
requires-python = ">=3.8"
Expand All @@ -18,7 +18,7 @@ dependencies = [
"matplotlib",
"pint",
"plotly",
"plotext",
"plotext >= 6.0, < 7",
"tikzfigure[vis]>=0.3.0",
]
[project.optional-dependencies]
Expand Down
106 changes: 101 additions & 5 deletions src/maxplotlib/backends/plotext/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import re
from pathlib import Path
from typing import Any

from plotext._figure import _figure_class
from plotext import figure as _plotext_figure

_ANSI_ESCAPE_RE = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]")

Expand All @@ -12,20 +13,115 @@ def strip_ansi(text: str) -> str:
return _ANSI_ESCAPE_RE.sub("", text)


def create_plotext_figure(nrows: int = 1, ncols: int = 1) -> _figure_class:
figure = _figure_class()
class _Plotext6Axes:
"""Small compatibility surface for maxplotlib's plotext renderer."""

def __init__(self, figure):
self._figure = figure

def _draw(self, signal, label=None):
if label is not None:
signal.label(label)
self._figure.draw(signal)

def plot(self, x, y, **kwargs):
signal = self._figure.signal(x, y, marker=kwargs.get("marker"))
self._draw(signal, kwargs.get("label"))

scatter = plot

def bar(self, *args, **kwargs):
label = kwargs.pop("label", None)
kwargs.pop("color", None)
kwargs.pop("fill", None)
signal = self._figure.bar(*args, **kwargs)
self._draw(signal, label)

def error(self, x, y, *, xerr=None, yerr=None, color=None, label=None):
signal = self._figure.error(x, y, yerr, xerr, pixel=color)
self._draw(signal, label)

def matrix_plot(self, data, **kwargs):
signal = self._figure.heatmap(data, symbol=kwargs.get("marker"))
self._draw(signal)

def text(self, label, x, y, **kwargs):
# Plotext 6 keeps text colour in its marker object; the renderer only
# needs the portable text/alignment arguments here.
kwargs.pop("color", None)
signal = self._figure.text(x, y, label, **kwargs)
self._figure.draw(signal)

def title(self, label):
self._figure.title(label)

def xlabel(self, label):
self._figure.label(label, axis=0)

def ylabel(self, label):
self._figure.label(label, axis=1)

def grid(self, active=True, *_args):
self._figure.ruler("x").grid(active)
self._figure.ruler("y").grid(active)

def xlim(self, lower=None, upper=None):
self._figure.ruler("x").lim(lower, upper)

def ylim(self, lower=None, upper=None):
self._figure.ruler("y").lim(lower, upper)

def xscale(self, scale):
self._figure.ruler("x").scale(scale)

def yscale(self, scale):
self._figure.ruler("y").scale(scale)

def xticks(self, positions=None, labels=None):
self._figure.ruler("x").ticks(positions, labels)

def yticks(self, positions=None, labels=None):
self._figure.ruler("y").ticks(positions, labels)

def plotsize(self, width=None, height=None):
self._figure.plot_size(width, height)

def horizontal_line(self, position, **kwargs):
kwargs.pop("color", None)
self._figure.line(position, orientation=0, **kwargs)

def vertical_line(self, position, **kwargs):
kwargs.pop("color", None)
self._figure.line(position, orientation=1, **kwargs)

def subplots(self, rows=None, cols=None):
self._figure.subplots(rows, cols)
return self

def subplot(self, row=None, col=None):
return _Plotext6Axes(self._figure.subplot(row, col))

def __getattr__(self, name):
return getattr(self._figure, name)


def create_plotext_figure(nrows: int = 1, ncols: int = 1):
# Plotext 6 exposes the figure through its public API rather than the
# removed private ``plotext._figure._figure_class``. It is a singleton,
# so reset it before handing it to maxplotlib as a fresh canvas.
figure = _Plotext6Axes(_plotext_figure.clear())
if nrows > 1 or ncols > 1:
figure.subplots(nrows, ncols)
return figure


class PlotextFigure:
def __init__(self, figure: _figure_class, suptitle: str | None = None):
def __init__(self, figure: Any, suptitle: str | None = None):
self.figure = figure
self.suptitle = suptitle

def build(self, keep_colors: bool = True) -> str:
output = self.figure.build()
output = str(self.figure.build())
if self.suptitle:
output = f"{self.suptitle}\n{output}"
return output if keep_colors else strip_ansi(output)
Expand Down
Loading
Loading