[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