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
4 changes: 4 additions & 0 deletions doc/_quartodoc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,10 @@ quartodoc:
contents:
- get_aesthetic_limits

- package: plotnine.session
contents:
- last_plot

- title: Datasets
desc: |
These datasets ship with the plotnine and you can import them with
Expand Down
2 changes: 2 additions & 0 deletions plotnine/composition/_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from ..composition._plot_layout import plot_layout
from ..composition._types import ComposeAddable
from ..options import get_option
from ..session import set_last_plot

if TYPE_CHECKING:
from pathlib import Path
Expand Down Expand Up @@ -559,6 +560,7 @@ def _draw(cmp):
self.theme.apply()
figure.set_layout_engine(PlotnineLayoutEngine(self))

set_last_plot(self)
return figure

def _draw_plots(self):
Expand Down
2 changes: 2 additions & 0 deletions plotnine/ggplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from .mapping.aes import aes
from .options import get_option
from .scales.scales import Scales
from .session import set_last_plot
from .themes.theme import theme, theme_get

if TYPE_CHECKING:
Expand Down Expand Up @@ -374,6 +375,7 @@ def draw(self, *, show: bool = False) -> Figure:
self.theme.apply()
figure.set_layout_engine(PlotnineLayoutEngine(self))

set_last_plot(self)
return figure

def _setup(self) -> Figure:
Expand Down
45 changes: 45 additions & 0 deletions plotnine/session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""
Session state for plotnine
"""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from plotnine.composition._compose import Compose
from plotnine.ggplot import ggplot

__all__ = ("last_plot",)

LAST_PLOT: ggplot | Compose | None = None


def last_plot() -> ggplot | Compose | None:
"""
Retrieve the last plot rendered in this session

Returns
-------
ggplot | Compose | None
The last plot that was rendered via `draw()`, `save()`,
or notebook display. Returns `None` if no plot has been
rendered yet.
"""
return LAST_PLOT


def set_last_plot(plot: ggplot | Compose) -> None:
"""
Save the last plot rendered in this session
"""
global LAST_PLOT
LAST_PLOT = plot


def reset_last_plot() -> None:
"""
Clear the last plot rendered in this session
"""
global LAST_PLOT
LAST_PLOT = None
8 changes: 7 additions & 1 deletion plotnine/themes/theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class theme:
# be targeted for theming by the themeables
# It is initialised in the setup method.
targets: ThemeTargets
_is_retina = False

def __init__(
self,
Expand Down Expand Up @@ -463,8 +464,13 @@ def to_retina(self) -> theme:

The result is a theme that has double the dpi.
"""
if self._is_retina:
return deepcopy(self)

dpi = self.getp("dpi")
return self + theme(dpi=dpi * 2)
self = self + theme(dpi=dpi * 2)
self._is_retina = True
return self

def _smart_title_and_subtitle_ha(
self, title: str | None, subtitle: str | None
Expand Down
43 changes: 43 additions & 0 deletions tests/test_session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from tempfile import NamedTemporaryFile

from plotnine import aes, geom_point, ggplot
from plotnine.composition import Compose
from plotnine.data import mtcars
from plotnine.session import last_plot, reset_last_plot


def test_last_plot_initially_none():
reset_last_plot()
assert last_plot() is None


def test_last_plot_after_draw():
reset_last_plot()
p = ggplot(mtcars, aes("wt", "mpg")) + geom_point()
p.draw()
assert last_plot() is not None
assert isinstance(last_plot(), ggplot)


def test_last_plot_after_save(tmp_path):
reset_last_plot()
p = ggplot(mtcars, aes("wt", "mpg")) + geom_point()

with NamedTemporaryFile(suffix=".png") as tmp_file:
p.save(tmp_file.name, verbose=False)

result = last_plot()
assert result is not None
# save() deepcopies, so last_plot won't be the same object
assert isinstance(result, ggplot)


def test_last_plot_tracks_compose():
reset_last_plot()
p1 = ggplot(mtcars, aes("wt", "mpg")) + geom_point()
p2 = ggplot(mtcars, aes("hp", "mpg")) + geom_point()
compose = p1 | p2
compose.draw()
result = last_plot()
assert result is not None
assert isinstance(result, Compose)