[Python-modules-commits] [python-mplexporter] 71/135: Added docstrings to plotly_renderer and plotly_utils modules.
Wolfgang Borgert
debacle at moszumanska.debian.org
Tue Sep 23 21:19:05 UTC 2014
This is an automated email from the git hooks/post-receive script.
debacle pushed a commit to branch master
in repository python-mplexporter.
commit 5adc25f8dc4c6d852f87d8462584af628d34cadf
Author: theengineear <andseier at gmail.com>
Date: Sun Mar 2 16:39:59 2014 -0800
Added docstrings to plotly_renderer and plotly_utils modules.
---
mplexporter/renderers/plotly/plotly_renderer.py | 235 ++++++++++++++++++------
mplexporter/renderers/plotly/plotly_utils.py | 144 ++++++++++++---
2 files changed, 300 insertions(+), 79 deletions(-)
diff --git a/mplexporter/renderers/plotly/plotly_renderer.py b/mplexporter/renderers/plotly/plotly_renderer.py
index c82d33a..c637b0e 100644
--- a/mplexporter/renderers/plotly/plotly_renderer.py
+++ b/mplexporter/renderers/plotly/plotly_renderer.py
@@ -1,19 +1,58 @@
"""
-Plotly Renderer
-================
-This is a renderer class to be used with an exporter for rendering plots in Plotly!
+Plotly Renderer.
+
+A renderer class to be used with an exporter for rendering matplotlib plots
+in Plotly.
+
+Attributes:
+ PlotlyRenderer -- a renderer class to be used with an Exporter obj
+ fig_to_plotly -- a function to send an mpl figure to Plotly
+
"""
-import plotly
-import matplotlib
from . import plotly_utils
from .. base import Renderer
+from ... import utils
from ... exporter import Exporter
class PlotlyRenderer(Renderer):
+ """A renderer class inheriting from base for rendering mpl plots in plotly.
+
+ Attributes:
+ username -- plotly username, required for fig_to_plotly (default None)
+ api_key -- api key for given plotly username (defualt None)
+ data -- a list of data dictionaries to be passed to plotly
+ layout -- a layout dictionary to be passed to plotly
+ axis_ct -- a reference to the number of axes rendered from mpl fig
+
+ Inherited Methods (see renderers.base):
+ ax_zoomable(ax) -- static method
+ ax_has_xgrid(ax) -- static method
+ ax_has_ygrid(ax) -- static method
+ current_ax_zoomable(self) -- property
+ current_ax_has_xgrid(self) -- property
+ current_ax_has_ygrid(self) -- property
+ draw_figure(self, fig, props) -- context manager
+ draw_axes(self, ax, props) -- context manager
+
+ Reimplemented Methods (see renderers.base):
+ open_figure(self, fig, props)
+ close_figure(self, fig)
+ open_axes(self, ax, props)
+ close_axes(self, ax)
+ draw_line(self, **props)
+ draw_markers(self, **props)
+ draw_text(self, **props)
+
+ """
def __init__(self, username=None, api_key=None):
- self.output = ""
+ """Initialize PlotlyRenderer obj.
+
+ PlotlyRenderer obj is called on by an Exporter object to draw
+ matplotlib objects like figures, axes, text, etc.
+
+ """
self.username = username
self.api_key = api_key
self.data = []
@@ -21,12 +60,23 @@ class PlotlyRenderer(Renderer):
self.axis_ct = 0
def open_figure(self, fig, props):
- self.output += "opening figure\n"
+ """Creates a new figure by beginning to fill out layout dict."""
self.layout['width'] = int(props['figwidth']*props['dpi'])
self.layout['height'] = int(props['figheight']*props['dpi'])
def close_figure(self, fig):
- self.output += "closing figure\n"
+ """Closes figure by cleaning up data and layout dictionaries.
+
+ The PlotlyRenderer's job is to create an appropriate set of data and
+ layout dictionaries. When the figure is closed, some cleanup and
+ repair is necessary. This method removes inappropriate dictionary
+ entries, freeing up Plotly to use defaults and best judgements to
+ complete the entries. This method is called by an Exporter object.
+
+ Positional arguments:
+ fig -- an mpl figure object.
+
+ """
plotly_utils.repair_data(self.data)
plotly_utils.repair_layout(self.layout)
for data_dict in self.data:
@@ -40,10 +90,23 @@ class PlotlyRenderer(Renderer):
self.layout['showlegend'] = False
def open_axes(self, ax, props):
+ """Setup a new axes object (subplot in plotly).
+
+ Plotly stores information about subplots in different 'xaxis' and
+ 'yaxis' objects which are numbered. These are just dictionaries
+ included in the layout dictionary. This function takes information
+ from the Exporter, fills in appropriate dictionary entries,
+ and updates the layout dictionary. PlotlyRenderer keeps track of the
+ number of plots by incrementing the axis_ct attribute.
+
+ Positional arguments:
+ ax -- an mpl axes object. This will become a subplot in plotly.
+ props -- selected axes properties from the exporter (a dict).
+
+ """
self.axis_ct += 1
- self.output += " opening axis {}\n".format(self.axis_ct)
layout = {
- 'title': props['title'],
+ 'title': props['title'], # this will currently get overwritten!
'xaxis{}'.format(self.axis_ct): {
'range': props['xlim'],
'title': props['xlabel'],
@@ -59,75 +122,99 @@ class PlotlyRenderer(Renderer):
'anchor': 'x{}'.format(self.axis_ct)
}
}
- if layout['xaxis{}'.format(self.axis_ct)]['title'] not in [None, 'None', 'none', '']:
- children = ax.xaxis.get_children()
- for child in children:
- if isinstance(child, matplotlib.text.Text):
- if child.get_text() == layout['xaxis{}'.format(self.axis_ct)]['title']:
- titlefont = {
- 'size': child.get_size()
- }
- layout['xaxis{}'.format(self.axis_ct)]['titlefont'] = titlefont
- if layout['yaxis{}'.format(self.axis_ct)]['title'] not in [None, 'None', 'none', '']:
- children = ax.yaxis.get_children()
- for child in children:
- if isinstance(child, matplotlib.text.Text):
- if child.get_text() == layout['yaxis{}'.format(self.axis_ct)]['title']:
- titlefont = {
- 'size': child.get_size()
- }
- layout['yaxis{}'.format(self.axis_ct)]['titlefont'] = titlefont
+ if props['xlabel'] not in [None, 'None', 'none', '']:
+ style = utils.get_style(ax.xaxis.get_label())
+ titlefont = {'size': style['fontsize'], 'color': style['color']}
+ layout['xaxis{}'.format(self.axis_ct)]['titlefont'] = titlefont
+ if props['ylabel'] not in [None, 'None', 'none', '']:
+ style = utils.get_style(ax.yaxis.get_label())
+ titlefont = {'size': style['fontsize'], 'color': style['color']}
+ layout['yaxis{}'.format(self.axis_ct)]['titlefont'] = titlefont
for key, value in layout.items():
self.layout[key] = value
def close_axes(self, ax):
- self.output += " closing axis {}\n".format(self.axis_ct)
+ """Close the axes object and clean up."""
+ pass
+
+ def draw_line(self, **props):
+ """Create a data dict for a line obj.
+
+ Props dict (key: value):
+ 'coordinates': 'data', 'axes', 'figure', or 'display'.
+ 'data': a list of xy pairs.
+ 'style': style dict from utils.get_line_style
+ 'mplobj': an mpl object, in this case the line object.
- def draw_line(self, data, coordinates, style, mplobj=None):
- if coordinates == 'data':
- self.output += " draw line with {0} points\n".format(data.shape[0])
+ """
+ if props['coordinates'] == 'data':
trace = {
'mode': 'lines',
- 'x': [xy_pair[0] for xy_pair in data],
- 'y': [xy_pair[1] for xy_pair in data],
+ 'x': [xy_pair[0] for xy_pair in props['data']],
+ 'y': [xy_pair[1] for xy_pair in props['data']],
'xaxis': 'x{}'.format(self.axis_ct),
'yaxis': 'y{}'.format(self.axis_ct),
'line': {
- 'opacity': style['alpha'],
- 'color': style['color'],
- 'width': style['linewidth'],
- 'dash': plotly_utils.convert_dash(style['dasharray'])
+ 'opacity': props['style']['alpha'],
+ 'color': props['style']['color'],
+ 'width': props['style']['linewidth'],
+ 'dash': plotly_utils.convert_dash(props['style'][
+ 'dasharray'])
}
}
self.data += trace,
else:
- self.output += " received {}-point line with 'figure' coordinates, skipping!".format(data.shape[0])
+ pass
+
+ def draw_markers(self, **props):
+ """Create a data dict for a line obj using markers.
+
+ Props dict (key: value):
+ 'coordinates': 'data', 'axes', 'figure', or 'display'.
+ 'data': a list of xy pairs.
+ 'style': style dict from utils.get_marker_style
+ 'mplobj': an mpl object, in this case the line object.
- def draw_markers(self, data, coordinates, style, mplobj=None):
- if coordinates == 'data':
- self.output += " draw {0} markers\n".format(data.shape[0])
+ """
+ if props['coordinates'] == 'data':
trace = {
'mode': 'markers',
- 'x': [xy_pair[0] for xy_pair in data],
- 'y': [xy_pair[1] for xy_pair in data],
+ 'x': [xy_pair[0] for xy_pair in props['data']],
+ 'y': [xy_pair[1] for xy_pair in props['data']],
'xaxis': 'x{}'.format(self.axis_ct),
'yaxis': 'y{}'.format(self.axis_ct),
'marker': {
- 'opacity': style['alpha'],
- 'color': style['facecolor'],
- 'symbol': plotly_utils.convert_symbol(style['marker']),
+ 'opacity': props['style']['alpha'],
+ 'color': props['style']['facecolor'],
+ 'symbol': plotly_utils.convert_symbol(props['style'][
+ 'marker']),
'line': {
- 'color': style['edgecolor'],
- 'width': style['edgewidth']
+ 'color': props['style']['edgecolor'],
+ 'width': props['style']['edgewidth']
}
}
}
# not sure whether we need to incorporate style['markerpath']
self.data += trace,
else:
- self.output += " received {} markers with 'figure' coordinates, skipping!".format(data.shape[0])
+ pass
def draw_text(self, **props):
+ """Create an annotation dict for a text obj.
+
+ Currently, plotly uses either 'page' or 'data' to reference
+ annotation locations. These refer to 'display' and 'data',
+ respectively for the 'coordinates' key used in the Exporter.
+ Appropriate measures are taken to transform text locations to
+ reference one of these two options.
+
+ Props dict (key: value):
+ 'coordinates': reference for position, 'data', 'axes', 'figure', etc.
+ 'position': x,y location of text in the given coordinates.
+ 'style': style dict from utils.get_text_style.
+ 'text': actual string content.
+
+ """
if 'annotations' not in self.layout:
self.layout['annotations'] = []
annotation = {
@@ -146,13 +233,57 @@ class PlotlyRenderer(Renderer):
data_pos = self._current_ax.transFigure.inverted().transform(props['position'])
annotation['x'], annotation['y'] = data_pos[0], data_pos[1]
self.layout['annotations'] += annotation,
- # position=position, coordinates=coordinates, style=style, mplobj=text)
def fig_to_plotly(fig, username=None, api_key=None, notebook=False):
- """Convert a matplotlib figure to plotly dictionary
+ """Convert a matplotlib figure to plotly dictionary and send.
+
+ All available information about matplotlib visualizations are stored
+ within a matplotlib.figure.Figure object. You can create a plot in python
+ using matplotlib, store the figure object, and then pass this object to
+ the fig_to_plotly function. In the background, mplexporter is used to
+ crawl through the mpl figure object for appropriate information. This
+ information is then systematically sent to the PlotlyRenderer which
+ creates the JSON structure used to make plotly visualizations. Finally,
+ these dictionaries are sent to plotly and your browser should open up a
+ new tab for viewing! Optionally, if you're working in IPython, you can
+ set notebook=True and the PlotlyRenderer will call plotly.iplot instead
+ of plotly.plot to have the graph appear directly in the IPython notebook.
+
+ Note, this function gives the user access to a simple, one-line way to
+ render an mpl figure in plotly. If you need to trouble shoot, you can do
+ this step manually by NOT running this fuction and entereing the following:
+
+ ============================================================================
+ from mplexporter import Exporter
+ from mplexporter.renderers import PlotlyRenderer
+
+ # create an mpl figure and store it under a varialble 'fig'
+
+ renderer = PlotlyRenderer()
+ exporter = Exporter(renderer)
+ exporter.run(fig)
+ ============================================================================
+
+ You can then inspect the JSON structures by accessing these:
+
+ renderer.layout -- a plotly layout dictionary
+ renderer.data -- a list of plotly data dictionaries
+
+ Positional arguments:
+ fig -- a matplotlib figure object
+ username -- a valid plotly username **
+ api_key -- a valid api_key for the above username **
+ notebook -- an option for use with an IPython notebook
+
+ ** Don't have a username/api_key? Try looking here:
+ https://plot.ly/plot
+
+ ** Forgot your api_key? Try signing in and looking here:
+ https://plot.ly/api/python/getting-started
"""
+ import plotly
renderer = PlotlyRenderer(username=username, api_key=api_key)
Exporter(renderer).run(fig)
py = plotly.plotly(renderer.username, renderer.api_key)
diff --git a/mplexporter/renderers/plotly/plotly_utils.py b/mplexporter/renderers/plotly/plotly_utils.py
index 9c0a06c..6a34b37 100644
--- a/mplexporter/renderers/plotly/plotly_utils.py
+++ b/mplexporter/renderers/plotly/plotly_utils.py
@@ -1,26 +1,50 @@
def convert_symbol(mpl_symbol):
- if mpl_symbol in symbol_map:
- return symbol_map[mpl_symbol]
+ """Convert mpl marker symbol to plotly symbol and return symbol."""
+ if mpl_symbol in SYMBOL_MAP:
+ return SYMBOL_MAP[mpl_symbol]
else:
return 'dot' # default
def convert_dash(mpl_dash):
- if mpl_dash in dash_map:
- return dash_map[mpl_dash]
+ """Convert mpl line symbol to plotly line symbol and return symbol."""
+ if mpl_dash in DASH_MAP:
+ return DASH_MAP[mpl_dash]
else:
return 'solid' # default
def get_x_domain(bounds):
+ """Convert matplotlib (x0,width) to (x0,x1) and return."""
return [bounds[0], bounds[0] + bounds[2]]
def get_y_domain(bounds):
+ """Convert matplotlib (y0,height) to (y0,y1) and return."""
return [bounds[1], bounds[1] + bounds[3]]
def clean_dict(node, parent=None, node_key=None):
+ """Remove None, 'none', 'None', and {} from a dictionary obj.
+
+ When Plotly JSON dictionary entries are populated, Plotly will
+ automatically fill in necessary items with defaults. However, if a
+ nonsense entry is sent to plotly, it won't know to deal with it. This
+ function removes some common 'nonsense' entries like empty dicts, None,
+ 'None', or 'none'.
+
+ The clean_dict function will typically be called with a dictionary
+ argument only, allowing parent and node_key to remain defaults. The
+ choice of node reflects that this function works recursively.
+
+ Positional arguments:
+ node -- a dictionary that needs to be cleaned
+
+ Keyword arguments:
+ parent -- the dictionary that contains node (default None)
+ node_key -- parent[node_key] == node (default None)
+
+ """
del_keys = []
for key, item in node.items():
if isinstance(item, dict):
@@ -36,6 +60,29 @@ def clean_dict(node, parent=None, node_key=None):
def repair_key(d, key_path_tup, fix):
+ """Repairs inappropriate keys caused by referencing self.ax_ct.
+
+ This function allows inappropriate keys to be used in the
+ PlotlyRenderer.layout and PlotlyRenderer.data dictionaries. This is done
+ for the following reasons:
+
+ - Code is made simpler by treating 1st axes instance the same as
+ subsequent axes instances.
+ - If future releases of Plotly accept keys such as 'xaxis1' or 'yaxis1',
+ plotly_renderer.py and plolty_utils.py can be updated simply.
+
+ Dictionaries need not be continuous for use with a key_path_tup. For
+ example, layout['annotations'] is actually a list of annotation
+ dictionaries. When repair_key() runs into such a list, it assumes that
+ the list is populated by dictionaries and continues down the rest of the
+ key_path_tup for each dictionary in the list.
+
+ Positional arguments:
+ d -- a plotly layout or data dictionary
+ key_path_tup -- a tuple of dictionary keys that leads to the conflict
+ fix -- the appropriate dictionary key for the key_path_tup
+
+ """
try:
for num, key in enumerate(key_path_tup[:-1]):
d = d[key]
@@ -50,37 +97,80 @@ def repair_key(d, key_path_tup, fix):
def repair_val(d, key_path_tup, repair_dict):
- try:
- for num, key in enumerate(key_path_tup[:-1]):
- d = d[key]
- if isinstance(d, list):
- for sub_d in d:
- repair_val(sub_d, key_path_tup[num+1:], repair_dict)
- for bug, fix in repair_dict.items():
- if d[key_path_tup[-1]] == bug:
- d[key_path_tup[-1]] = fix
- except KeyError:
- pass
- except TypeError:
- pass
+ """Repairs inappropriate values caused by referencing self.ax_ct.
+
+ This function allows inappropriate values to be used in the
+ PlotlyRenderer.layout and PlotlyRenderer.data dictionaries. This is done
+ for the following reasons:
+
+ - Code is made simpler by treating 1st axes instance the same as
+ subsequent axes instances.
+ - If future releases of Plotly accept values such as 'x1' or 'y1',
+ plotly_renderer.py and plolty_utils.py can be updated simply.
+
+ Dictionaries need not be continuous for use with a key_path_tup. For
+ example, layout['annotations'] is actually a list of annotation
+ dictionaries. When repair_val() runs into such a list, it assumes that
+ the list is populated by dictionaries and continues down the rest of the
+ key_path_tup for each dictionary in the list.
+
+ Positional arguments:
+ d -- a plotly layout or data dictionary
+ key_path_tup -- a tuple of dictionary keys that leads to the conflict
+ repair_dict -- a dictionary that contains {bad_value: good_value} entries
+
+ """
+ try:
+ for num, key in enumerate(key_path_tup[:-1]):
+ d = d[key]
+ if isinstance(d, list):
+ for sub_d in d:
+ repair_val(sub_d, key_path_tup[num+1:], repair_dict)
+ for bug, fix in repair_dict.items():
+ if d[key_path_tup[-1]] == bug:
+ d[key_path_tup[-1]] = fix
+ except KeyError:
+ pass
+ except TypeError:
+ pass
def repair_data(data):
+ """Fixes innapropriate keys and values in plotly data list.
+
+ This function calls repair_key() and repair_val() for each entry in
+ DATA_KEY_REPAIRS and DATA_VAL_REPAIRS. It assumes that the keys in these
+ dictionaries are tuples with paths to known possible errors.
+
+ Positional arguments:
+ data -- a list of plotly data dictionaries
+
+ """
for data_dict in data:
- for key_path_tup, fix in data_key_repairs.items():
+ for key_path_tup, fix in DATA_KEY_REPAIRS.items():
repair_key(data_dict, key_path_tup, fix)
- for key_path_tup, repair_dict in data_val_repairs.items():
+ for key_path_tup, repair_dict in DATA_VAL_REPAIRS.items():
repair_val(data_dict, key_path_tup, repair_dict)
def repair_layout(layout):
- for key_path_tup, fix in layout_key_repairs.items():
+ """Fixes innapropriate keys and values in plotly layout dict.
+
+ This function calls repair_key() and repair_val() for each entry in
+ LAYOUT_KEY_REPAIRS and LAYOUT_VAL_REPAIRS. It assumes that the keys in
+ these dictionaries are tuples with paths to known possible errors.
+
+ Positional arguments:
+ layout -- a plotly layout dictionary
+
+ """
+ for key_path_tup, fix in LAYOUT_KEY_REPAIRS.items():
repair_key(layout, key_path_tup, fix)
- for key_path_tup, repair_dict in layout_val_repairs.items():
+ for key_path_tup, repair_dict in LAYOUT_VAL_REPAIRS.items():
repair_val(layout, key_path_tup, repair_dict)
-dash_map = {
+DASH_MAP = {
'10,0': 'solid',
'6,6': 'dash',
'2,2': 'dot',
@@ -88,7 +178,7 @@ dash_map = {
'none': 'solid'
}
-symbol_map = {
+SYMBOL_MAP = {
'o': 'dot',
'v': 'triangle-down',
'^': 'triangle-up',
@@ -104,19 +194,19 @@ symbol_map = {
'-.': 'dashdot'
}
-data_key_repairs = {}
+DATA_KEY_REPAIRS = {}
-layout_key_repairs = {
+LAYOUT_KEY_REPAIRS = {
('xaxis1',): 'xaxis',
('yaxis1',): 'yaxis'
}
-data_val_repairs = { # (1) keys with None get deleted, (2) empty dicts get deleted!
+DATA_VAL_REPAIRS = {
('xaxis',): {'x1': None},
('yaxis',): {'y1': None}
}
-layout_val_repairs = {
+LAYOUT_VAL_REPAIRS = {
('xaxis', 'anchor'): {'y1': 'y'},
('yaxis', 'anchor'): {'x1': 'x'},
('annotations', 'xref'): {'x1': 'x'},
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-mplexporter.git
More information about the Python-modules-commits
mailing list