Skip to content

Commit a2b4f3c

Browse files
committed
add write_images and to_images functions (not yet using shared Kaleido so they are not faster
1 parent e75a5df commit a2b4f3c

File tree

3 files changed

+156
-36
lines changed

3 files changed

+156
-36
lines changed

plotly/io/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import TYPE_CHECKING
44

55
if sys.version_info < (3, 7) or TYPE_CHECKING:
6-
from ._kaleido import to_image, write_image, full_figure_for_development
6+
from ._kaleido import to_image, write_image, to_images, write_images, full_figure_for_development
77
from . import orca, kaleido
88
from . import json
99
from ._json import to_json, from_json, read_json, write_json
@@ -15,6 +15,8 @@
1515
__all__ = [
1616
"to_image",
1717
"write_image",
18+
"to_images",
19+
"write_images",
1820
"orca",
1921
"json",
2022
"to_json",
@@ -37,6 +39,8 @@
3739
[
3840
"._kaleido.to_image",
3941
"._kaleido.write_image",
42+
"._kaleido.to_images",
43+
"._kaleido.write_images",
4044
"._kaleido.full_figure_for_development",
4145
"._json.to_json",
4246
"._json.from_json",

plotly/io/_kaleido.py

Lines changed: 115 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import warnings
77

88
import plotly
9-
from plotly.io._utils import validate_coerce_fig_to_dict
9+
from plotly.io._utils import validate_coerce_fig_to_dict, as_individual_kwargs
1010

1111
ENGINE_SUPPORT_TIMELINE = "September 2025"
1212

@@ -29,6 +29,7 @@
2929
scope.mathjax = (
3030
"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js"
3131
)
32+
3233
except ImportError as e:
3334
kaleido_available = False
3435
kaleido_major = -1
@@ -37,7 +38,14 @@
3738

3839

3940
def to_image(
40-
fig, format=None, width=None, height=None, scale=None, validate=True, engine=None
41+
fig,
42+
format=None,
43+
width=None,
44+
height=None,
45+
scale=None,
46+
validate=True,
47+
engine=None,
48+
kaleido_instance=None,
4149
):
4250
"""
4351
Convert a figure to a static image bytes string
@@ -87,6 +95,9 @@ def to_image(
8795
engine (deprecated): str
8896
No longer used. Kaleido is the only supported engine.
8997
98+
kaleido_instance: kaleido.Kaleido or None
99+
An instance of the Kaleido class. If None, a new instance will be created.
100+
90101
Returns
91102
-------
92103
bytes
@@ -162,15 +173,17 @@ def to_image(
162173
# Check if trying to export to EPS format, which is not supported in Kaleido v1
163174
if format == "eps":
164175
raise ValueError(
165-
"""
166-
EPS export is not supported with Kaleido v1.
167-
Please downgrade to Kaleido v0 to use EPS export:
168-
$ pip install kaleido==0.2.1
176+
f"""
177+
EPS export is not supported with Kaleido v1. Please use SVG or PDF instead.
178+
You can also downgrade to Kaleido v0, but support for v0 will be removed after {ENGINE_SUPPORT_TIMELINE}.
179+
To downgrade to Kaleido v0, run:
180+
$ pip install kaleido<1.0.0
169181
"""
170182
)
171183
import choreographer
172184

173185
try:
186+
# TODO: Actually use provided kaleido_instance here
174187
img_bytes = kaleido.calc_fig_sync(
175188
fig_dict,
176189
path=None,
@@ -204,35 +217,6 @@ def to_image(
204217
return img_bytes
205218

206219

207-
def install_chrome():
208-
"""
209-
Install Google Chrome for Kaleido
210-
This function can be run from the command line using the command plotly_install_chrome
211-
defined in pyproject.toml
212-
"""
213-
if not kaleido_available or kaleido_major < 1:
214-
raise ValueError(
215-
"This command requires Kaleido v1.0.0 or greater. Install it using `pip install kaleido`."
216-
)
217-
import choreographer
218-
import sys
219-
220-
cli_yes = len(sys.argv) > 1 and sys.argv[1] == "-y"
221-
if not cli_yes:
222-
print(
223-
"\nPlotly will install a copy of Google Chrome to be used for generating static images of plots.\n"
224-
)
225-
# TODO: Print path where Chrome will be installed
226-
# print(f"Chrome will be installed at {chrome_download_path}\n")
227-
response = input("Do you want to proceed? [y/n] ")
228-
if not response or response[0].lower() != "y":
229-
print("Cancelled")
230-
return
231-
print("Installing Chrome for Plotly...")
232-
kaleido.get_chrome_sync()
233-
print("Chrome installed successfully.")
234-
235-
236220
def write_image(
237221
fig,
238222
file,
@@ -242,6 +226,7 @@ def write_image(
242226
height=None,
243227
validate=True,
244228
engine="auto",
229+
kaleido_instance=None,
245230
):
246231
"""
247232
Convert a figure to a static image and write it to a file or writeable
@@ -300,6 +285,9 @@ def write_image(
300285
engine (deprecated): str
301286
No longer used. Kaleido is the only supported engine.
302287
288+
kaleido_instance: kaleido.Kaleido or None
289+
An instance of the Kaleido class. If None, a new instance will be created.
290+
303291
Returns
304292
-------
305293
None
@@ -348,6 +336,7 @@ def write_image(
348336
height=height,
349337
validate=validate,
350338
engine=engine,
339+
kaleido_instance=kaleido_instance,
351340
)
352341

353342
# Open file
@@ -373,6 +362,69 @@ def write_image(
373362
path.write_bytes(img_data)
374363

375364

365+
def to_images(**kwargs):
366+
"""
367+
Convert multiple figures to static images and return a list of image bytes
368+
369+
Parameters
370+
----------
371+
Accepts the same parameters as pio.to_image(), but any parameter may be either
372+
a single value or a list of values. If more than one parameter is a list,
373+
all must be the same length.
374+
375+
Returns
376+
-------
377+
list of bytes
378+
The image data
379+
"""
380+
individual_kwargs = as_individual_kwargs(**kwargs)
381+
382+
if kaleido_available and kaleido_major > 0:
383+
# Kaleido v1
384+
# TODO: Use a single shared kaleido instance for all images
385+
return [to_image(**kw) for kw in individual_kwargs]
386+
else:
387+
# Kaleido v0, or orca
388+
return [to_image(**kw) for kw in individual_kwargs]
389+
390+
391+
def write_images(**kwargs):
392+
"""
393+
Write multiple images to files or writeable objects. This is much faster than
394+
calling write_image() multiple times.
395+
396+
Parameters
397+
----------
398+
Accepts the same parameters as pio.write_image(), but any parameter may be either
399+
a single value or a list of values. If more than one parameter is a list,
400+
all must be the same length.
401+
402+
Returns
403+
-------
404+
None
405+
"""
406+
407+
if "file" not in kwargs:
408+
raise ValueError("'file' argument is required")
409+
410+
# Get individual arguments, and separate out the 'file' argument
411+
individual_kwargs = as_individual_kwargs(**kwargs)
412+
files = [kw["file"] for kw in individual_kwargs]
413+
individual_kwargs = [
414+
{k: v for k, v in kw.items() if k != "file"} for kw in individual_kwargs
415+
]
416+
417+
if kaleido_available and kaleido_major > 0:
418+
# Kaleido v1
419+
# TODO: Use a single shared kaleido instance for all images
420+
for f, kw in zip(files, individual_kwargs):
421+
write_image(file=f, **kw)
422+
else:
423+
# Kaleido v0, or orca
424+
for f, kw in zip(files, individual_kwargs):
425+
write_image(file=f, **kw)
426+
427+
376428
def full_figure_for_development(fig, warn=True, as_dict=False):
377429
"""
378430
Compute default values for all attributes not specified in the input figure and
@@ -442,4 +494,32 @@ def full_figure_for_development(fig, warn=True, as_dict=False):
442494
return go.Figure(fig, skip_invalid=True)
443495

444496

497+
def install_chrome():
498+
"""
499+
Install Google Chrome for Kaleido
500+
This function can be run from the command line using the command `plotly_install_chrome`
501+
defined in pyproject.toml
502+
"""
503+
if not kaleido_available or kaleido_major < 1:
504+
raise ValueError(
505+
"This command requires Kaleido v1.0.0 or greater. Install it using `pip install kaleido`."
506+
)
507+
import sys
508+
509+
cli_yes = len(sys.argv) > 1 and sys.argv[1] == "-y"
510+
if not cli_yes:
511+
print(
512+
"\nPlotly will install a copy of Google Chrome to be used for generating static images of plots.\n"
513+
)
514+
# TODO: Print path where Chrome will be installed
515+
# print(f"Chrome will be installed at {chrome_download_path}\n")
516+
response = input("Do you want to proceed? [y/n] ")
517+
if not response or response[0].lower() != "y":
518+
print("Cancelled")
519+
return
520+
print("Installing Chrome for Plotly...")
521+
kaleido.get_chrome_sync()
522+
print("Chrome installed successfully.")
523+
524+
445525
__all__ = ["to_image", "write_image", "scope", "full_figure_for_development"]

plotly/io/_utils.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,42 @@ def validate_coerce_output_type(output_type):
4343
return cls
4444

4545

46+
def as_individual_kwargs(**kwargs):
47+
"""
48+
Given one or more keyword arguments which may be either a single value or a list of values,
49+
return a list of dictionaries where each dictionary has only single values for each keyword
50+
by expanding the single values into lists.
51+
If more than one keyword is a list, all lists must be the same length.
52+
53+
Parameters
54+
----------
55+
kwargs: dict
56+
The keyword arguments
57+
58+
Returns
59+
-------
60+
list of dicts
61+
A list of dictionaries
62+
"""
63+
# Check that all list arguments have the same length,
64+
# and find out what that length is
65+
# If there are no list arguments, length is 1
66+
list_lengths = [len(v) for v in kwargs.values() if isinstance(v, list)]
67+
if list_lengths and len(set(list_lengths)) > 1:
68+
raise ValueError("All list arguments must have the same length.")
69+
list_length = list_lengths[0] if list_lengths else 1
70+
71+
# Expand all arguments to lists of the same length
72+
expanded_kwargs = {
73+
k: [v] * list_length if not isinstance(v, list) else v
74+
for k, v in kwargs.items()
75+
}
76+
77+
# Reshape into a list of dictionaries
78+
# Each dictionary represents the arguments for a single call to to_image
79+
return [{k: v[i] for k, v in expanded_kwargs.items()} for i in range(list_length)]
80+
81+
4682
def plotly_cdn_url(cdn_ver=get_plotlyjs_version()):
4783
"""Return a valid plotly CDN url."""
4884
return "https://cdn.plot.ly/plotly-{cdn_ver}.min.js".format(

0 commit comments

Comments
 (0)