""" plotly ====== A module that contains the plotly class, a liaison between the user and ploty's servers. 1. get DEFAULT_PLOT_OPTIONS for options 2. update plot_options with .plotly/ dir 3. update plot_options with _plot_options 4. update plot_options with kwargs! """ from __future__ import absolute_import import sys import json import warnings import copy import os import six import base64 import requests if sys.version[:1] == '2': from urlparse import urlparse else: from urllib.parse import urlparse from plotly.plotly import chunked_requests from plotly import utils from plotly import tools from plotly import exceptions from plotly import version __all__ = None _DEFAULT_PLOT_OPTIONS = dict( filename="plot from API", fileopt="new", world_readable=True, auto_open=True, validate=True) _credentials = dict() _plot_options = dict() _config = dict() ### test file permissions and make sure nothing is corrupted ### tools.ensure_local_plotly_files() ### _credentials stuff ### def sign_in(username, api_key, **kwargs): """Set module-scoped _credentials for session. Optionally, set config info. """ _credentials['username'], _credentials['api_key'] = username, api_key # TODO: verify these _credentials with plotly _config['plotly_domain'] = kwargs.get('plotly_domain') _config['plotly_streaming_domain'] = kwargs.get('plotly_streaming_domain') _config['plotly_api_domain'] = kwargs.get('plotly_api_domain') _config['plotly_ssl_verification'] = kwargs.get('plotly_ssl_verification') # TODO: verify format of config options ### plot options stuff ### def update_plot_options(**kwargs): """ Update the module-level _plot_options """ _plot_options.update(kwargs) def get_plot_options(): """ Returns a copy of the user supplied plot options. Use `update_plot_options()` to change. """ return copy.copy(_plot_options) def get_credentials(): """Returns the credentials that will be sent to plotly.""" credentials = tools.get_credentials_file() for credentials_key in credentials: if _credentials.get(credentials_key): credentials[credentials_key] = _credentials[credentials_key] return credentials ### plot stuff ### def iplot(figure_or_data, **plot_options): """Create a unique url for this plot in Plotly and open in IPython. plot_options keyword agruments: filename (string) -- the name that will be associated with this figure fileopt ('new' | 'overwrite' | 'extend' | 'append') 'new': create a new, unique url for this plot 'overwrite': overwrite the file associated with `filename` with this 'extend': add additional numbers (data) to existing traces 'append': add additional traces to existing data lists world_readable (default=True) -- make this figure private/public """ if 'auto_open' not in plot_options: plot_options['auto_open'] = False res = plot(figure_or_data, **plot_options) urlsplit = res.split('/') username, plot_id = urlsplit[-2][1:], urlsplit[-1] # TODO: HACKY! embed_options = dict() if 'width' in plot_options: embed_options['width'] = plot_options['width'] if 'height' in plot_options: embed_options['height'] = plot_options['height'] return tools.embed(username, plot_id, **embed_options) def _plot_option_logic(plot_options): """Sets plot_options via a precedence hierarchy.""" options = dict() options.update(_DEFAULT_PLOT_OPTIONS) options.update(_plot_options) options.update(plot_options) if ('filename' in plot_options and 'fileopt' not in _plot_options and 'fileopt' not in plot_options): options['fileopt'] = 'overwrite' return options def get_config(): """Returns either module config or file config.""" config = tools.get_config_file() for config_key in config: if _config.get(config_key) is not None: config[config_key] = _config[config_key] return config def plot(figure_or_data, validate=True, **plot_options): """Create a unique url for this plot in Plotly and optionally open url. plot_options keyword agruments: filename (string) -- the name that will be associated with this figure fileopt ('new' | 'overwrite' | 'extend' | 'append') -- 'new' creates a 'new': create a new, unique url for this plot 'overwrite': overwrite the file associated with `filename` with this 'extend': add additional numbers (data) to existing traces 'append': add additional traces to existing data lists world_readable (default=True) -- make this figure private/public auto_open (default=True) -- Toggle browser options True: open this plot in a new browser tab False: do not open plot in the browser, but do return the unique url """ if isinstance(figure_or_data, dict): figure = figure_or_data elif isinstance(figure_or_data, list): figure = {'data': figure_or_data} else: raise exceptions.PlotlyError("The `figure_or_data` positional argument " "must be either `dict`-like or " "`list`-like.") if validate: try: tools.validate(figure, obj_type='Figure') except exceptions.PlotlyError as err: raise exceptions.PlotlyError("Invalid 'figure_or_data' argument. " "Plotly will not be able to properly " "parse the resulting JSON. If you " "want to send this 'figure_or_data' " "to Plotly anyway (not recommended), " "you can set 'validate=False' as a " "plot option.\nHere's why you're " "seeing this error:\n\n{0}" "".format(err)) if not figure['data']: raise exceptions.PlotlyEmptyDataError( "Empty data list found. Make sure that you populated the " "list of data objects you're sending and try again.\n" "Questions? support@plot.ly" ) for entry in figure['data']: for key, val in list(entry.items()): try: if len(val) > 40000: msg = ("Woah there! Look at all those points! Due to " "browser limitations, Plotly has a hard time " "graphing more than 500k data points for line " "charts, or 40k points for other types of charts. " "Here are some suggestions:\n" "(1) Trying using the image API to return an image " "instead of a graph URL\n" "(2) Use matplotlib\n" "(3) See if you can create your visualization with " "fewer data points\n\n" "If the visualization you're using aggregates " "points (e.g., box plot, histogram, etc.) you can " "disregard this warning.") warnings.warn(msg) except TypeError: pass plot_options = _plot_option_logic(plot_options) res = _send_to_plotly(figure, **plot_options) if res['error'] == '': if plot_options['auto_open']: _open_url(res['url']) return res['url'] else: raise exceptions.PlotlyAccountError(res['error']) def iplot_mpl(fig, resize=True, strip_style=False, update=None, **plot_options): """Replot a matplotlib figure with plotly in IPython. This function: 1. converts the mpl figure into JSON (run help(plolty.tools.mpl_to_plotly)) 2. makes a request to Plotly to save this figure in your account 3. displays the image in your IPython output cell Positional agruments: fig -- a figure object from matplotlib Keyword arguments: resize (default=True) -- allow plotly to choose the figure size strip_style (default=False) -- allow plotly to choose style options update (default=None) -- update the resulting figure with an 'update' dictionary-like object resembling a plotly 'Figure' object Additional keyword arguments: plot_options -- run help(plotly.plotly.iplot) """ fig = tools.mpl_to_plotly(fig, resize=resize, strip_style=strip_style) if update and isinstance(update, dict): try: fig.update(update) fig.validate() except exceptions.PlotlyGraphObjectError as err: err.add_note("Your updated figure could not be properly validated.") err.prepare() raise elif update is not None: raise exceptions.PlotlyGraphObjectError( "'update' must be dictionary-like and a valid plotly Figure " "object. Run 'help(plotly.graph_objs.Figure)' for more info." ) return iplot(fig, **plot_options) def plot_mpl(fig, resize=True, strip_style=False, update=None, **plot_options): """Replot a matplotlib figure with plotly. This function: 1. converts the mpl figure into JSON (run help(plolty.tools.mpl_to_plotly)) 2. makes a request to Plotly to save this figure in your account 3. opens your figure in a browser tab OR returns the unique figure url Positional agruments: fig -- a figure object from matplotlib Keyword arguments: resize (default=True) -- allow plotly to choose the figure size strip_style (default=False) -- allow plotly to choose style options update (default=None) -- update the resulting figure with an 'update' dictionary-like object resembling a plotly 'Figure' object Additional keyword arguments: plot_options -- run help(plotly.plotly.plot) """ fig = tools.mpl_to_plotly(fig, resize=resize, strip_style=strip_style) if update and isinstance(update, dict): try: fig.update(update) fig.validate() except exceptions.PlotlyGraphObjectError as err: err.add_note("Your updated figure could not be properly validated.") err.prepare() raise elif update is not None: raise exceptions.PlotlyGraphObjectError( "'update' must be dictionary-like and a valid plotly Figure " "object. Run 'help(plotly.graph_objs.Figure)' for more info." ) return plot(fig, **plot_options) def get_figure(file_owner_or_url, file_id=None, raw=False): """Returns a JSON figure representation for the specified file Plotly uniquely identifies figures with a 'file_owner'/'file_id' pair. Since each file is given a corresponding unique url, you may also simply pass a valid plotly url as the first argument. Examples: fig = get_figure('https://plot.ly/~chris/1638') fig = get_figure('chris', 1638) Note, if you're using a file_owner string as the first argument, you MUST specify a `file_id` keyword argument. Else, if you're using a url string as the first argument, you MUST NOT specify a `file_id` keyword argu