This code borrows heavily from bokeh after finding out that they have implemented the function export_png
to, well, export png
files from their web-based plots. This has been requested many times of plotly
with no solution so I tried applying what bokeh
does... and it seems to work!
import io
import pandas as pd
import plotly
import plotly.graph_objs as go
import selenium.webdriver
import shutil
import time
from os.path import devnull
from PIL import Image
from subprocess import Popen, PIPE
### from bokeh/util, slightly modified to avoid using bokeh's settings.py
### - https://github.com/bokeh/bokeh/blob/master/bokeh/util/dependencies.py
def detect_phantomjs():
'''Detect if PhantomJS is avaiable in PATH.'''
try:
phantomjs_path = shutil.which('phantomjs')
# Python 2 relies on Environment variable in PATH - attempt to use as follows
except AttributeError:
phantomjs_path = "phantomjs"
try:
proc = Popen([phantomjs_path, "--version"], stdout=PIPE, stderr=PIPE)
proc.wait()
except OSError:
raise RuntimeError('PhantomJS is not present in PATH. Try "conda install phantomjs" or \
"npm install -g phantomjs-prebuilt"')
return phantomjs_path
### from bokeh/io, slightly modified to avoid their import_required util
### - https://github.com/bokeh/bokeh/blob/master/bokeh/io/export.py
def create_default_webdriver():
'''Return phantomjs enabled webdriver'''
phantomjs_path = detect_phantomjs()
return webdriver.PhantomJS(executable_path=phantomjs_path, service_log_path=devnull)
Here we create some data and save the plot as an html file for reading in
df = pd.DataFrame(
{'fruits': ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries'],
'counts': [5, 3, 4, 2, 4, 6] })
data = [go.Bar(x=df['fruits'],
y=df['counts'])]
html_file = 'plotly-fruit-plot.html'
plotly.offline.plot(data, filename=html_file)
This, again, is stolen entirely from how bokeh
is doing this. I really don't entirely understand it, for example, how it knows the perimeter of the plot as it's cropped so nicely with no modification from their call. I find that calling driver.maximize_window()
give a bigger output file, but I needed to wait just a hair or it would grab the screenshot before this too effect. I didn't see any difference between using or omitting the driver.execute_script()
call, so I left it commented.
### create webdrive, open file, maximize, and sleep
driver = create_default_webdriver()
driver.get(html_file)
driver.maximize_window()
#driver.execute_script("document.body.style.width = '100%';")
time.sleep(1)
fname = 'plotly-fruit-plot.png'
### original bokeh call gets byte data
### - http://selenium-python.readthedocs.io/api.html
# png = driver.get_screenshot_as_png()
# image = Image.open(io.BytesIO(png))
### I found this and seems simpler (could eliminate io import)
png = driver.get_screenshot_as_file(fname)
image = Image.open(fname)
driver.quit()
image