From ef58782883a960907fdc3fdfc15c2991130b3821 Mon Sep 17 00:00:00 2001 From: Andrew Seier Date: Fri, 30 Dec 2016 12:38:51 -0800 Subject: [PATCH 1/6] =?UTF-8?q?Don=E2=80=99t=20auto-download=20graph=20ref?= =?UTF-8?q?erence=20on=20import.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Conflicts: plotly/api/v2/plot_schema.py plotly/graph_reference.py plotly/tests/test_core/test_api/test_v2/test_plot_schema.py plotly/tests/test_core/test_graph_reference/test_graph_reference.py --- makefile | 9 ++-- plotly/files.py | 1 - plotly/graph_reference.py | 48 +++---------------- .../test_graph_reference.py | 36 ++------------ plotly/tests/utils.py | 5 -- plotly/tools.py | 7 +-- 6 files changed, 15 insertions(+), 91 deletions(-) diff --git a/makefile b/makefile index 8242f8c888e..8a370d00a33 100644 --- a/makefile +++ b/makefile @@ -18,11 +18,12 @@ setup_subs : update_default_schema : @echo "Making sure the default-schema.json file is up to date" - python -c "import json;\ - from plotly.graph_reference import GRAPH_REFERENCE;\ + python -c "import requests;/ + from requests.compat import json as _json;\ + response = requests.get('https://api.plot.ly/v2/plot-schema?sha1';/ f = open('plotly/graph_reference/default-schema.json', 'w');\ - json.dump(GRAPH_REFERENCE, f, indent=4, sort_keys=True,\ - separators=(',', ': '));\ + _json.dump(response.json()['schema'], f, indent=4,\ + sort_keys=True, separators=(',', ': '));\ f.close()" install : sync_subs diff --git a/plotly/files.py b/plotly/files.py index a04523c0254..a97e79fe255 100644 --- a/plotly/files.py +++ b/plotly/files.py @@ -4,7 +4,6 @@ PLOTLY_DIR = os.path.join(os.path.expanduser("~"), ".plotly") CREDENTIALS_FILE = os.path.join(PLOTLY_DIR, ".credentials") CONFIG_FILE = os.path.join(PLOTLY_DIR, ".config") -GRAPH_REFERENCE_FILE = os.path.join(PLOTLY_DIR, ".graph_reference") TEST_DIR = os.path.join(os.path.expanduser("~"), ".test") TEST_FILE = os.path.join(PLOTLY_DIR, ".permission_test") diff --git a/plotly/graph_reference.py b/plotly/graph_reference.py index 1e8eccb0213..18c9118c852 100644 --- a/plotly/graph_reference.py +++ b/plotly/graph_reference.py @@ -4,19 +4,14 @@ """ from __future__ import absolute_import -import hashlib -import json import os import re from pkg_resources import resource_string -import requests import six +from requests.compat import json as _json -from plotly import files, utils - -GRAPH_REFERENCE_PATH = '/v2/plot-schema' -GRAPH_REFERENCE_DOWNLOAD_TIMEOUT = 5 # seconds +from plotly import utils # For backwards compat, we keep this list of previously known objects. @@ -66,45 +61,14 @@ def get_graph_reference(): """ - Attempts to load local copy of graph reference or makes GET request if DNE. + Load graph reference JSON (aka plot-schema) :return: (dict) The graph reference. - :raises: (PlotlyError) When graph reference DNE and GET request fails. """ - default_config = files.FILE_CONTENT[files.CONFIG_FILE] - if files.check_file_permissions(): - graph_reference = utils.load_json_dict(files.GRAPH_REFERENCE_FILE) - config = utils.load_json_dict(files.CONFIG_FILE) - - # TODO: https://github.com/plotly/python-api/issues/293 - plotly_api_domain = config.get('plotly_api_domain', - default_config['plotly_api_domain']) - else: - graph_reference = {} - plotly_api_domain = default_config['plotly_api_domain'] - - sha1 = hashlib.sha1(six.b(str(graph_reference))).hexdigest() - - graph_reference_url = '{}{}?sha1={}'.format(plotly_api_domain, - GRAPH_REFERENCE_PATH, sha1) - try: - response = requests.get(graph_reference_url, - timeout=GRAPH_REFERENCE_DOWNLOAD_TIMEOUT) - response.raise_for_status() - except requests.exceptions.RequestException: - if not graph_reference: - path = os.path.join('graph_reference', 'default-schema.json') - s = resource_string('plotly', path).decode('utf-8') - graph_reference = json.loads(s) - else: - if six.PY3: - content = str(response.content, encoding='utf-8') - else: - content = response.content - data = json.loads(content) - if data['modified']: - graph_reference = data['schema'] + path = os.path.join('graph_reference', 'default-schema.json') + s = resource_string('plotly', path).decode('utf-8') + graph_reference = _json.loads(s) return utils.decode_unicode(graph_reference) diff --git a/plotly/tests/test_core/test_graph_reference/test_graph_reference.py b/plotly/tests/test_core/test_graph_reference/test_graph_reference.py index 38ac0b872b7..ed271dda37f 100644 --- a/plotly/tests/test_core/test_graph_reference/test_graph_reference.py +++ b/plotly/tests/test_core/test_graph_reference/test_graph_reference.py @@ -13,46 +13,17 @@ import six from nose.plugins.attrib import attr -from plotly import files, graph_reference as gr, tools, utils +from plotly import files, graph_reference as gr from plotly.graph_reference import string_to_class_name, get_role from plotly.tests.utils import PlotlyTestCase class TestGraphReferenceCaching(PlotlyTestCase): - def set_graph_reference(self, graph_reference): - if files.check_file_permissions(): - utils.save_json_dict(files.GRAPH_REFERENCE_FILE, graph_reference) - - @attr('slow') - def test_get_graph_reference_outdated(self): - - # if the hash of the current graph reference doesn't match the hash of - # the graph reference a Plotly server has, we should update! - - outdated_graph_reference = {'real': 'old'} - self.set_graph_reference(outdated_graph_reference) - graph_reference = gr.get_graph_reference() - self.assertNotEqual(graph_reference, outdated_graph_reference) - - def test_get_graph_reference_bad_request_local_copy(self): - - # if the request fails (mocked by using a bad url here) and a local - # copy of the graph reference exists, we can just use that. - - tools.set_config_file(plotly_api_domain='api.am.not.here.ly') - local_graph_reference = {'real': 'local'} - self.set_graph_reference(local_graph_reference) - graph_reference = gr.get_graph_reference() - self.assertEqual(graph_reference, local_graph_reference) - - def test_get_graph_reference_bad_request_no_copy(self): + def test_get_graph_reference(self): # if we don't have a graph reference we load an outdated default - tools.set_config_file(plotly_api_domain='api.am.not.here.ly') - empty_graph_reference = {} # set it to a false-y value. - self.set_graph_reference(empty_graph_reference) path = os.path.join('graph_reference', 'default-schema.json') s = resource_string('plotly', path).decode('utf-8') default_graph_reference = json.loads(s) @@ -62,8 +33,7 @@ def test_get_graph_reference_bad_request_no_copy(self): @attr('slow') def test_default_schema_is_up_to_date(self): api_domain = files.FILE_CONTENT[files.CONFIG_FILE]['plotly_api_domain'] - graph_reference_url = '{}{}?sha1'.format(api_domain, - gr.GRAPH_REFERENCE_PATH) + graph_reference_url = '{}{}?sha1'.format(api_domain, '/v2/plot-schema') response = requests.get(graph_reference_url) if six.PY3: content = str(response.content, encoding='utf-8') diff --git a/plotly/tests/utils.py b/plotly/tests/utils.py index fb837d74bfa..f8b1438e6f1 100644 --- a/plotly/tests/utils.py +++ b/plotly/tests/utils.py @@ -37,8 +37,6 @@ def stash_files(self): if files.check_file_permissions(): self._credentials = utils.load_json_dict(files.CREDENTIALS_FILE) self._config = utils.load_json_dict(files.CONFIG_FILE) - self._graph_reference = \ - utils.load_json_dict(files.GRAPH_REFERENCE_FILE) def restore_files(self): if files.check_file_permissions(): @@ -46,9 +44,6 @@ def restore_files(self): utils.save_json_dict(files.CREDENTIALS_FILE, self._credentials) if self._config is not None: utils.save_json_dict(files.CONFIG_FILE, self._config) - if self._graph_reference is not None: - utils.save_json_dict(files.GRAPH_REFERENCE_FILE, - self._graph_reference) def stash_session(self): self._session = copy.deepcopy(session._session) diff --git a/plotly/tools.py b/plotly/tools.py index 0b78c535996..e43106a940d 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -22,7 +22,7 @@ from plotly import graph_reference from plotly import session from plotly.files import (CONFIG_FILE, CREDENTIALS_FILE, FILE_CONTENT, - GRAPH_REFERENCE_FILE, check_file_permissions) + check_file_permissions) DEFAULT_PLOTLY_COLORS = ['rgb(31, 119, 180)', 'rgb(255, 127, 14)', 'rgb(44, 160, 44)', 'rgb(214, 39, 40)', @@ -146,11 +146,6 @@ def ensure_local_plotly_files(): del contents[key] utils.save_json_dict(fn, contents) - # make a request to get graph reference if DNE. - utils.ensure_file_exists(GRAPH_REFERENCE_FILE) - utils.save_json_dict(GRAPH_REFERENCE_FILE, - graph_reference.GRAPH_REFERENCE) - else: warnings.warn("Looks like you don't have 'read-write' permission to " "your 'home' ('~') directory or to our '~/.plotly' " From c26e318fb974ddd0f0dea85a51db3bb320d5ac43 Mon Sep 17 00:00:00 2001 From: Andrew Seier Date: Fri, 30 Dec 2016 12:48:09 -0800 Subject: [PATCH 2/6] Add file to auto-generate graph objects. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, we generated these *on import*, which is slow and it’s a little sneaky. Users end up using classes that are not actually written *anywhere* in our package. Now that we only update the plot schema in version releases (no longer on import), we can also just programmatically add in all the graph obj classes. Instead of adding them to memory, we literally just print them to the file. This is some serious meta-programming… but, the resulting file will have to be syntactically valid and pass our test suite, so I’m not *too* worried about it. --- makefile | 2 + plotly/graph_objs/graph_objs.py | 937 +++++++++++++++++++++++--- plotly/graph_objs/graph_objs_tools.py | 12 +- update_graph_objs.py | 270 ++++++++ 4 files changed, 1124 insertions(+), 97 deletions(-) create mode 100644 update_graph_objs.py diff --git a/makefile b/makefile index 8a370d00a33..a0ba2bc614c 100644 --- a/makefile +++ b/makefile @@ -25,6 +25,8 @@ update_default_schema : _json.dump(response.json()['schema'], f, indent=4,\ sort_keys=True, separators=(',', ': '));\ f.close()" + @echo "Auto-generating graph objects based on updated default-schema." + python update_graph_objs.py install : sync_subs @echo "" diff --git a/plotly/graph_objs/graph_objs.py b/plotly/graph_objs/graph_objs.py index 65632cadad6..10e6221f09e 100644 --- a/plotly/graph_objs/graph_objs.py +++ b/plotly/graph_objs/graph_objs.py @@ -797,60 +797,333 @@ def create(object_name, *args, **kwargs): return PlotlyDict(*args, **kwargs) -def _add_classes_to_globals(globals): +# AUTO-GENERATED BELOW. DO NOT EDIT! See makefile. + + +class AngularAxis(PlotlyDict): + """ + Valid attributes for 'angularaxis' at path [] under parents (): + + ['domain', 'endpadding', 'range', 'showline', 'showticklabels', + 'tickcolor', 'ticklen', 'tickorientation', 'ticksuffix', 'visible'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'angularaxis' + + +class Annotation(PlotlyDict): + """ + Valid attributes for 'annotation' at path [] under parents (): + + ['align', 'arrowcolor', 'arrowhead', 'arrowsize', 'arrowwidth', 'ax', + 'axref', 'ay', 'ayref', 'bgcolor', 'bordercolor', 'borderpad', + 'borderwidth', 'font', 'opacity', 'ref', 'showarrow', 'text', + 'textangle', 'visible', 'x', 'xanchor', 'xref', 'y', 'yanchor', 'yref'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'annotation' + + +class Annotations(PlotlyList): + """ + Valid items for 'annotations' at path [] under parents (): + ['Annotation'] + + """ + _name = 'annotations' + + +class Area(PlotlyDict): + """ + Valid attributes for 'area' at path [] under parents (): + + ['hoverinfo', 'legendgroup', 'marker', 'name', 'opacity', 'r', 'rsrc', + 'showlegend', 'stream', 't', 'tsrc', 'type', 'uid', 'visible'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'area' + + +class Bar(PlotlyDict): + """ + Valid attributes for 'bar' at path [] under parents (): + + ['bardir', 'base', 'basesrc', 'dx', 'dy', 'error_x', 'error_y', + 'hoverinfo', 'insidetextfont', 'legendgroup', 'marker', 'name', + 'offset', 'offsetsrc', 'opacity', 'orientation', 'outsidetextfont', + 'r', 'rsrc', 'showlegend', 'stream', 't', 'text', 'textfont', + 'textposition', 'textpositionsrc', 'textsrc', 'tsrc', 'type', 'uid', + 'visible', 'width', 'widthsrc', 'x', 'x0', 'xaxis', 'xcalendar', + 'xsrc', 'y', 'y0', 'yaxis', 'ycalendar', 'ysrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'bar' + + +class Box(PlotlyDict): + """ + Valid attributes for 'box' at path [] under parents (): + + ['boxmean', 'boxpoints', 'fillcolor', 'hoverinfo', 'jitter', + 'legendgroup', 'line', 'marker', 'name', 'opacity', 'orientation', + 'pointpos', 'showlegend', 'stream', 'type', 'uid', 'visible', + 'whiskerwidth', 'x', 'x0', 'xaxis', 'xsrc', 'y', 'y0', 'yaxis', 'ysrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'box' + + +class Candlestick(PlotlyDict): + """ + Valid attributes for 'candlestick' at path [] under parents (): + + ['close', 'closesrc', 'decreasing', 'high', 'highsrc', 'hoverinfo', + 'increasing', 'legendgroup', 'line', 'low', 'lowsrc', 'name', + 'opacity', 'open', 'opensrc', 'showlegend', 'stream', 'text', + 'textsrc', 'type', 'uid', 'visible', 'whiskerwidth', 'x', 'xaxis', + 'xcalendar', 'xsrc', 'yaxis'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'candlestick' + + +class Choropleth(PlotlyDict): + """ + Valid attributes for 'choropleth' at path [] under parents (): + + ['autocolorscale', 'colorbar', 'colorscale', 'geo', 'hoverinfo', + 'legendgroup', 'locationmode', 'locations', 'locationssrc', 'marker', + 'name', 'opacity', 'reversescale', 'showlegend', 'showscale', 'stream', + 'text', 'textsrc', 'type', 'uid', 'visible', 'z', 'zauto', 'zmax', + 'zmin', 'zsrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'choropleth' + + +class ColorBar(PlotlyDict): + """ + Valid attributes for 'colorbar' at path [] under parents (): + + ['bgcolor', 'bordercolor', 'borderwidth', 'dtick', 'exponentformat', + 'len', 'lenmode', 'nticks', 'outlinecolor', 'outlinewidth', + 'separatethousands', 'showexponent', 'showticklabels', + 'showtickprefix', 'showticksuffix', 'thickness', 'thicknessmode', + 'tick0', 'tickangle', 'tickcolor', 'tickfont', 'tickformat', 'ticklen', + 'tickmode', 'tickprefix', 'ticks', 'ticksuffix', 'ticktext', + 'ticktextsrc', 'tickvals', 'tickvalssrc', 'tickwidth', 'title', + 'titlefont', 'titleside', 'x', 'xanchor', 'xpad', 'y', 'yanchor', + 'ypad'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + """ - Create and add all the Graph Objects to this module for export. + _name = 'colorbar' + - :param (dict) globals: The globals() dict from this module. +class Contour(PlotlyDict): + """ + Valid attributes for 'contour' at path [] under parents (): + + ['autocolorscale', 'autocontour', 'colorbar', 'colorscale', + 'connectgaps', 'contours', 'dx', 'dy', 'hoverinfo', 'legendgroup', + 'line', 'name', 'ncontours', 'opacity', 'reversescale', 'showlegend', + 'showscale', 'stream', 'text', 'textsrc', 'transpose', 'type', 'uid', + 'visible', 'x', 'x0', 'xaxis', 'xcalendar', 'xsrc', 'xtype', 'y', 'y0', + 'yaxis', 'ycalendar', 'ysrc', 'ytype', 'z', 'zauto', 'zmax', 'zmin', + 'zsrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] """ - for class_name, class_dict in graph_reference.CLASSES.items(): - object_name = class_dict['object_name'] - base_type = class_dict['base_type'] + _name = 'contour' + + +class Contours(PlotlyDict): + """ + Valid attributes for 'contours' at path [] under parents (): + + ['coloring', 'end', 'showlines', 'size', 'start', 'x', 'y', 'z'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'contours' + + +class Data(PlotlyList): + """ + Valid items for 'data' at path [] under parents (): + ['Area', 'Bar', 'Box', 'Candlestick', 'Choropleth', 'Contour', + 'Heatmap', 'Heatmapgl', 'Histogram', 'Histogram2d', + 'Histogram2dcontour', 'Mesh3d', 'Ohlc', 'Pie', 'Pointcloud', 'Scatter', + 'Scatter3d', 'Scattergeo', 'Scattergl', 'Scattermapbox', + 'Scatterternary', 'Surface'] + + """ + _name = 'data' + + def _value_to_graph_object(self, index, value, _raise=True): + + if not isinstance(value, dict): + if _raise: + notes = ['Entry should subclass dict.'] + path = self._get_path() + (index, ) + raise exceptions.PlotlyListEntryError(self, path, + notes=notes) + else: + return + + item = value.get('type', 'scatter') + if item not in graph_reference.ARRAYS['data']['items']: + if _raise: + path = self._get_path() + (0, ) + raise exceptions.PlotlyDataTypeError(self, path) + + return GraphObjectFactory.create(item, _raise=_raise, + _parent=self, + _parent_key=index, **value) + + def get_data(self, flatten=False): + """ + Returns the JSON for the plot with non-data elements stripped. - # This is for backwards compat (e.g., Trace) and future changes. - if object_name is None: - globals[class_name] = base_type - continue + :param (bool) flatten: {'a': {'b': ''}} --> {'a.b': ''} + :returns: (dict|list) Depending on (flat|unflat) + + """ + if flatten: + data = [v.get_data(flatten=flatten) for v in self] + d = {} + taken_names = [] + for i, trace in enumerate(data): + + # we want to give the traces helpful names + # however, we need to be sure they're unique too... + trace_name = trace.pop('name', 'trace_{0}'.format(i)) + if trace_name in taken_names: + j = 1 + new_trace_name = "{0}_{1}".format(trace_name, j) + while new_trace_name in taken_names: + new_trace_name = ( + "{0}_{1}".format(trace_name, j) + ) + j += 1 + trace_name = new_trace_name + taken_names.append(trace_name) - doc = graph_objs_tools.get_help(object_name) - if object_name in graph_reference.ARRAYS: - class_bases = (PlotlyList, ) + # finish up the dot-concatenation + for k, v in trace.items(): + key = "{0}.{1}".format(trace_name, k) + d[key] = v + return d else: - class_bases = (PlotlyDict, ) + return super(Data, self).get_data(flatten=flatten) + + +class ErrorX(PlotlyDict): + """ + Valid attributes for 'error_x' at path [] under parents (): + + ['array', 'arrayminus', 'arrayminussrc', 'arraysrc', 'color', + 'copy_ystyle', 'copy_zstyle', 'opacity', 'symmetric', 'thickness', + 'traceref', 'tracerefminus', 'type', 'value', 'valueminus', 'visible', + 'width'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] - class_dict = {'__doc__': doc, '__name__': class_name, - '_name': object_name} + """ + _name = 'error_x' + + +class ErrorY(PlotlyDict): + """ + Valid attributes for 'error_y' at path [] under parents (): + + ['array', 'arrayminus', 'arrayminussrc', 'arraysrc', 'color', + 'copy_ystyle', 'copy_zstyle', 'opacity', 'symmetric', 'thickness', + 'traceref', 'tracerefminus', 'type', 'value', 'valueminus', 'visible', + 'width'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'error_y' + + +class ErrorZ(PlotlyDict): + """ + Valid attributes for 'error_z' at path [] under parents (): + + ['array', 'arrayminus', 'arrayminussrc', 'arraysrc', 'color', + 'copy_ystyle', 'copy_zstyle', 'opacity', 'symmetric', 'thickness', + 'traceref', 'tracerefminus', 'type', 'value', 'valueminus', 'visible', + 'width'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] - cls = type(str(class_name), class_bases, class_dict) + """ + _name = 'error_z' - globals[class_name] = cls +class Figure(PlotlyDict): + """ + Valid attributes for 'figure' at path [] under parents (): + + ['data', 'layout'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] -def _patch_figure_class(figure_class): + """ + _name = 'figure' def __init__(self, *args, **kwargs): - super(figure_class, self).__init__(*args, **kwargs) + super(Figure, self).__init__(*args, **kwargs) if 'data' not in self: self.data = GraphObjectFactory.create('data', _parent=self, _parent_key='data') - figure_class.__init__ = __init__ # TODO better integrate frames into Figure - #604 def __setitem__(self, key, value, **kwargs): if key == 'frames': super(PlotlyDict, self).__setitem__(key, value) else: - super(figure_class, self).__setitem__(key, value, **kwargs) - figure_class.__setitem__ = __setitem__ + super(Figure, self).__setitem__(key, value, **kwargs) def _get_valid_attributes(self): - super(figure_class, self)._get_valid_attributes() + super(Figure, self)._get_valid_attributes() # TODO better integrate frames into Figure - #604 if 'frames' not in self._valid_attributes: self._valid_attributes.add('frames') return self._valid_attributes - figure_class._get_valid_attributes = _get_valid_attributes def get_data(self, flatten=False): """ @@ -863,19 +1136,18 @@ def get_data(self, flatten=False): """ return self.data.get_data(flatten=flatten) - figure_class.get_data = get_data def to_dataframe(self): """ - Create a pandas dataframe with trace names and keys as column names. + Create a dataframe with trace names and keys as column names. :return: (DataFrame) """ data = self.get_data(flatten=True) from pandas import DataFrame, Series - return DataFrame(dict([(k, Series(v)) for k, v in data.items()])) - figure_class.to_dataframe = to_dataframe + return DataFrame( + dict([(k, Series(v)) for k, v in data.items()])) def print_grid(self): """ @@ -891,14 +1163,14 @@ def print_grid(self): raise Exception("Use plotly.tools.make_subplots " "to create a subplot grid.") print(grid_str) - figure_class.print_grid = print_grid def append_trace(self, trace, row, col): """ - Add a data traces to your figure bound to axes at the row, col index. + Add a trace to your figure bound to axes at the row, col index. The row, col index is generated from figures created with - plotly.tools.make_subplots and can be viewed with Figure.print_grid. + plotly.tools.make_subplots and can be viewed with + Figure.print_grid. :param (dict) trace: The data trace to be bound. :param (int) row: Subplot row index (see Figure.print_grid). @@ -920,7 +1192,8 @@ def append_trace(self, trace, row, col): grid_ref = self._grid_ref except AttributeError: raise Exception("In order to use Figure.append_trace, " - "you must first use plotly.tools.make_subplots " + "you must first use " + "plotly.tools.make_subplots " "to create a subplot grid.") if row <= 0: raise Exception("Row value is out of range. " @@ -931,13 +1204,15 @@ def append_trace(self, trace, row, col): try: ref = grid_ref[row-1][col-1] except IndexError: - raise Exception("The (row, col) pair sent is out of range. " - "Use Figure.print_grid to view the subplot grid. ") + raise Exception("The (row, col) pair sent is out of " + "range. Use Figure.print_grid to view the " + "subplot grid. ") if 'scene' in ref[0]: trace['scene'] = ref[0] if ref[0] not in self['layout']: raise Exception("Something went wrong. " - "The scene object for ({r},{c}) subplot cell " + "The scene object for ({r},{c}) " + "subplot cell " "got deleted.".format(r=row, c=col)) else: xaxis_key = "xaxis{ref}".format(ref=ref[0][1:]) @@ -945,76 +1220,554 @@ def append_trace(self, trace, row, col): if (xaxis_key not in self['layout'] or yaxis_key not in self['layout']): raise Exception("Something went wrong. " - "An axis object for ({r},{c}) subplot cell " - "got deleted.".format(r=row, c=col)) + "An axis object for ({r},{c}) subplot " + "cell got deleted." + .format(r=row, c=col)) trace['xaxis'] = ref[0] trace['yaxis'] = ref[1] self['data'] += [trace] - figure_class.append_trace = append_trace -def _patch_data_class(data_class): +class Font(PlotlyDict): + """ + Valid attributes for 'font' at path [] under parents (): + + ['color', 'family', 'size'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] - def _value_to_graph_object(self, index, value, _raise=True): + """ + _name = 'font' - if not isinstance(value, dict): - if _raise: - notes = ['Entry should subclass dict.'] - path = self._get_path() + (index, ) - raise exceptions.PlotlyListEntryError(self, path, notes=notes) - else: - return - item = value.get('type', 'scatter') - if item not in graph_reference.ARRAYS['data']['items']: - if _raise: - path = self._get_path() + (0, ) - raise exceptions.PlotlyDataTypeError(self, path) +class Frames(dict): + pass - return GraphObjectFactory.create(item, _raise=_raise, _parent=self, - _parent_key=index, **value) - data_class._value_to_graph_object = _value_to_graph_object - def get_data(self, flatten=False): - """ - Returns the JSON for the plot with non-data elements stripped. +class Heatmap(PlotlyDict): + """ + Valid attributes for 'heatmap' at path [] under parents (): + + ['autocolorscale', 'colorbar', 'colorscale', 'connectgaps', 'dx', 'dy', + 'hoverinfo', 'legendgroup', 'name', 'opacity', 'reversescale', + 'showlegend', 'showscale', 'stream', 'text', 'textsrc', 'transpose', + 'type', 'uid', 'visible', 'x', 'x0', 'xaxis', 'xcalendar', 'xgap', + 'xsrc', 'xtype', 'y', 'y0', 'yaxis', 'ycalendar', 'ygap', 'ysrc', + 'ytype', 'z', 'zauto', 'zmax', 'zmin', 'zsmooth', 'zsrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] - :param (bool) flatten: {'a': {'b': ''}} --> {'a.b': ''} - :returns: (dict|list) Depending on (flat|unflat) + """ + _name = 'heatmap' - """ - if flatten: - data = [v.get_data(flatten=flatten) for v in self] - d = {} - taken_names = [] - for i, trace in enumerate(data): - # we want to give the traces helpful names - # however, we need to be sure they're unique too... - trace_name = trace.pop('name', 'trace_{0}'.format(i)) - if trace_name in taken_names: - j = 1 - new_trace_name = "{0}_{1}".format(trace_name, j) - while new_trace_name in taken_names: - new_trace_name = "{0}_{1}".format(trace_name, j) - j += 1 - trace_name = new_trace_name - taken_names.append(trace_name) +class Heatmapgl(PlotlyDict): + """ + Valid attributes for 'heatmapgl' at path [] under parents (): + + ['autocolorscale', 'colorbar', 'colorscale', 'dx', 'dy', 'hoverinfo', + 'legendgroup', 'name', 'opacity', 'reversescale', 'showlegend', + 'showscale', 'stream', 'text', 'textsrc', 'transpose', 'type', 'uid', + 'visible', 'x', 'x0', 'xaxis', 'xsrc', 'xtype', 'y', 'y0', 'yaxis', + 'ysrc', 'ytype', 'z', 'zauto', 'zmax', 'zmin', 'zsrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'heatmapgl' + + +class Histogram(PlotlyDict): + """ + Valid attributes for 'histogram' at path [] under parents (): + + ['autobinx', 'autobiny', 'bardir', 'error_x', 'error_y', 'histfunc', + 'histnorm', 'hoverinfo', 'legendgroup', 'marker', 'name', 'nbinsx', + 'nbinsy', 'opacity', 'orientation', 'showlegend', 'stream', 'text', + 'textsrc', 'type', 'uid', 'visible', 'x', 'xaxis', 'xbins', + 'xcalendar', 'xsrc', 'y', 'yaxis', 'ybins', 'ycalendar', 'ysrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'histogram' + + +class Histogram2d(PlotlyDict): + """ + Valid attributes for 'histogram2d' at path [] under parents (): + + ['autobinx', 'autobiny', 'autocolorscale', 'colorbar', 'colorscale', + 'histfunc', 'histnorm', 'hoverinfo', 'legendgroup', 'marker', 'name', + 'nbinsx', 'nbinsy', 'opacity', 'reversescale', 'showlegend', + 'showscale', 'stream', 'type', 'uid', 'visible', 'x', 'xaxis', 'xbins', + 'xcalendar', 'xgap', 'xsrc', 'y', 'yaxis', 'ybins', 'ycalendar', + 'ygap', 'ysrc', 'z', 'zauto', 'zmax', 'zmin', 'zsmooth', 'zsrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'histogram2d' + + +class Histogram2dContour(PlotlyDict): + """ + Valid attributes for 'histogram2dcontour' at path [] under parents (): + + ['autobinx', 'autobiny', 'autocolorscale', 'autocontour', 'colorbar', + 'colorscale', 'contours', 'histfunc', 'histnorm', 'hoverinfo', + 'legendgroup', 'line', 'marker', 'name', 'nbinsx', 'nbinsy', + 'ncontours', 'opacity', 'reversescale', 'showlegend', 'showscale', + 'stream', 'type', 'uid', 'visible', 'x', 'xaxis', 'xbins', 'xcalendar', + 'xsrc', 'y', 'yaxis', 'ybins', 'ycalendar', 'ysrc', 'z', 'zauto', + 'zmax', 'zmin', 'zsrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'histogram2dcontour' + + +class Histogram2dcontour(PlotlyDict): + """ + Valid attributes for 'histogram2dcontour' at path [] under parents (): + + ['autobinx', 'autobiny', 'autocolorscale', 'autocontour', 'colorbar', + 'colorscale', 'contours', 'histfunc', 'histnorm', 'hoverinfo', + 'legendgroup', 'line', 'marker', 'name', 'nbinsx', 'nbinsy', + 'ncontours', 'opacity', 'reversescale', 'showlegend', 'showscale', + 'stream', 'type', 'uid', 'visible', 'x', 'xaxis', 'xbins', 'xcalendar', + 'xsrc', 'y', 'yaxis', 'ybins', 'ycalendar', 'ysrc', 'z', 'zauto', + 'zmax', 'zmin', 'zsrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'histogram2dcontour' + + +class Layout(PlotlyDict): + """ + Valid attributes for 'layout' at path [] under parents (): + + ['angularaxis', 'annotations', 'autosize', 'bargap', 'bargroupgap', + 'barmode', 'barnorm', 'boxgap', 'boxgroupgap', 'boxmode', 'calendar', + 'direction', 'dragmode', 'font', 'geo', 'height', 'hiddenlabels', + 'hiddenlabelssrc', 'hidesources', 'hovermode', 'images', 'legend', + 'mapbox', 'margin', 'orientation', 'paper_bgcolor', 'plot_bgcolor', + 'radialaxis', 'scene', 'separators', 'shapes', 'showlegend', 'sliders', + 'smith', 'ternary', 'title', 'titlefont', 'updatemenus', 'width', + 'xaxis', 'yaxis'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'layout' + + +class Legend(PlotlyDict): + """ + Valid attributes for 'legend' at path [] under parents (): + + ['bgcolor', 'bordercolor', 'borderwidth', 'font', 'orientation', + 'tracegroupgap', 'traceorder', 'x', 'xanchor', 'y', 'yanchor'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'legend' + + +class Line(PlotlyDict): + """ + Valid attributes for 'line' at path [] under parents (): + + ['autocolorscale', 'cauto', 'cmax', 'cmin', 'color', 'colorscale', + 'colorsrc', 'dash', 'outliercolor', 'outlierwidth', 'reversescale', + 'shape', 'showscale', 'simplify', 'smoothing', 'width', 'widthsrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'line' + + +class Margin(PlotlyDict): + """ + Valid attributes for 'margin' at path [] under parents (): + + ['autoexpand', 'b', 'l', 'pad', 'r', 't'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'margin' + + +class Marker(PlotlyDict): + """ + Valid attributes for 'marker' at path [] under parents (): + + ['autocolorscale', 'blend', 'border', 'cauto', 'cmax', 'cmin', 'color', + 'colorbar', 'colors', 'colorscale', 'colorsrc', 'colorssrc', 'line', + 'maxdisplayed', 'opacity', 'opacitysrc', 'outliercolor', + 'reversescale', 'showscale', 'size', 'sizemax', 'sizemin', 'sizemode', + 'sizeref', 'sizesrc', 'symbol', 'symbolsrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'marker' + + +class Mesh3d(PlotlyDict): + """ + Valid attributes for 'mesh3d' at path [] under parents (): + + ['alphahull', 'color', 'colorbar', 'colorscale', 'contour', + 'delaunayaxis', 'facecolor', 'facecolorsrc', 'flatshading', + 'hoverinfo', 'i', 'intensity', 'intensitysrc', 'isrc', 'j', 'jsrc', + 'k', 'ksrc', 'legendgroup', 'lighting', 'lightposition', 'name', + 'opacity', 'reversescale', 'scene', 'showlegend', 'showscale', + 'stream', 'type', 'uid', 'vertexcolor', 'vertexcolorsrc', 'visible', + 'x', 'xcalendar', 'xsrc', 'y', 'ycalendar', 'ysrc', 'z', 'zcalendar', + 'zsrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'mesh3d' + + +class Ohlc(PlotlyDict): + """ + Valid attributes for 'ohlc' at path [] under parents (): + + ['close', 'closesrc', 'decreasing', 'high', 'highsrc', 'hoverinfo', + 'increasing', 'legendgroup', 'line', 'low', 'lowsrc', 'name', + 'opacity', 'open', 'opensrc', 'showlegend', 'stream', 'text', + 'textsrc', 'tickwidth', 'type', 'uid', 'visible', 'x', 'xaxis', + 'xcalendar', 'xsrc', 'yaxis'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'ohlc' + + +class Pie(PlotlyDict): + """ + Valid attributes for 'pie' at path [] under parents (): + + ['direction', 'dlabel', 'domain', 'hole', 'hoverinfo', + 'insidetextfont', 'label0', 'labels', 'labelssrc', 'legendgroup', + 'marker', 'name', 'opacity', 'outsidetextfont', 'pull', 'pullsrc', + 'rotation', 'scalegroup', 'showlegend', 'sort', 'stream', 'text', + 'textfont', 'textinfo', 'textposition', 'textpositionsrc', 'textsrc', + 'type', 'uid', 'values', 'valuessrc', 'visible'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'pie' + + +class Pointcloud(PlotlyDict): + """ + Valid attributes for 'pointcloud' at path [] under parents (): + + ['hoverinfo', 'indices', 'indicessrc', 'legendgroup', 'marker', 'name', + 'opacity', 'showlegend', 'stream', 'text', 'textsrc', 'type', 'uid', + 'visible', 'x', 'xaxis', 'xbounds', 'xboundssrc', 'xsrc', 'xy', + 'xysrc', 'y', 'yaxis', 'ybounds', 'yboundssrc', 'ysrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'pointcloud' + + +class RadialAxis(PlotlyDict): + """ + Valid attributes for 'radialaxis' at path [] under parents (): + + ['domain', 'endpadding', 'orientation', 'range', 'showline', + 'showticklabels', 'tickcolor', 'ticklen', 'tickorientation', + 'ticksuffix', 'visible'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'radialaxis' + + +class Scatter(PlotlyDict): + """ + Valid attributes for 'scatter' at path [] under parents (): + + ['connectgaps', 'dx', 'dy', 'error_x', 'error_y', 'fill', 'fillcolor', + 'hoverinfo', 'hoveron', 'ids', 'idssrc', 'legendgroup', 'line', + 'marker', 'mode', 'name', 'opacity', 'r', 'rsrc', 'showlegend', + 'stream', 't', 'text', 'textfont', 'textposition', 'textpositionsrc', + 'textsrc', 'tsrc', 'type', 'uid', 'visible', 'x', 'x0', 'xaxis', + 'xcalendar', 'xsrc', 'y', 'y0', 'yaxis', 'ycalendar', 'ysrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'scatter' + + +class Scatter3d(PlotlyDict): + """ + Valid attributes for 'scatter3d' at path [] under parents (): + + ['connectgaps', 'error_x', 'error_y', 'error_z', 'hoverinfo', + 'legendgroup', 'line', 'marker', 'mode', 'name', 'opacity', + 'projection', 'scene', 'showlegend', 'stream', 'surfaceaxis', + 'surfacecolor', 'text', 'textfont', 'textposition', 'textpositionsrc', + 'textsrc', 'type', 'uid', 'visible', 'x', 'xcalendar', 'xsrc', 'y', + 'ycalendar', 'ysrc', 'z', 'zcalendar', 'zsrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'scatter3d' + + +class Scattergeo(PlotlyDict): + """ + Valid attributes for 'scattergeo' at path [] under parents (): + + ['connectgaps', 'fill', 'fillcolor', 'geo', 'hoverinfo', 'lat', + 'latsrc', 'legendgroup', 'line', 'locationmode', 'locations', + 'locationssrc', 'lon', 'lonsrc', 'marker', 'mode', 'name', 'opacity', + 'showlegend', 'stream', 'text', 'textfont', 'textposition', + 'textpositionsrc', 'textsrc', 'type', 'uid', 'visible'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'scattergeo' + + +class Scattergl(PlotlyDict): + """ + Valid attributes for 'scattergl' at path [] under parents (): + + ['connectgaps', 'dx', 'dy', 'error_x', 'error_y', 'fill', 'fillcolor', + 'hoverinfo', 'legendgroup', 'line', 'marker', 'mode', 'name', + 'opacity', 'showlegend', 'stream', 'text', 'textsrc', 'type', 'uid', + 'visible', 'x', 'x0', 'xaxis', 'xcalendar', 'xsrc', 'y', 'y0', 'yaxis', + 'ycalendar', 'ysrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'scattergl' + + +class Scattermapbox(PlotlyDict): + """ + Valid attributes for 'scattermapbox' at path [] under parents (): + + ['connectgaps', 'fill', 'fillcolor', 'hoverinfo', 'lat', 'latsrc', + 'legendgroup', 'line', 'lon', 'lonsrc', 'marker', 'mode', 'name', + 'opacity', 'showlegend', 'stream', 'subplot', 'text', 'textfont', + 'textposition', 'textsrc', 'type', 'uid', 'visible'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'scattermapbox' + + +class Scatterternary(PlotlyDict): + """ + Valid attributes for 'scatterternary' at path [] under parents (): + + ['a', 'asrc', 'b', 'bsrc', 'c', 'connectgaps', 'csrc', 'fill', + 'fillcolor', 'hoverinfo', 'hoveron', 'legendgroup', 'line', 'marker', + 'mode', 'name', 'opacity', 'showlegend', 'stream', 'subplot', 'sum', + 'text', 'textfont', 'textposition', 'textpositionsrc', 'textsrc', + 'type', 'uid', 'visible'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'scatterternary' + + +class Scene(PlotlyDict): + """ + Valid attributes for 'scene' at path [] under parents (): + + ['aspectmode', 'aspectratio', 'bgcolor', 'camera', 'cameraposition', + 'domain', 'dragmode', 'hovermode', 'xaxis', 'yaxis', 'zaxis'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'scene' + + +class Stream(PlotlyDict): + """ + Valid attributes for 'stream' at path [] under parents (): + + ['maxpoints', 'token'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'stream' + + +class Surface(PlotlyDict): + """ + Valid attributes for 'surface' at path [] under parents (): + + ['autocolorscale', 'cauto', 'cmax', 'cmin', 'colorbar', 'colorscale', + 'contours', 'hidesurface', 'hoverinfo', 'legendgroup', 'lighting', + 'lightposition', 'name', 'opacity', 'reversescale', 'scene', + 'showlegend', 'showscale', 'stream', 'surfacecolor', 'surfacecolorsrc', + 'text', 'textsrc', 'type', 'uid', 'visible', 'x', 'xcalendar', 'xsrc', + 'y', 'ycalendar', 'ysrc', 'z', 'zauto', 'zcalendar', 'zmax', 'zmin', + 'zsrc'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'surface' + + +class Trace(dict): + pass + + +class XAxis(PlotlyDict): + """ + Valid attributes for 'xaxis' at path [] under parents (): + + ['anchor', 'autorange', 'autotick', 'backgroundcolor', 'calendar', + 'categoryarray', 'categoryarraysrc', 'categoryorder', 'color', + 'domain', 'dtick', 'exponentformat', 'fixedrange', 'gridcolor', + 'gridwidth', 'hoverformat', 'linecolor', 'linewidth', 'mirror', + 'nticks', 'overlaying', 'position', 'range', 'rangemode', + 'rangeselector', 'rangeslider', 'separatethousands', 'showaxeslabels', + 'showbackground', 'showexponent', 'showgrid', 'showline', 'showspikes', + 'showticklabels', 'showtickprefix', 'showticksuffix', 'side', + 'spikecolor', 'spikesides', 'spikethickness', 'tick0', 'tickangle', + 'tickcolor', 'tickfont', 'tickformat', 'ticklen', 'tickmode', + 'tickprefix', 'ticks', 'ticksuffix', 'ticktext', 'ticktextsrc', + 'tickvals', 'tickvalssrc', 'tickwidth', 'title', 'titlefont', 'type', + 'zeroline', 'zerolinecolor', 'zerolinewidth'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'xaxis' + + +class XBins(PlotlyDict): + """ + Valid attributes for 'xbins' at path [] under parents (): + + ['end', 'size', 'start'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'xbins' - # finish up the dot-concatenation - for k, v in trace.items(): - key = "{0}.{1}".format(trace_name, k) - d[key] = v - return d - else: - return super(data_class, self).get_data(flatten=flatten) - data_class.get_data = get_data +class YAxis(PlotlyDict): + """ + Valid attributes for 'yaxis' at path [] under parents (): + + ['anchor', 'autorange', 'autotick', 'backgroundcolor', 'calendar', + 'categoryarray', 'categoryarraysrc', 'categoryorder', 'color', + 'domain', 'dtick', 'exponentformat', 'fixedrange', 'gridcolor', + 'gridwidth', 'hoverformat', 'linecolor', 'linewidth', 'mirror', + 'nticks', 'overlaying', 'position', 'range', 'rangemode', + 'separatethousands', 'showaxeslabels', 'showbackground', + 'showexponent', 'showgrid', 'showline', 'showspikes', 'showticklabels', + 'showtickprefix', 'showticksuffix', 'side', 'spikecolor', 'spikesides', + 'spikethickness', 'tick0', 'tickangle', 'tickcolor', 'tickfont', + 'tickformat', 'ticklen', 'tickmode', 'tickprefix', 'ticks', + 'ticksuffix', 'ticktext', 'ticktextsrc', 'tickvals', 'tickvalssrc', + 'tickwidth', 'title', 'titlefont', 'type', 'zeroline', 'zerolinecolor', + 'zerolinewidth'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'yaxis' -_add_classes_to_globals(globals()) -_patch_figure_class(globals()['Figure']) -_patch_data_class(globals()['Data']) -# We don't want to expose this module to users, just the classes. -# See http://blog.labix.org/2008/06/27/watch-out-for-listdictkeys-in-python-3 -__all__ = list(graph_reference.CLASSES.keys()) +class YBins(PlotlyDict): + """ + Valid attributes for 'ybins' at path [] under parents (): + + ['end', 'size', 'start'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'ybins' + + +class ZAxis(PlotlyDict): + """ + Valid attributes for 'zaxis' at path [] under parents (): + + ['autorange', 'backgroundcolor', 'calendar', 'categoryarray', + 'categoryarraysrc', 'categoryorder', 'color', 'dtick', + 'exponentformat', 'fixedrange', 'gridcolor', 'gridwidth', + 'hoverformat', 'linecolor', 'linewidth', 'mirror', 'nticks', 'range', + 'rangemode', 'separatethousands', 'showaxeslabels', 'showbackground', + 'showexponent', 'showgrid', 'showline', 'showspikes', 'showticklabels', + 'showtickprefix', 'showticksuffix', 'spikecolor', 'spikesides', + 'spikethickness', 'tick0', 'tickangle', 'tickcolor', 'tickfont', + 'tickformat', 'ticklen', 'tickmode', 'tickprefix', 'ticks', + 'ticksuffix', 'ticktext', 'ticktextsrc', 'tickvals', 'tickvalssrc', + 'tickwidth', 'title', 'titlefont', 'type', 'zeroline', 'zerolinecolor', + 'zerolinewidth'] + + Run `.help('attribute')` on any of the above. + '' is the object at [] + + """ + _name = 'zaxis' + +__all__ = [cls for cls in graph_reference.CLASSES.keys() if cls in globals()] diff --git a/plotly/graph_objs/graph_objs_tools.py b/plotly/graph_objs/graph_objs_tools.py index 334927ef4c4..950adab9455 100644 --- a/plotly/graph_objs/graph_objs_tools.py +++ b/plotly/graph_objs/graph_objs_tools.py @@ -37,7 +37,8 @@ def _list_help(object_name, path=(), parent_object_names=()): items = graph_reference.ARRAYS[object_name]['items'] items_classes = [graph_reference.string_to_class_name(item) for item in items] - lines = textwrap.wrap(repr(items_classes), width=LINE_SIZE-TAB_SIZE) + items_classes.sort() + lines = textwrap.wrap(repr(items_classes), width=LINE_SIZE-TAB_SIZE-1) help_dict = { 'object_name': object_name, @@ -54,9 +55,10 @@ def _list_help(object_name, path=(), parent_object_names=()): def _dict_object_help(object_name, path, parent_object_names): """See get_help().""" - attributes = graph_reference.get_valid_attributes(object_name, - parent_object_names) - lines = textwrap.wrap(repr(list(attributes)), width=LINE_SIZE-TAB_SIZE) + attributes = list( + graph_reference.get_valid_attributes(object_name, parent_object_names)) + attributes.sort() + lines = textwrap.wrap(repr(list(attributes)), width=LINE_SIZE-TAB_SIZE-1) help_dict = { 'object_name': object_name, @@ -167,7 +169,7 @@ def _dict_attribute_help(object_name, path, parent_object_names, attribute): if isinstance(val, list) and attribute == 'showline': val = val[0] - lines = textwrap.wrap(val, width=LINE_SIZE) + lines = textwrap.wrap(val, width=LINE_SIZE-1) help_string += '\n\t\t'.join(lines) else: help_string += '{}'.format(val) diff --git a/update_graph_objs.py b/update_graph_objs.py new file mode 100644 index 00000000000..71882c8d537 --- /dev/null +++ b/update_graph_objs.py @@ -0,0 +1,270 @@ +from plotly.graph_objs import graph_objs_tools +from plotly.graph_reference import ARRAYS, CLASSES + +FLAG = '# AUTO-GENERATED BELOW. DO NOT EDIT! See makefile.' + + +def get_non_generated_file_lines(): + """ + Copy each line up to our special FLAG line and return. + + :raises: (ValueError) If the FLAG isn't found. + :return: (list) The lines we're copying. + """ + + lines_to_copy = [] + flag_found = False + with open('./plotly/graph_objs/graph_objs.py', 'r') as f: + for line_to_copy in f: + if line_to_copy.startswith(FLAG): + flag_found = True + break + lines_to_copy.append(line_to_copy) + if not flag_found: + raise ValueError( + 'Failed to find flag:\n"{}"\nin graph_objs_tools.py.'.format(FLAG) + ) + return lines_to_copy + + +def print_figure_patch(f): + """Print a patch to our Figure object into the given open file.""" + + print( + ''' + def __init__(self, *args, **kwargs): + super(Figure, self).__init__(*args, **kwargs) + if 'data' not in self: + self.data = GraphObjectFactory.create('data', _parent=self, + _parent_key='data') + + # TODO better integrate frames into Figure - #604 + def __setitem__(self, key, value, **kwargs): + if key == 'frames': + super(PlotlyDict, self).__setitem__(key, value) + else: + super(Figure, self).__setitem__(key, value, **kwargs) + + def _get_valid_attributes(self): + super(Figure, self)._get_valid_attributes() + # TODO better integrate frames into Figure - #604 + if 'frames' not in self._valid_attributes: + self._valid_attributes.add('frames') + return self._valid_attributes + + def get_data(self, flatten=False): + """ + Returns the JSON for the plot with non-data elements stripped. + + Flattening may increase the utility of the result. + + :param (bool) flatten: {'a': {'b': ''}} --> {'a.b': ''} + :returns: (dict|list) Depending on (flat|unflat) + + """ + return self.data.get_data(flatten=flatten) + + def to_dataframe(self): + """ + Create a dataframe with trace names and keys as column names. + + :return: (DataFrame) + + """ + data = self.get_data(flatten=True) + from pandas import DataFrame, Series + return DataFrame( + dict([(k, Series(v)) for k, v in data.items()])) + + def print_grid(self): + """ + Print a visual layout of the figure's axes arrangement. + + This is only valid for figures that are created + with plotly.tools.make_subplots. + + """ + try: + grid_str = self.__dict__['_grid_str'] + except AttributeError: + raise Exception("Use plotly.tools.make_subplots " + "to create a subplot grid.") + print(grid_str) + + def append_trace(self, trace, row, col): + """ + Add a trace to your figure bound to axes at the row, col index. + + The row, col index is generated from figures created with + plotly.tools.make_subplots and can be viewed with + Figure.print_grid. + + :param (dict) trace: The data trace to be bound. + :param (int) row: Subplot row index (see Figure.print_grid). + :param (int) col: Subplot column index (see Figure.print_grid). + + Example: + # stack two subplots vertically + fig = tools.make_subplots(rows=2) + + This is the format of your plot grid: + [ (1,1) x1,y1 ] + [ (2,1) x2,y2 ] + + fig.append_trace(Scatter(x=[1,2,3], y=[2,1,2]), 1, 1) + fig.append_trace(Scatter(x=[1,2,3], y=[2,1,2]), 2, 1) + + """ + try: + grid_ref = self._grid_ref + except AttributeError: + raise Exception("In order to use Figure.append_trace, " + "you must first use " + "plotly.tools.make_subplots " + "to create a subplot grid.") + if row <= 0: + raise Exception("Row value is out of range. " + "Note: the starting cell is (1, 1)") + if col <= 0: + raise Exception("Col value is out of range. " + "Note: the starting cell is (1, 1)") + try: + ref = grid_ref[row-1][col-1] + except IndexError: + raise Exception("The (row, col) pair sent is out of " + "range. Use Figure.print_grid to view the " + "subplot grid. ") + if 'scene' in ref[0]: + trace['scene'] = ref[0] + if ref[0] not in self['layout']: + raise Exception("Something went wrong. " + "The scene object for ({r},{c}) " + "subplot cell " + "got deleted.".format(r=row, c=col)) + else: + xaxis_key = "xaxis{ref}".format(ref=ref[0][1:]) + yaxis_key = "yaxis{ref}".format(ref=ref[1][1:]) + if (xaxis_key not in self['layout'] + or yaxis_key not in self['layout']): + raise Exception("Something went wrong. " + "An axis object for ({r},{c}) subplot " + "cell got deleted." + .format(r=row, c=col)) + trace['xaxis'] = ref[0] + trace['yaxis'] = ref[1] + self['data'] += [trace] +''', file=f, end='' + ) + + +def print_data_patch(f): + """Print a patch to our Data object into the given open file.""" + print( + ''' + def _value_to_graph_object(self, index, value, _raise=True): + + if not isinstance(value, dict): + if _raise: + notes = ['Entry should subclass dict.'] + path = self._get_path() + (index, ) + raise exceptions.PlotlyListEntryError(self, path, + notes=notes) + else: + return + + item = value.get('type', 'scatter') + if item not in graph_reference.ARRAYS['data']['items']: + if _raise: + path = self._get_path() + (0, ) + raise exceptions.PlotlyDataTypeError(self, path) + + return GraphObjectFactory.create(item, _raise=_raise, + _parent=self, + _parent_key=index, **value) + + def get_data(self, flatten=False): + """ + Returns the JSON for the plot with non-data elements stripped. + + :param (bool) flatten: {'a': {'b': ''}} --> {'a.b': ''} + :returns: (dict|list) Depending on (flat|unflat) + + """ + if flatten: + data = [v.get_data(flatten=flatten) for v in self] + d = {} + taken_names = [] + for i, trace in enumerate(data): + + # we want to give the traces helpful names + # however, we need to be sure they're unique too... + trace_name = trace.pop('name', 'trace_{0}'.format(i)) + if trace_name in taken_names: + j = 1 + new_trace_name = "{0}_{1}".format(trace_name, j) + while new_trace_name in taken_names: + new_trace_name = ( + "{0}_{1}".format(trace_name, j) + ) + j += 1 + trace_name = new_trace_name + taken_names.append(trace_name) + + # finish up the dot-concatenation + for k, v in trace.items(): + key = "{0}.{1}".format(trace_name, k) + d[key] = v + return d + else: + return super(Data, self).get_data(flatten=flatten) +''', file=f, end='' + ) + + +def print_class(name, f): + class_dict = CLASSES[name] + print('\n', file=f) + object_name = class_dict['object_name'] + base_type = class_dict['base_type'] + + # This is for backwards compat (e.g., Trace) and future changes. + if object_name is None: + print('class {}({}):'.format(name, base_type.__name__), + file=f) + print(' pass', file=f) + return + + doc = graph_objs_tools.get_help(object_name) + if object_name in ARRAYS: + base_name = 'PlotlyList' + else: + base_name = 'PlotlyDict' + print('class {}({}):'.format(name, base_name), file=f) + doc_lines = doc.splitlines() + print(' """', file=f) + for doc_line in doc_lines: + print(' ' + doc_line, file=f) + print('\n """', file=f) + print(" _name = '{}'".format(object_name), file=f) + if name == 'Figure': + print_figure_patch(f) + elif name == 'Data': + print_data_patch(f) + +copied_lines = get_non_generated_file_lines() +with open('./plotly/graph_objs/graph_objs.py', 'w') as graph_objs_file: + + # Keep things *exactly* as they were above our special FLAG. + for line in copied_lines: + print(line, file=graph_objs_file, end='') + print(FLAG, file=graph_objs_file) + + # For each object in the plot schema, generate a class in the file. + class_names = list(CLASSES.keys()) + class_names.sort() + for class_name in class_names: + print_class(class_name, graph_objs_file) + + # Finish off the file by only exporting plot-schema names. + print('\n__all__ = [cls for cls in graph_reference.CLASSES.keys() ' + 'if cls in globals()]', file=graph_objs_file) From af5960be86859248ceabce7aec01255270e2629c Mon Sep 17 00:00:00 2001 From: Andrew Seier Date: Wed, 4 Jan 2017 08:42:30 -0800 Subject: [PATCH 3/6] Fix duplicate import source `graph_reference`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Python 2, you *must* have an `__init__.py` file to have a python package. However, in Python 3, you don’t need that! We have `/graph_reference` and `graph_reference.py` defined, which seems to *work*, but is poorly organized and should be fixed. For instance, it causes `unresolved import errors` in IDEs that have `goto-source`-type functionality. Conflicts: makefile --- makefile | 2 +- plotly/graph_reference.py | 2 +- plotly/{graph_reference => package_data}/default-schema.json | 0 .../test_core/test_graph_reference/test_graph_reference.py | 4 ++-- setup.py | 4 +++- 5 files changed, 7 insertions(+), 5 deletions(-) rename plotly/{graph_reference => package_data}/default-schema.json (100%) diff --git a/makefile b/makefile index a0ba2bc614c..9112b33dd0b 100644 --- a/makefile +++ b/makefile @@ -21,7 +21,7 @@ update_default_schema : python -c "import requests;/ from requests.compat import json as _json;\ response = requests.get('https://api.plot.ly/v2/plot-schema?sha1';/ - f = open('plotly/graph_reference/default-schema.json', 'w');\ + f = open('plotly/package_data/default-schema.json', 'w');\ _json.dump(response.json()['schema'], f, indent=4,\ sort_keys=True, separators=(',', ': '));\ f.close()" diff --git a/plotly/graph_reference.py b/plotly/graph_reference.py index 18c9118c852..007824f0f30 100644 --- a/plotly/graph_reference.py +++ b/plotly/graph_reference.py @@ -66,7 +66,7 @@ def get_graph_reference(): :return: (dict) The graph reference. """ - path = os.path.join('graph_reference', 'default-schema.json') + path = os.path.join('package_data', 'default-schema.json') s = resource_string('plotly', path).decode('utf-8') graph_reference = _json.loads(s) diff --git a/plotly/graph_reference/default-schema.json b/plotly/package_data/default-schema.json similarity index 100% rename from plotly/graph_reference/default-schema.json rename to plotly/package_data/default-schema.json diff --git a/plotly/tests/test_core/test_graph_reference/test_graph_reference.py b/plotly/tests/test_core/test_graph_reference/test_graph_reference.py index ed271dda37f..84fb8720f72 100644 --- a/plotly/tests/test_core/test_graph_reference/test_graph_reference.py +++ b/plotly/tests/test_core/test_graph_reference/test_graph_reference.py @@ -24,7 +24,7 @@ def test_get_graph_reference(self): # if we don't have a graph reference we load an outdated default - path = os.path.join('graph_reference', 'default-schema.json') + path = os.path.join('package_data', 'default-schema.json') s = resource_string('plotly', path).decode('utf-8') default_graph_reference = json.loads(s) graph_reference = gr.get_graph_reference() @@ -41,7 +41,7 @@ def test_default_schema_is_up_to_date(self): content = response.content schema = json.loads(content)['schema'] - path = os.path.join('graph_reference', 'default-schema.json') + path = os.path.join('package_data', 'default-schema.json') s = resource_string('plotly', path).decode('utf-8') default_schema = json.loads(s) diff --git a/setup.py b/setup.py index e72f78ae770..5bf9464f394 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,8 @@ def readme(): 'plotly/matplotlylib', 'plotly/matplotlylib/mplexporter', 'plotly/matplotlylib/mplexporter/renderers'], - package_data={'plotly': ['graph_reference/*.json', 'widgets/*.js', 'offline/*.js']}, + package_data={ + 'plotly': ['package_data/*.json', 'widgets/*.js', 'offline/*.js'] + }, install_requires=['requests', 'six', 'pytz'], zip_safe=False) From 2d888fe482b980d0acf36a499849fb21bda16152 Mon Sep 17 00:00:00 2001 From: Andrew Seier Date: Wed, 4 Jan 2017 08:57:22 -0800 Subject: [PATCH 4/6] Move all our `package_data` files into one dir. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This has been a source of confusion every time we end up needing a new resource (like a js or json file). If you forget to add the new pattern in `setup.py`, the installed site-package just won’t include the file. Now all our non `.py` files should live in `package_data/`. --- makefile | 2 +- plotly/offline/offline.py | 2 +- plotly/{widgets => package_data}/graphWidget.js | 0 plotly/{offline => package_data}/plotly.min.js | 0 plotly/widgets/graph_widget.py | 2 +- setup.py | 4 +--- 6 files changed, 4 insertions(+), 6 deletions(-) rename plotly/{widgets => package_data}/graphWidget.js (100%) rename plotly/{offline => package_data}/plotly.min.js (100%) diff --git a/makefile b/makefile index 9112b33dd0b..455b9582246 100644 --- a/makefile +++ b/makefile @@ -68,7 +68,7 @@ update_plotlyjs_for_offline : cdn_url = 'https://cdn.plot.ly/plotly-latest.min.js';\ response = urllib2.urlopen(cdn_url);\ html = response.read();\ - f = open('./plotly/offline/plotly.min.js', 'w');\ + f = open('./plotly/package_data/plotly.min.js', 'w');\ f.write(html);\ f.close()" @echo "---------------------------------" diff --git a/plotly/offline/offline.py b/plotly/offline/offline.py index 1031240714a..965b5af09e0 100644 --- a/plotly/offline/offline.py +++ b/plotly/offline/offline.py @@ -45,7 +45,7 @@ def download_plotlyjs(download_url): def get_plotlyjs(): - path = os.path.join('offline', 'plotly.min.js') + path = os.path.join('package_data', 'plotly.min.js') plotlyjs = resource_string('plotly', path).decode('utf-8') return plotlyjs diff --git a/plotly/widgets/graphWidget.js b/plotly/package_data/graphWidget.js similarity index 100% rename from plotly/widgets/graphWidget.js rename to plotly/package_data/graphWidget.js diff --git a/plotly/offline/plotly.min.js b/plotly/package_data/plotly.min.js similarity index 100% rename from plotly/offline/plotly.min.js rename to plotly/package_data/plotly.min.js diff --git a/plotly/widgets/graph_widget.py b/plotly/widgets/graph_widget.py index ea400d95717..a359474a7d0 100644 --- a/plotly/widgets/graph_widget.py +++ b/plotly/widgets/graph_widget.py @@ -21,7 +21,7 @@ # No officially recommended way to do this in any other way # http://mail.scipy.org/pipermail/ipython-dev/2014-April/013835.html js_widget_code = resource_string('plotly', - 'widgets/graphWidget.js').decode('utf-8') + 'package_data/graphWidget.js').decode('utf-8') display(Javascript(js_widget_code)) diff --git a/setup.py b/setup.py index 5bf9464f394..d533529f700 100644 --- a/setup.py +++ b/setup.py @@ -40,8 +40,6 @@ def readme(): 'plotly/matplotlylib', 'plotly/matplotlylib/mplexporter', 'plotly/matplotlylib/mplexporter/renderers'], - package_data={ - 'plotly': ['package_data/*.json', 'widgets/*.js', 'offline/*.js'] - }, + package_data={'plotly': ['package_data/*']}, install_requires=['requests', 'six', 'pytz'], zip_safe=False) From e4e452cc8ab5abc575e4d8e18a2bbb85c57025b3 Mon Sep 17 00:00:00 2001 From: Andrew Seier Date: Wed, 4 Jan 2017 20:39:03 -0800 Subject: [PATCH 5/6] =?UTF-8?q?Version=20bump=20(no-auto-load-schema)=20?= =?UTF-8?q?=E2=80=94>=201.13.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 8 +++++++- plotly/version.py | 2 +- setup.py | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6fefd84707..b8d15791e7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,16 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## [Unreleased] +## [1.13.0] +### Added +- Python 3.5 has been added as a tested environment for this package. + ### Updated - `plotly.plotly.create_animations` and `plotly.plotly.icreate_animations` now return appropriate error messages if the response is not successful. +### Changed +- The plot-schema from `https://api.plot.ly/plot-schema` is no longer updated on import. + ## [1.12.12] - 2016-12-06 ### Updated - Updated `plotly.min.js` to version 1.20.5 for `plotly.offline`. diff --git a/plotly/version.py b/plotly/version.py index f89b5cce884..84c54b74824 100644 --- a/plotly/version.py +++ b/plotly/version.py @@ -1 +1 @@ -__version__ = '1.12.12' +__version__ = '1.13.0' diff --git a/setup.py b/setup.py index d533529f700..9c50bcbf475 100644 --- a/setup.py +++ b/setup.py @@ -24,9 +24,9 @@ def readme(): 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Topic :: Scientific/Engineering :: Visualization', ], license='MIT', From 33754a7fc8020d428a994ec8e15f5411b4a87245 Mon Sep 17 00:00:00 2001 From: Andrew Seier Date: Thu, 5 Jan 2017 09:42:15 -0800 Subject: [PATCH 6/6] Fix makefile syntax errors. I think this got introduced during some rebases ;__;. --- makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/makefile b/makefile index 455b9582246..ca75f11480c 100644 --- a/makefile +++ b/makefile @@ -18,9 +18,9 @@ setup_subs : update_default_schema : @echo "Making sure the default-schema.json file is up to date" - python -c "import requests;/ + python -c "import requests;\ from requests.compat import json as _json;\ - response = requests.get('https://api.plot.ly/v2/plot-schema?sha1';/ + response = requests.get('https://api.plot.ly/v2/plot-schema?sha1');\ f = open('plotly/package_data/default-schema.json', 'w');\ _json.dump(response.json()['schema'], f, indent=4,\ sort_keys=True, separators=(',', ': '));\