|
6 | 6 | from io import BytesIO |
7 | 7 | from typing import TYPE_CHECKING, overload |
8 | 8 |
|
| 9 | +from .._utils.context import plot_composition_context |
9 | 10 | from .._utils.ipython import ( |
10 | 11 | get_ipython, |
11 | 12 | get_mimebundle, |
12 | 13 | is_inline_backend, |
13 | 14 | ) |
14 | | -from .._utils.quarto import is_quarto_environment |
| 15 | +from .._utils.quarto import is_knitr_engine, is_quarto_environment |
15 | 16 | from ..options import get_option |
16 | 17 | from ._plotspec import plotspec |
17 | 18 |
|
18 | 19 | if TYPE_CHECKING: |
19 | 20 | from pathlib import Path |
20 | | - from typing import Generator, Iterator, Self |
| 21 | + from typing import Generator, Iterator |
21 | 22 |
|
22 | 23 | from matplotlib.figure import Figure |
23 | 24 |
|
@@ -101,6 +102,22 @@ def __post_init__(self): |
101 | 102 | for op in self.items |
102 | 103 | ] |
103 | 104 |
|
| 105 | + def __repr__(self): |
| 106 | + """ |
| 107 | + repr |
| 108 | +
|
| 109 | + Notes |
| 110 | + ----- |
| 111 | + Subclasses that are dataclasses should be declared with |
| 112 | + `@dataclass(repr=False)`. |
| 113 | + """ |
| 114 | + # knitr relies on __repr__ to automatically print the last object |
| 115 | + # in a cell. |
| 116 | + if is_knitr_engine(): |
| 117 | + self.show() |
| 118 | + return "" |
| 119 | + return super().__repr__() |
| 120 | + |
104 | 121 | @abc.abstractmethod |
105 | 122 | def __or__(self, rhs: ggplot | Compose) -> Compose: |
106 | 123 | """ |
@@ -321,6 +338,15 @@ def _create_gridspec(self, figure, nest_into): |
321 | 338 | self.nrow, self.ncol, figure, nest_into=nest_into |
322 | 339 | ) |
323 | 340 |
|
| 341 | + def _setup(self) -> Figure: |
| 342 | + """ |
| 343 | + Setup this instance for the building process |
| 344 | + """ |
| 345 | + if not hasattr(self, "figure"): |
| 346 | + self._create_figure() |
| 347 | + |
| 348 | + return self.figure |
| 349 | + |
324 | 350 | def _create_figure(self): |
325 | 351 | import matplotlib.pyplot as plt |
326 | 352 |
|
@@ -364,6 +390,13 @@ def _make_plotspecs( |
364 | 390 | self.figure = plt.figure() |
365 | 391 | self.plotspecs = list(_make_plotspecs(self, None)) |
366 | 392 |
|
| 393 | + def _draw_plots(self): |
| 394 | + """ |
| 395 | + Draw all plots in the composition |
| 396 | + """ |
| 397 | + for ps in self.plotspecs: |
| 398 | + ps.plot.draw() |
| 399 | + |
367 | 400 | def show(self): |
368 | 401 | """ |
369 | 402 | Display plot in the cells output |
@@ -400,15 +433,9 @@ def draw(self, *, show: bool = False) -> Figure: |
400 | 433 | from .._mpl.layout_manager import PlotnineCompositionLayoutEngine |
401 | 434 |
|
402 | 435 | with plot_composition_context(self, show): |
403 | | - self._create_figure() |
404 | | - figure = self.figure |
405 | | - |
406 | | - for ps in self.plotspecs: |
407 | | - ps.plot.draw() |
408 | | - |
409 | | - self.figure.set_layout_engine( |
410 | | - PlotnineCompositionLayoutEngine(self) |
411 | | - ) |
| 436 | + figure = self._setup() |
| 437 | + self._draw_plots() |
| 438 | + figure.set_layout_engine(PlotnineCompositionLayoutEngine(self)) |
412 | 439 | return figure |
413 | 440 |
|
414 | 441 | def save( |
@@ -442,42 +469,3 @@ def save( |
442 | 469 | plot = (self + theme(dpi=dpi)) if dpi else self |
443 | 470 | figure = plot.draw() |
444 | 471 | figure.savefig(filename, format=format) |
445 | | - |
446 | | - |
447 | | -@dataclass |
448 | | -class plot_composition_context: |
449 | | - cmp: Compose |
450 | | - show: bool |
451 | | - |
452 | | - def __post_init__(self): |
453 | | - import matplotlib as mpl |
454 | | - |
455 | | - # The dpi is needed when the figure is created, either as |
456 | | - # a parameter to plt.figure() or an rcParam. |
457 | | - # https://github.com/matplotlib/matplotlib/issues/24644 |
458 | | - # When drawing the Composition, the dpi themeable is infective |
459 | | - # because it sets the rcParam after this figure is created. |
460 | | - rcParams = {"figure.dpi": self.cmp.last_plot.theme.getp("dpi")} |
461 | | - self._rc_context = mpl.rc_context(rcParams) |
462 | | - |
463 | | - def __enter__(self) -> Self: |
464 | | - """ |
465 | | - Enclose in matplolib & pandas environments |
466 | | - """ |
467 | | - self._rc_context.__enter__() |
468 | | - return self |
469 | | - |
470 | | - def __exit__(self, exc_type, exc_value, exc_traceback): |
471 | | - import matplotlib.pyplot as plt |
472 | | - |
473 | | - if exc_type is None: |
474 | | - if self.show: |
475 | | - plt.show() |
476 | | - else: |
477 | | - plt.close(self.cmp.figure) |
478 | | - else: |
479 | | - # There is an exception, close any figure |
480 | | - if hasattr(self.cmp, "figure"): |
481 | | - plt.close(self.cmp.figure) |
482 | | - |
483 | | - self._rc_context.__exit__(exc_type, exc_value, exc_traceback) |
0 commit comments