Skip to content

Commit

Permalink
Merge branch 'master' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
cphalpert authored Nov 20, 2018
2 parents 4d42adb + 768bdc0 commit 6f1fc7b
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,8 @@ ENV/
.ropeproject

.DS_Store

# Test outputs
tests/outputs/**/*.png
tests/outputs/**/*.html
tests/outputs/**/*.svg
70 changes: 62 additions & 8 deletions chartify/_core/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
"""
from collections import OrderedDict
from functools import wraps
import io
from io import BytesIO
import tempfile
import warnings

import bokeh
from bokeh.io.export import _SVG_SCRIPT
import bokeh.plotting
from bokeh.embed import file_html

Expand Down Expand Up @@ -168,7 +171,8 @@ def _add_subtitle_to_figure(self, subtitle_text=None):
align=subtitle_settings['subtitle_align'],
text_color=subtitle_settings['subtitle_text_color'],
text_font_size=subtitle_settings['subtitle_text_size'],
text_font=subtitle_settings['subtitle_text_font'])
text_font=subtitle_settings['subtitle_text_font'],
)
self.figure.add_layout(_subtitle_glyph,
subtitle_settings['subtitle_location'])
return _subtitle_glyph
Expand Down Expand Up @@ -351,6 +355,8 @@ def show(self, format='html'):
# Need to re-enable this when logos are added back.
# image = self.logo._add_logo_to_image(image)
return display(image)
elif format == 'svg':
return self._show_svg()

def save(self, filename, format='html'):
"""Save the chart.
Expand Down Expand Up @@ -383,6 +389,9 @@ def save(self, filename, format='html'):
# Need to re-enable this when logos are added back.
# image = self.logo._add_logo_to_image(image)
image.save(filename)
elif format == 'svg':
image = self._figure_to_svg()
self._save_svg(image, filename)

print('Saved to {filename}'.format(filename=filename))

Expand All @@ -391,20 +400,16 @@ def save(self, filename, format='html'):
def _set_toolbar_for_format(self, format):
if format == 'html':
self.figure.toolbar_location = 'right'
elif format == 'png':
elif format in ('png', 'svg'):
self.figure.toolbar_location = None
elif format is None: # If format is None the chart won't be shown.
pass
else:
raise ValueError(
"""Invalid format. Valid options are 'html' or 'png'.""")

def _figure_to_png(self):
"""Convert figure object to PNG
Bokeh can only save figure objects as html.
To convert to PNG the HTML file is opened in a headless browser.
"""
# Initialize headless browser options
def _initialize_webdriver(self):
"""Initialize headless chrome browser"""
options = Options()
options.add_argument("window-size={width},{height}".format(
width=self.style.plot_width, height=self.style.plot_height))
Expand All @@ -416,6 +421,14 @@ def _figure_to_png(self):
options.add_argument('--headless')
options.add_argument('--hide-scrollbars')
driver = webdriver.Chrome(options=options)
return driver

def _figure_to_png(self):
"""Convert figure object to PNG
Bokeh can only save figure objects as html.
To convert to PNG the HTML file is opened in a headless browser.
"""
driver = self._initialize_webdriver()
# Save figure as HTML
html = file_html(self.figure, resources=INLINE, title="")
fp = tempfile.NamedTemporaryFile(
Expand All @@ -435,6 +448,47 @@ def _figure_to_png(self):
image = image.resize(target_dimensions, resample=Image.LANCZOS)
return image

def _set_svg_backend_decorator(f):
"""Sets the chart backend to svg and resets
after the function has run."""
@wraps(f)
def wrapper(self, *args, **kwargs):
old_backend = self.figure.output_backend
self.figure.output_backend = 'svg'
return f(self, *args, **kwargs)
self.figure.output_backend = old_backend
return wrapper

@_set_svg_backend_decorator
def _show_svg(self):
"""Show the chart figure with an svg output backend."""
return bokeh.io.show(self.figure)

@_set_svg_backend_decorator
def _figure_to_svg(self):
"""
Convert the figure to an svg so that it can be saved to a file.
https://github.com/bokeh/bokeh/blob/master/bokeh/io/export.py
"""
driver = self._initialize_webdriver()
html = file_html(self.figure, resources=INLINE, title="")

fp = tempfile.NamedTemporaryFile(
'w', prefix='chartify', suffix='.html', encoding='utf-8')
fp.write(html)
fp.flush()
driver.get("file:///" + fp.name)
svgs = driver.execute_script(_SVG_SCRIPT)
fp.close()

driver.quit()
return svgs[0]

def _save_svg(self, svg, filename):
"""Write the svg to a file"""
with io.open(filename, mode="w", encoding="utf-8") as f:
f.write(svg)


class Logo:

Expand Down
1 change: 0 additions & 1 deletion chartify/_core/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,6 @@ def __init__(self, chart, layout):
'subtitle_text_color': '#666666',
'subtitle_location': 'above',
'subtitle_text_size': '12pt',
'subtitle_text_size': '100%',
'subtitle_text_font': 'helvetica'
},
'text_callout_and_plot': {
Expand Down
4 changes: 2 additions & 2 deletions docs/_static/coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions tests/outputs/chart/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
26 changes: 24 additions & 2 deletions tests/test_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import chartify
import numpy as np
import pandas as pd
import pytest
from selenium.common.exceptions import WebDriverException


class TestLegend:
Expand Down Expand Up @@ -93,7 +95,8 @@ def test_reverse_vertical_legend_bar(self):


class TestChart:
def test_data(self):

def setup(self):
data = chartify.examples.example_data()
data = data.sort_values(['date', 'fruit'])
color_order = data['fruit'].unique()
Expand All @@ -105,7 +108,26 @@ def test_data(self):
size_column='quantity',
color_column='fruit',
color_order=color_order)
self.chart = ch
self.data = data
self.color_order = color_order

def test_data(self):
data = self.data
color_order = self.color_order
assert (np.array_equal(
list(filter(lambda x: x['fruit'][0] == color_order[0],
ch.data))[0]['unit_price'],
self.chart.data))[0]['unit_price'],
data[data['fruit'] == color_order[0]]['unit_price'].values))

def test_save(self):
ch = self.chart
output_path = './tests/outputs/chart/'
base_filename = 'test_save'
for filetype in ('png', 'svg', 'html'):
filename = output_path + base_filename + '.' + filetype
try:
ch.save(filename=filename, format=filetype)
# Occurs if chromedriver is not found
except WebDriverException:
pytest.skip("Skipping save tests")

0 comments on commit 6f1fc7b

Please sign in to comment.