Skip to content

Commit

Permalink
Fix retina output in quarto
Browse files Browse the repository at this point in the history
  • Loading branch information
has2k1 committed Nov 5, 2024
1 parent 0f8b87e commit 1682dcb
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 30 deletions.
2 changes: 2 additions & 0 deletions doc/changelog.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ title: Changelog
- Fix expansion of datetime and timedelta scales when using both the lower
and upper addition constants.

- In quarto documents make the output `retina` even if the `fig-format` is
`png`.

## v0.14.0
(2024-10-28)
Expand Down
10 changes: 7 additions & 3 deletions plotnine/_utils/ipython.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ def is_inline_backend():
return "matplotlib_inline.backend_inline" in mpl.get_backend()


def get_display_function(format: FigureFormat) -> Callable[[bytes], None]:
def get_display_function(
format: FigureFormat, figure_size_px: tuple[int, int]
) -> Callable[[bytes], None]:
"""
Return a function that will display the plot image
"""
Expand All @@ -52,14 +54,16 @@ def get_display_function(format: FigureFormat) -> Callable[[bytes], None]:
display_svg,
)

w, h = figure_size_px

def png(b: bytes):
display_png(Image(b, format="png"))
display_png(Image(b, format="png", width=w, height=h))

def retina(b: bytes):
display_png(Image(b, format="png", retina=True))

def jpeg(b: bytes):
display_jpeg(Image(b, format="jpeg"))
display_jpeg(Image(b, format="jpeg", width=w, height=h))

def svg(b: bytes):
display_svg(SVG(b))
Expand Down
33 changes: 33 additions & 0 deletions plotnine/_utils/quarto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import os


def is_quarto_environment() -> bool:
"""
Return True if running in quarto
"""
return "QUARTO_FIG_WIDTH" in os.environ


def set_options_from_quarto():
"""
Set options from quarto
"""
from plotnine.options import set_option

dpi = int(os.environ["QUARTO_FIG_DPI"])
figure_size = (
float(os.environ["QUARTO_FIG_WIDTH"]),
float(os.environ["QUARTO_FIG_HEIGHT"]),
)
# quarto verifies the format
# If is retina, it doubles the original dpi and changes the
# format to png. Since we cannot tell whether fig-format is
# png or retina, we assume retina.
figure_format = os.environ["QUARTO_FIG_FORMAT"]
if figure_format == "png":
figure_format = "retina"
dpi = dpi // 2

set_option("dpi", dpi)
set_option("figure_size", figure_size)
set_option("figure_format", figure_format)
17 changes: 11 additions & 6 deletions plotnine/ggplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
get_ipython,
is_inline_backend,
)
from ._utils.quarto import is_quarto_environment
from .coords import coord_cartesian
from .exceptions import PlotnineError, PlotnineWarning
from .facets import facet_null
Expand Down Expand Up @@ -127,10 +128,8 @@ def __str__(self) -> str:
"""
Return a wrapped display size (in pixels) of the plot
"""
dpi = self.theme.getp("dpi")
width, height = self.theme.getp("figure_size")
W, H = int(width * dpi), int(height * dpi)
return f"<ggplot: ({W} x {H})>"
w, h = self.theme._figure_size_px
return f"<ggplot: ({w} x {h})>"

def _ipython_display_(self):
"""
Expand All @@ -148,7 +147,12 @@ def show(self):
Users should prefer this method instead of printing or repring
the object.
"""
self._display() if is_inline_backend() else self.draw(show=True)
if is_inline_backend() or is_quarto_environment():
# Take charge of the display because we have to make
# adjustments for retina output.
self._display()
else:
self.draw(show=True)

def _display(self):
"""
Expand All @@ -172,9 +176,10 @@ def _display(self):
self.theme = self.theme.to_retina()
save_format = "png"

figure_size_px = self.theme._figure_size_px
buf = BytesIO()
self.save(buf, format=save_format, verbose=False)
display_func = get_display_function(format)
display_func = get_display_function(format, figure_size_px)
display_func(buf.getvalue())

def __deepcopy__(self, memo: dict[Any, Any]) -> ggplot:
Expand Down
25 changes: 4 additions & 21 deletions plotnine/options.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from __future__ import annotations

import os
from typing import TYPE_CHECKING

from ._utils import quarto

if TYPE_CHECKING:
from typing import Any, Literal, Optional, Type

Expand Down Expand Up @@ -125,23 +126,5 @@ def set_option(name: str, value: Any) -> Any:
# Note that, reading the variables and setting them in a context manager
# cannot not work since the option values would be set after the original
# defaults have been used by the theme.
if "QUARTO_FIG_WIDTH" in os.environ:

def _set_options_from_quarto():
"""
Set options from quarto
"""
global dpi, figure_size, figure_format

dpi = int(os.environ["QUARTO_FIG_DPI"])
figure_size = (
float(os.environ["QUARTO_FIG_WIDTH"]),
float(os.environ["QUARTO_FIG_HEIGHT"]),
)

# quarto verifies the format
# If is retina, it doubles the original dpi and changes the
# format to png
figure_format = os.environ["QUARTO_FIG_FORMAT"] # pyright: ignore

_set_options_from_quarto()
if quarto.is_quarto_environment():
quarto.set_options_from_quarto()
9 changes: 9 additions & 0 deletions plotnine/themes/theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,15 @@ def _smart_title_and_subtitle_ha(self):
if kwargs:
self += theme(**kwargs)

@property
def _figure_size_px(self) -> tuple[int, int]:
"""
Return the size of the output in pixels
"""
dpi = self.getp("dpi")
width, height = self.getp("figure_size")
return (int(width * dpi), int(height * dpi))


def theme_get() -> theme:
"""
Expand Down

0 comments on commit 1682dcb

Please sign in to comment.