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

Support saving to PDF with VlConvert 1.0 #3244

Merged
merged 8 commits into from Nov 3, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions altair/utils/_importers.py
Expand Up @@ -8,7 +8,7 @@ def import_vegafusion() -> ModuleType:
try:
version = importlib_version("vegafusion")
if Version(version) < Version(min_version):
raise ImportError(
raise RuntimeError(
f"The vegafusion package must be version {min_version} or greater. "
f"Found version {version}"
)
Expand All @@ -29,11 +29,11 @@ def import_vegafusion() -> ModuleType:


def import_vl_convert() -> ModuleType:
min_version = "0.14.0"
min_version = "1.0.0"
try:
version = importlib_version("vl-convert-python")
if Version(version) < Version(min_version):
raise ImportError(
raise RuntimeError(
f"The vl-convert-python package must be version {min_version} or greater. "
f"Found version {version}"
)
Expand All @@ -58,7 +58,7 @@ def import_pyarrow_interchange() -> ModuleType:
version = importlib_version("pyarrow")

if Version(version) < Version(min_version):
raise ImportError(
raise RuntimeError(
f"The pyarrow package must be version {min_version} or greater. "
f"Found version {version}"
)
Expand Down
2 changes: 1 addition & 1 deletion altair/utils/_vegafusion_data.py
Expand Up @@ -182,7 +182,7 @@ def compile_with_vegafusion(vegalite_spec: dict) -> dict:


def using_vegafusion() -> bool:
"""Check whether the vegafusion data transfomer is enabled"""
"""Check whether the vegafusion data transformer is enabled"""
# Local import to avoid circular ImportError
from altair import data_transformers

Expand Down
29 changes: 23 additions & 6 deletions altair/utils/mimebundle.py
@@ -1,6 +1,8 @@
from .deprecation import AltairDeprecationWarning
from .html import spec_to_html
from ._importers import import_vl_convert
import struct
import warnings


def spec_to_mimebundle(
Expand Down Expand Up @@ -145,11 +147,31 @@ def _spec_to_mimebundle_with_engine(spec, format, mode, **kwargs):
return {"image/png": png}, {
"image/png": {"width": w / factor, "height": h / factor}
}
elif format == "pdf":
scale = kwargs.get("scale_factor", 1)
if mode == "vega":
pdf = vlc.vega_to_pdf(
spec,
scale=scale,
)
else:
pdf = vlc.vegalite_to_pdf(
spec,
vl_version=vl_version,
scale=scale,
)
return {"application/pdf": pdf}
else:
# This should be validated above
# but raise exception for the sake of future development
raise ValueError("Unexpected format {fmt!r}".format(fmt=format))
elif normalized_engine == "altairsaver":
warnings.warn(
"The altair_saver export engine is deprecated and will be removed in a future version.\n"
"Please migrate to the vl-convert engine",
AltairDeprecationWarning,
stacklevel=1,
)
import altair_saver

return altair_saver.render(spec, format, mode=mode, **kwargs)
Expand Down Expand Up @@ -192,18 +214,13 @@ def _validate_normalize_engine(engine, format):
raise ValueError(
"The 'vl-convert' conversion engine requires the vl-convert-python package"
)
if format == "pdf":
raise ValueError(
"The 'vl-convert' conversion engine does not support the {fmt!r} format.\n"
"Use the 'altair_saver' engine instead".format(fmt=format)
)
elif normalized_engine == "altairsaver":
if altair_saver is None:
raise ValueError(
"The 'altair_saver' conversion engine requires the altair_saver package"
)
elif normalized_engine is None:
if vlc is not None and format != "pdf":
if vlc is not None:
normalized_engine = "vlconvert"
elif altair_saver is not None:
normalized_engine = "altairsaver"
Expand Down
5 changes: 2 additions & 3 deletions doc/user_guide/saving_charts.rst
Expand Up @@ -172,9 +172,8 @@ or::
pip install vl-convert-python

Unlike altair_saver_, vl-convert_ does not require any external dependencies.
However, it only supports saving charts to PNG and SVG formats. To save directly to
PDF, altair_saver_ is still required. See the vl-convert documentation for information
on other `limitations <https://github.com/vega/vl-convert#limitations>`_.
See the vl-convert documentation for information and for known
`limitations <https://github.com/vega/vl-convert#limitations>`_.

altair_saver
^^^^^^^^^^^^
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Expand Up @@ -64,7 +64,7 @@ dev = [
"pytest-cov",
"m2r",
"vega_datasets",
"vl-convert-python>=0.14.0",
"vl-convert-python>=1.0.0",
"mypy",
"pandas-stubs",
"types-jsonschema",
Expand Down
18 changes: 6 additions & 12 deletions tests/vegalite/v5/test_api.py
Expand Up @@ -322,23 +322,15 @@ def test_save(format, engine, basic_chart):
return

elif engine == "vl-convert":
if vlc is None and format != "bogus":
with pytest.raises(ValueError) as err:
basic_chart.save(out, format=format, engine=engine)
assert "vl-convert-python" in str(err.value)
return
elif format == "pdf":
if format == "bogus":
with pytest.raises(ValueError) as err:
basic_chart.save(out, format=format, engine=engine)
assert (
f"The 'vl-convert' conversion engine does not support the '{format}' format"
in str(err.value)
)
assert f"Unsupported format: '{format}'" in str(err.value)
return
elif format not in ("png", "svg"):
elif vlc is None:
with pytest.raises(ValueError) as err:
basic_chart.save(out, format=format, engine=engine)
assert f"Unsupported format: '{format}'" in str(err.value)
assert "vl-convert-python" in str(err.value)
return

basic_chart.save(out, format=format, engine=engine)
Expand All @@ -353,6 +345,8 @@ def test_save(format, engine, basic_chart):
assert content.startswith("<svg")
elif format == "png":
assert content.startswith(b"\x89PNG")
elif format == "pdf":
assert content.startswith(b"%PDF-")

fid, filename = tempfile.mkstemp(suffix="." + format)
os.close(fid)
Expand Down