Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export to file-like object #938

Merged
merged 20 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Returns path string in functions
  • Loading branch information
RYangazov committed Dec 5, 2023
commit b9d2773e51d1e234b20b4d6c194efd15e118f0aa
492 changes: 227 additions & 265 deletions docs/f-23f/new_save_methods.ipynb

Large diffs are not rendered by default.

12 changes: 4 additions & 8 deletions python-package/lets_plot/export/ggsave_.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,22 +74,18 @@ def ggsave(plot: Union[PlotSpec, SupPlotsSpec, GGBunch], filename: str, *, path:
if not path:
path = join(os.getcwd(), _DEF_EXPORT_DIR)

if not os.path.exists(path):
os.makedirs(path)

pathname = join(path, filename)

ext = ext[1:].lower()
if ext == 'svg':
_to_svg(plot, pathname)
return _to_svg(plot, pathname)
elif ext in ['html', 'htm']:
_to_html(plot, pathname, iframe=iframe)
return _to_html(plot, pathname, iframe=iframe)
elif ext == 'png':
_to_png(plot, pathname, scale)
return _to_png(plot, pathname, scale)
elif ext == 'pdf':
_to_pdf(plot, pathname, scale)
return _to_pdf(plot, pathname, scale)
else:
raise ValueError(
"Unsupported file extension: '{}'\nPlease use one of: 'png', 'svg', 'pdf', 'html', 'htm'".format(ext)
)
return abspath(pathname)
67 changes: 55 additions & 12 deletions python-package/lets_plot/plot/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
# Copyright (c) 2019. JetBrains s.r.o.
# Use of this source code is governed by the MIT license that can be found in the LICENSE file.
#
from typing import Any

import io
import json
import os

__all__ = ['aes', 'layer']

Expand Down Expand Up @@ -474,7 +477,7 @@ def show(self):
from ..frontend_context._configuration import _display_plot
_display_plot(self)

def to_svg(self, path):
def to_svg(self, path) -> str:
"""
Write a plot to a file or to a file-like object in SVG format.

Expand All @@ -487,6 +490,11 @@ def to_svg(self, path):
If a string is provided, the result will be exported to the file at that path.
If a file-like object is provided, the result will be exported to that object.

Returns
-------
str
Absolute pathname of created file or None if file-like object is provided.

Examples
--------
.. jupyter-execute::
Expand All @@ -504,9 +512,9 @@ def to_svg(self, path):
p.to_svg(file_like)
display.SVG(file_like.getvalue())
"""
_to_svg(self, path)
return _to_svg(self, path)

def to_html(self, path, iframe: bool = None):
def to_html(self, path, iframe: bool = None) -> str:
"""
Write a plot to a file or to a file-like object in HTML format.

Expand All @@ -521,6 +529,11 @@ def to_html(self, path, iframe: bool = None):
iframe : bool, default=False
Whether to wrap HTML page into a iFrame.

Returns
-------
str
Absolute pathname of created file or None if file-like object is provided.

Examples
--------
.. jupyter-execute::
Expand All @@ -536,9 +549,9 @@ def to_html(self, path, iframe: bool = None):
file_like = io.BytesIO()
p.to_html(file_like)
"""
_to_html(self, path, iframe)
return _to_html(self, path, iframe)

def to_png(self, path, scale: float = None):
def to_png(self, path, scale: float = None) -> str:
"""
Write a plot to a file or to a file-like object in PNG format.

Expand All @@ -553,6 +566,11 @@ def to_png(self, path, scale: float = None):
scale : float
Scaling factor for raster output. Default value is 2.0.

Returns
-------
str
Absolute pathname of created file or None if file-like object is provided.

Notes
-----
Export to PNG file uses the CairoSVG library.
Expand All @@ -576,9 +594,9 @@ def to_png(self, path, scale: float = None):
p.to_png(file_like)
display.Image(file_like.getvalue())
"""
_to_png(self, path, scale)
return _to_png(self, path, scale)

def to_pdf(self, path, scale: float = None):
def to_pdf(self, path, scale: float = None) -> str:
"""
Write a plot to a file or to a file-like object in PDF format.

Expand All @@ -593,6 +611,11 @@ def to_pdf(self, path, scale: float = None):
scale : float
Scaling factor for raster output. Default value is 2.0.

Returns
-------
str
Absolute pathname of created file or None if file-like object is provided.

Notes
-----
Export to PDF file uses the CairoSVG library.
Expand All @@ -619,7 +642,7 @@ def to_pdf(self, path, scale: float = None):
file_like = io.BytesIO()
p.to_pdf(file_like)
"""
_to_pdf(self, path, scale)
return _to_pdf(self, path, scale)


class LayerSpec(FeatureSpec):
Expand Down Expand Up @@ -771,32 +794,36 @@ def _theme_dicts_merge(x, y):
return {**x, **y, **z}


def _to_svg(spec, path):
def _to_svg(spec, path) -> str:
from .. import _kbridge as kbr

svg = kbr._generate_svg(spec.as_dict())
if isinstance(path, str):
_makedirs(path)
with io.open(path, mode="w", encoding="utf-8") as f:
f.write(svg)
else:
path.write(svg.encode())
return _abspath(path)


def _to_html(spec, path, iframe: bool):
def _to_html(spec, path, iframe: bool) -> str:
if iframe is None:
iframe = False

from .. import _kbridge as kbr
html_page = kbr._generate_static_html_page(spec.as_dict(), iframe)

if isinstance(path, str):
_makedirs(path)
with io.open(path, mode="w", encoding="utf-8") as f:
f.write(html_page)
else:
path.write(html_page.encode())
return _abspath(path)


def _to_png(spec, path, scale: float):
def _to_png(spec, path, scale: float) -> str:
if scale is None:
scale = 2.0

Expand All @@ -813,10 +840,12 @@ def _to_png(spec, path, scale: float):
from .. import _kbridge
# Use SVG image-rendering style as Cairo doesn't support CSS image-rendering style,
svg = _kbridge._generate_svg(spec.as_dict(), use_css_pixelated_image_rendering=False)
_makedirs(path)
cairosvg.svg2png(bytestring=svg, write_to=path, scale=scale)
return _abspath(path)


def _to_pdf(spec, path, scale: float):
def _to_pdf(spec, path, scale: float) -> str:
if scale is None:
scale = 2.0

Expand All @@ -833,4 +862,18 @@ def _to_pdf(spec, path, scale: float):
from .. import _kbridge
# Use SVG image-rendering style as Cairo doesn't support CSS image-rendering style,
svg = _kbridge._generate_svg(spec.as_dict(), use_css_pixelated_image_rendering=False)
_makedirs(path)
cairosvg.svg2pdf(bytestring=svg, write_to=path, scale=scale)
return _abspath(path)


def _makedirs(path):
if isinstance(path, str):
dirname = os.path.dirname(path) or '.'
if not os.path.exists(dirname):
os.makedirs(dirname)


def _abspath(path) -> str | None:
if isinstance(path, str):
return os.path.abspath(path)
36 changes: 28 additions & 8 deletions python-package/lets_plot/plot/subplots.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def show(self):
from ..frontend_context._configuration import _display_plot
_display_plot(self)

def to_svg(self, path):
def to_svg(self, path) -> str:
"""
Write all plots currently in this 'bunch' to a file or file-like object in SVG format.

Expand All @@ -128,6 +128,11 @@ def to_svg(self, path):
If a string is provided, the result will be exported to the file at that path.
If a file-like object is provided, the result will be exported to that object.

Returns
-------
str
Absolute pathname of created file or None if file-like object is provided.

Examples
--------
.. jupyter-execute::
Expand All @@ -149,9 +154,9 @@ def to_svg(self, path):
p.to_svg(file_like)
display.SVG(file_like.getvalue())
"""
_to_svg(self, path)
return _to_svg(self, path)

def to_html(self, path, iframe: bool = None):
def to_html(self, path, iframe: bool = None) -> str:
"""
Write all plots currently in this 'bunch' to a file or file-like object in HTML format.

Expand All @@ -166,6 +171,11 @@ def to_html(self, path, iframe: bool = None):
iframe : bool, default=False
Whether to wrap HTML page into a iFrame.

Returns
-------
str
Absolute pathname of created file or None if file-like object is provided.

Examples
--------
.. jupyter-execute::
Expand All @@ -185,9 +195,9 @@ def to_html(self, path, iframe: bool = None):
file_like = io.BytesIO()
p.to_html(file_like)
"""
_to_html(self, path, iframe)
return _to_html(self, path, iframe)

def to_png(self, path, scale=None):
def to_png(self, path, scale=None) -> str:
"""
Write all plots currently in this 'bunch' to a file or file-like object in PNG format.

Expand All @@ -202,6 +212,11 @@ def to_png(self, path, scale=None):
scale : float
Scaling factor for raster output. Default value is 2.0.

Returns
-------
str
Absolute pathname of created file or None if file-like object is provided.

Notes
-----
Export to PNG file uses the CairoSVG library.
Expand Down Expand Up @@ -229,9 +244,9 @@ def to_png(self, path, scale=None):
p.to_png(file_like)
display.Image(file_like.getvalue())
"""
_to_png(self, path, scale)
return _to_png(self, path, scale)

def to_pdf(self, path, scale=None):
def to_pdf(self, path, scale=None) -> str:
"""
Write all plots currently in this 'bunch' to a file or file-like object in PDF format.

Expand All @@ -246,6 +261,11 @@ def to_pdf(self, path, scale=None):
scale : float
Scaling factor for raster output. Default value is 2.0.

Returns
-------
str
Absolute pathname of created file or None if file-like object is provided.

Notes
-----
Export to PDF file uses the CairoSVG library.
Expand All @@ -271,4 +291,4 @@ def to_pdf(self, path, scale=None):
file_like = io.BytesIO()
p.to_pdf(file_like)
"""
_to_pdf(self, path, scale)
return _to_pdf(self, path, scale)