|
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