From 93e334b371e0d49ae0525c9a9e5d0321c0314535 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Fri, 20 Nov 2015 15:17:03 -0500 Subject: [PATCH 01/12] Add tables and annotated heatmaps to FigureFactory - add FF option for annotated heatmaps with logical text colouring - users can supply a matrix of text for annotations otherwise the value from the z matrix is used - add FF option to create a table (defaults to striped table) - input options are a z matrix or a pandas data frame - option for user to supply table colours and font colours --- plotly/tools.py | 321 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 317 insertions(+), 4 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index f26dc7d3e43..8131ff068eb 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -49,6 +49,12 @@ def warning_on_one_line(message, category, filename, lineno, except ImportError: _numpy_imported = False +try: + import pandas as pd + _pandas_imported = True +except ImportError: + _pandas_imported = False + try: import scipy as scp _scipy_imported = True @@ -1428,10 +1434,12 @@ class FigureFactory(object): without notice. Supported chart types include candlestick, open high low close, quiver, - and streamline. See FigureFactory.create_candlestick, - FigureFactory.create_ohlc, FigureFactory.create_quiver, or - FigureFactory.create_streamline for for more infomation and examples of a - specific chart type. + streamline, distplot, dendrogram, annotated heatmap, and tables. See + FigureFactory.create_candlestick, FigureFactory.create_ohlc, + FigureFactory.create_quiver, FigureFactory.create_streamline, + FigureFactory.create_distplot, FigureFactory.create_dendrogram, + FigureFactory.create_annotated_heatmap, or FigureFactory.create_table for + more infomation and examples of a specific chart type. """ @staticmethod @@ -1585,6 +1593,13 @@ def _flatten(array): "flattened! Make sure your data is " "entered as lists or ndarrays!") + @staticmethod + def _hex_to_rgb(value): + value = value.lstrip('#') + lv = len(value) + return tuple(int(value[i:i + lv // 3], 16) + for i in range(0, lv, lv // 3)) + @staticmethod def create_quiver(x, y, u, v, scale=.1, arrow_scale=.3, angle=math.pi / 9, **kwargs): @@ -2509,6 +2524,107 @@ def create_dendrogram(X, orientation="bottom", labels=None, return {'layout': dendrogram.layout, 'data': dendrogram.data} + @staticmethod + def create_annotated_heatmap(z, x=None, y=None, text=None, + fontcolor=None, showscale=False, + **kwargs): + """ + BETA function that creates annotated heatmaps + + The distplot can be composed of all or any combination of the following + 3 components: (1) histogram, (2) curve: (a) kernal density estimation + or (b) normal curve, and (3) rug plot. Additionally, multiple distplots + (from multiple datasets) can be created in the same plot. + + :param (list[list]) z: z matrix to create heatmap. + :param (list) x: x labels. Default = None + :param (list) y: y labels. Default = None + :param (list[list]) text: Text for annotations. Should be the same + dimmensions as the z matrix. If no text is added, the the values of + the z matrix are annotated. Default = None + :param (list) font_color: Add histogram to distplot? Default = True + :param (bool) showscale: Display colorscale. Default = False + :param kwargs: kwargs passed through plotly.graph_objs.Heatmap. + These kwargs describe other attributes about the annotated Heatmap + trace such as the colorscale. For more information on valid kwargs + call help(plotly.graph_objs.Heatmap) + + Example 1: Simple distplot of 1 data set + ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + ``` + """ + # TODO: protected until #282 + from plotly.graph_objs import graph_objs + # FigureFactory._validate_annotated_heatmap() + annotations = _AnnotatedHeatmap(z, x, y, text, fontcolor, + **kwargs).make_annotations() + + if x and y: + trace = dict(type='heatmap', + z=z, + x=x, + y=y, + showscale=showscale, + **kwargs) + else: + trace = dict(type='heatmap', + z=z, + showscale=showscale, + **kwargs) + + data = [trace] + layout = dict(annotations=annotations, + xaxis=dict(ticks='', side='top', + gridcolor='rgb(0, 0, 0)'), + yaxis=dict(ticks='', ticksuffix=' ')) + return graph_objs.Figure(data=data, layout=layout) + + @staticmethod + def create_table(z, index=False, + colorscale=[[0, 'purple'], [.5, 'grey'], [1, 'white']], + fontcolor=['#000000'], line_color=[], index_title='', + height_constant=30, showscale=False, + **kwargs): + """ + BETA function that creates Plotly tables + """ + # TODO: protected until #282 + from plotly.graph_objs import graph_objs + # FigureFactory._validate_annotated_heatmap() + table_matrix = _Table(z, index, colorscale, fontcolor, index_title, + **kwargs).get_table_matrix() + annotations = _Table(z, index, colorscale, fontcolor, index_title, + **kwargs).make_table_annotations() + + trace = dict(type='heatmap', + z=table_matrix, + opacity=.7, + colorscale=colorscale, + showscale=showscale, + **kwargs) + + data = [trace] + layout = dict(annotations=annotations, + height=len(table_matrix)*height_constant + 200, + yaxis=dict(autorange='reversed', + zeroline=False, + gridcolor=line_color, + gridwidth=2, + ticks='', + dtick=1, + tick0=.5, + showticklabels=False), + xaxis=dict(zeroline=False, + gridcolor=line_color, + gridwidth=2, + ticks='', + dtick=1, + tick0=-0.5, + showticklabels=False)) + return graph_objs.Figure(data=data, layout=layout) + class _Quiver(FigureFactory): """ @@ -3442,3 +3558,200 @@ def get_dendrogram_traces(self, X, colorscale): trace_list.append(trace) return trace_list, icoord, dcoord, ordered_labels, P['leaves'] + + +class _AnnotatedHeatmap(FigureFactory): + """ + Refer to TraceFactory.create_annotated_heatmap() for docstring + """ + def __init__(self, z, x=[], y=[], text=[], + fontcolor=[], colorscale=[], + reversescale=False, **kwargs): + from plotly.graph_objs import graph_objs + self.z = z + if x: + self.x = x + else: + self.x = range(len(z[0])) + if y: + self.y = y + else: + self.y = range(len(z)) + if text: + self.text = text + else: + self.text = self.z + if colorscale: + self.colorscale = colorscale + else: + self.colorscale = 'RdBu' + self.reversescale = reversescale + self.fontcolor = fontcolor + + def get_text_color(self): + ''' + Get font color for annotations. + + The annotated heatmap can feature 2 text colors: min_color_text and max_color_text + If no The user can define the text color for heatmap values >= + (max_value - min_value)/2 and for heatmap values < + (max_value - min_value)/2 Depending on the colorscale of the heatmap the text will be either + :rtype (string, string) min_color_text, max_color_text: text + color for the annotations + ''' + colorscales = ['Greys', 'Greens', 'Blues', + 'YIGnBu', 'YIOrRd', 'RdBu', + 'Picnic', 'Jet', 'Hot', 'Blackbody', + 'Earth', 'Electric', 'Viridis'] + colorscales_opp = ['Reds'] + if self.fontcolor: + min_color_text = self.fontcolor[0] + max_color_text = self.fontcolor[-1] + elif self.colorscale in colorscales and self.reversescale: + min_color_text = '#000000' + max_color_text = '#FFFFFF' + elif self.colorscale in colorscales: + min_color_text = '#FFFFFF' + max_color_text = '#000000' + elif self.colorscale in colorscales_opp and self.reversescale: + min_color_text = '#FFFFFF' + max_color_text = '#000000' + elif self.colorscale in colorscales_opp: + min_color_text = '#000000' + max_color_text = '#FFFFFF' + elif isinstance(self.colorscale, list): + if 'rgb' in self.colorscale[0][1]: + min_col = map(int, + self.colorscale[0][1].strip('rgb()').split(',')) + max_col = map(int, + self.colorscale[-1][1].strip('rgb()').split(',')) + elif '#' in self.colorscale[0][1]: + min_col = FigureFactory._hex_to_rgb(self.colorscale[0][1]) + max_col = FigureFactory._hex_to_rgb(self.colorscale[-1][1]) + else: + min_col = [255, 255, 255] + max_col = [255, 255, 255] + + if (min_col[0]*0.299 + min_col[1]*0.587 + min_col[2]*0.114) > 186: + min_color_text = '#000000' + else: + min_color_text = '#FFFFFF' + if (max_col[0]*0.299 + max_col[1]*0.587 + max_col[2]*0.114) > 186: + max_color_text = '#000000' + else: + max_color_text = '#FFFFFF' + else: + min_color_text = '#000000' + max_color_text = '#000000' + return min_color_text, max_color_text + + def make_annotations(self): + ''' + Generate annotations + + :rtype (dict) annotations: + ''' + from plotly.graph_objs import graph_objs + min_color_text, max_color_text = _AnnotatedHeatmap.get_text_color(self) + annotations = [] + for n, row in enumerate(self.text): + for m, val in enumerate(row): + annotations.append( + graph_objs.Annotation( + text=str(val), + x=self.x[m], + y=self.y[n], + xref='x1', + yref='y1', + font=dict(color=max_color_text if val > + max(max(self.z)) / 2 else min_color_text), + showarrow=False)) + return annotations + + +class _Table(FigureFactory): + """ + Refer to TraceFactory.create_annotated_heatmap() for docstring + """ + def __init__(self, z, index, colorscale, + fontcolor, index_title='', + **kwargs): + from plotly.graph_objs import graph_objs + if _pandas_imported and isinstance(z, pd.DataFrame): + headers = z.columns.tolist() + z_index = z.index.tolist() + z = z.values.tolist() + z.insert(0, headers) + if index: + z_index.insert(0, index_title) + for i in range(len(z)): + z[i].insert(0, z_index[i]) + self.z = z + self.x = range(len(z[0])) + self.y = range(len(z)) + if colorscale: + self.colorscale = colorscale + else: + self.colorscale = 'RdBu' + self.fontcolor = fontcolor + self.index = index + + def get_table_matrix(self): + ''' + Create + + :rtype (list[list] table_matrix): + ''' + header = [0] * len(self.z[0]) + odd_row = [.5] * len(self.z[0]) + even_row = [1] * len(self.z[0]) + table_matrix = [None] * len(self.z) + table_matrix[0] = header + for i in range(1, len(self.z), 2): + table_matrix[i] = odd_row + for i in range(2, len(self.z), 2): + table_matrix[i] = even_row + if self.index: + for array in table_matrix: + array[0] = 0 + return table_matrix + + def get_table_fontcolor(self): + ''' + Make fontcolor array if a fontcolor array is not supplied. + ''' + if len(self.fontcolor) == 1: + self.fontcolor *= len(self.z) + elif len(self.fontcolor) == 3: + all_font_color = range(len(self.z)) + all_font_color[0] = self.fontcolor[0] + for i in range(1, len(self.z), 2): + all_font_color[i] = self.fontcolor[1] + for i in range(2, len(self.z), 2): + all_font_color[i] = self.fontcolor[2] + self.fontcolor = all_font_color + + def make_table_annotations(self): + ''' + Generates annotations... + ''' + from plotly.graph_objs import graph_objs + table_matrix = _Table.get_table_matrix(self) + _Table.get_table_fontcolor(self) + annotations = [] + for n, row in enumerate(self.z): + for m, val in enumerate(row): + annotations.append( + graph_objs.Annotation( + text=('' + str(val) + '' if n < 1 or + self.index and m < 1 else str(val)), + x=self.x[m] - .45, + y=self.y[n], + xref='x1', + yref='y1', + align="left", + xanchor="left", + font=dict(color=self.fontcolor[n]), + showarrow=False)) + return annotations + From 990041aa216c58305e5cdaf33a8dd1404cc201a4 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Tue, 24 Nov 2015 20:03:16 -0500 Subject: [PATCH 02/12] add validations and tests - add validation functions and tests for annotated_heatmap and tables - add docstrings for all functions - fix annotated_heatmap annotations so the font color logic is followed when a text matrix is supplied. --- .../test_tools/test_figure_factory.py | 299 ++++++++++++++++++ plotly/tools.py | 279 ++++++++++------ 2 files changed, 478 insertions(+), 100 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index 3616eb1e9cf..7b3c585a723 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -686,6 +686,305 @@ def test_datetime_candlestick(self): self.assertEqual(candle, exp_candle) +class TestAnnotatedHeatmap(TestCase): + + def test_unequal_z_text_size(self): + + # check: PlotlyError if z and text are not the same dimmensions + + kwargs = {'z': [[1, 2], [1, 2]], 'text': [[1, 2, 3], [1, 2]]} + self.assertRaises(PlotlyError, + tls.FigureFactory.create_annotated_heatmap, + **kwargs) + + kwargs = {'z': [[1, 2], [1, 2]], 'text': [[1, 2], [1, 2], [1, 2]]} + self.assertRaises(PlotlyError, + tls.FigureFactory.create_annotated_heatmap, + **kwargs) + + def test_simple_annotated_heatmap(self): + + # we should be able to create a heatmap with annotated values with a + # logical text color + + z = [[1, 0, .5], [.25, .75, .45]] + a_heat = tls.FigureFactory.create_annotated_heatmap(z) + expected_a_heat = { + 'data': [{'showscale': False, + 'type': 'heatmap', + 'z': [[1, 0, 0.5], [0.25, 0.75, 0.45]]}], + 'layout': {'annotations': [{'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '1', + 'x': 0, + 'xref': 'x1', + 'y': 0, + 'yref': 'y1'}, + {'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': '0', + 'x': 1, + 'xref': 'x1', + 'y': 0, + 'yref': 'y1'}, + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '0.5', + 'x': 2, + 'xref': 'x1', + 'y': 0, + 'yref': 'y1'}, + {'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': '0.25', + 'x': 0, + 'xref': 'x1', + 'y': 1, + 'yref': 'y1'}, + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '0.75', + 'x': 1, + 'xref': 'x1', + 'y': 1, + 'yref': 'y1'}, + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '0.45', + 'x': 2, + 'xref': 'x1', + 'y': 1, + 'yref': 'y1'}], + 'xaxis': {'gridcolor': 'rgb(0, 0, 0)', + 'showticklabels': False, + 'side': 'top', + 'ticks': ''}, + 'yaxis': {'showticklabels': False, 'ticks': '', + 'ticksuffix': ' '}}} + self.assertEqual(a_heat, expected_a_heat) + + def test_more_kwargs(self): + + # we should be able to create an annotated heatmap with x and y axes + # lables, a defined colorscale, and supplied text. + + z = [[1, 0], [.25, .75], [.45, .5]] + text = [['first', 'second'], ['third', 'fourth'], ['fifth', 'sixth']] + a = tls.FigureFactory.create_annotated_heatmap(z, + x=['A', 'B'], + y=['One', 'Two', + 'Three'], + text=text, + colorscale=[[0, + '#ffffff'], + [1, + '#e6005a']] + ) + expected_a = {'data': [{'colorscale': [[0, '#ffffff'], [1, '#e6005a']], + 'showscale': False, + 'type': 'heatmap', + 'x': ['A', 'B'], + 'y': ['One', 'Two', 'Three'], + 'z': [[1, 0], [0.25, 0.75], [0.45, 0.5]]}], + 'layout': {'annotations': [{'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': 'first', + 'x': 'A', + 'xref': 'x1', + 'y': 'One', + 'yref': 'y1'}, + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'second', + 'x': 'B', + 'xref': 'x1', + 'y': 'One', + 'yref': 'y1'}, + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'third', + 'x': 'A', + 'xref': 'x1', + 'y': 'Two', + 'yref': 'y1'}, + {'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': 'fourth', + 'x': 'B', + 'xref': 'x1', + 'y': 'Two', + 'yref': 'y1'}, + {'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': 'fifth', + 'x': 'A', + 'xref': 'x1', + 'y': 'Three', + 'yref': 'y1'}, + {'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': 'sixth', + 'x': 'B', + 'xref': 'x1', + 'y': 'Three', + 'yref': 'y1'}], + 'xaxis': {'gridcolor': 'rgb(0, 0, 0)', + 'side': 'top', 'ticks': ''}, + 'yaxis': {'ticks': '', 'ticksuffix': ' '}}} + self.assertEqual(a, expected_a) + + +class TestTable(TestCase): + + def test_fontcolor_input(self): + + # check: PlotlyError if fontcolor input is incorrect + + kwargs = {'text': [['one', 'two'], [1, 2], [1, 2], [1, 2]], + 'fontcolor': '#000000'} + self.assertRaises(PlotlyError, + tls.FigureFactory.create_table, **kwargs) + + kwargs = {'text': [['one', 'two'], [1, 2], [1, 2], [1, 2]], + 'fontcolor': ['red', 'blue']} + self.assertRaises(PlotlyError, + tls.FigureFactory.create_table, **kwargs) + + def test_simple_table(self): + + # we should be able to create a striped table by suppling a text matrix + + text = [['Country', 'Year', 'Population'], ['US', 2000, 282200000], + ['Canada', 2000, 27790000], ['US', 1980, 226500000]] + table = tls.FigureFactory.create_table(text) + expected_table = {'data': [{'colorscale': [[0, '#66b2ff'], [0.5, '#e6e6e6'], [1, '#ffffff']], + 'opacity': 0.7, + 'showscale': False, + 'type': 'heatmap', + 'z': [[0, 0, 0], [0.5, 0.5, 0.5], [1, 1, 1], [0.5, 0.5, 0.5]]}], + 'layout': {'annotations': [{'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'Country', + 'x': -0.45, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 0, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'Year', + 'x': 0.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 0, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'Population', + 'x': 1.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 0, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'US', + 'x': -0.45, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 1, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '2000', + 'x': 0.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 1, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '282200000', + 'x': 1.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 1, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'Canada', + 'x': -0.45, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 2, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '2000', + 'x': 0.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 2, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '27790000', + 'x': 1.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 2, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'US', + 'x': -0.45, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 3, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '1980', + 'x': 0.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 3, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '226500000', + 'x': 1.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 3, + 'yref': 'y1'}], + 'height': 320, + 'xaxis': {'dtick': 1, + 'gridwidth': 2, + 'showticklabels': False, + 'tick0': -0.5, + 'ticks': '', + 'zeroline': False}, + 'yaxis': {'autorange': 'reversed', + 'dtick': 1, + 'gridwidth': 2, + 'showticklabels': False, + 'tick0': 0.5, + 'ticks': '', + 'zeroline': False}}} + # class TestDistplot(TestCase): # def test_scipy_import_error(self): diff --git a/plotly/tools.py b/plotly/tools.py index 8131ff068eb..3b114bd4ed1 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1554,7 +1554,7 @@ def _validate_positive_scalars(**kwargs): @staticmethod def _validate_streamline(x, y): """ - streamline specific validations + Streamline specific validations Specifically, this checks that x and y are both evenly spaced, and that the package numpy is available. @@ -1577,6 +1577,43 @@ def _validate_streamline(x, y): raise exceptions.PlotlyError("y must be a 1 dimensional, " "evenly spaced array") + @staticmethod + def _validate_annotated_heatmap(z, text): + """ + Annotated heatmap specific validations + + Check that if a text matrix is supplied, it is the same + dimmensions as the z matrix. + + See FigureFactory.create_annotated_heatmap() for params + + :raises: (PlotlyError) If z and text matrices are not the same + dimmensions. + """ + if text: + FigureFactory._validate_equal_length(z, text) + for lst in range(len(z)): + if len(z[lst]) != len(text[lst]): + raise exceptions.PlotlyError("z and text should have the " + "same dimmensions") + + @staticmethod + def _validate_table(text, fontcolor): + """ + Table specific validations + + Check that the fontcolor is supplied correctly (1, 3, or len(text) + colors). + + :raises: (PlotlyError) If fontcolor is supplied incorretly. + + See FigureFactory.create_table() for params + """ + fontcolor_len_options = [1, 3, len(text)] + if len(fontcolor) not in fontcolor_len_options: + raise exceptions.PlotlyError("Oops, fontcolor should be a list of " + "length 1, 3 or len(text)") + @staticmethod def _flatten(array): """ @@ -2525,31 +2562,31 @@ def create_dendrogram(X, orientation="bottom", labels=None, 'data': dendrogram.data} @staticmethod - def create_annotated_heatmap(z, x=None, y=None, text=None, - fontcolor=None, showscale=False, - **kwargs): + def create_annotated_heatmap(z, x=None, y=None, text=[], fontcolor=[], + showscale=False, **kwargs): """ BETA function that creates annotated heatmaps - The distplot can be composed of all or any combination of the following - 3 components: (1) histogram, (2) curve: (a) kernal density estimation - or (b) normal curve, and (3) rug plot. Additionally, multiple distplots - (from multiple datasets) can be created in the same plot. + This functions adds annotations to each cell of the heatmap. :param (list[list]) z: z matrix to create heatmap. - :param (list) x: x labels. Default = None - :param (list) y: y labels. Default = None - :param (list[list]) text: Text for annotations. Should be the same - dimmensions as the z matrix. If no text is added, the the values of - the z matrix are annotated. Default = None - :param (list) font_color: Add histogram to distplot? Default = True + :param (list) x: x axis labels. + :param (list) y: y axis labels. + :param (list[list]) text: Text strings for annotations. Should be the + same dimmensions as the z matrix. If no text is added, the the + values of the z matrix are annotated. Default = z matrix values. + :param (list) fontcolor: List of two colorstrings: [min_text_color, + max_text_color] where min_text_color is applied to annotations for + heatmap values < (max_value - min_value)/2. If fontcolor is not + defined the colors are defined logically as black or white + depending on the heatmap's colorscale. :param (bool) showscale: Display colorscale. Default = False :param kwargs: kwargs passed through plotly.graph_objs.Heatmap. These kwargs describe other attributes about the annotated Heatmap trace such as the colorscale. For more information on valid kwargs call help(plotly.graph_objs.Heatmap) - Example 1: Simple distplot of 1 data set + Example 1: Simple annotated heatmap with default configuration ``` import plotly.plotly as py from plotly.tools import FigureFactory as FF @@ -2557,52 +2594,82 @@ def create_annotated_heatmap(z, x=None, y=None, text=None, """ # TODO: protected until #282 from plotly.graph_objs import graph_objs - # FigureFactory._validate_annotated_heatmap() + FigureFactory._validate_annotated_heatmap(z, text) annotations = _AnnotatedHeatmap(z, x, y, text, fontcolor, **kwargs).make_annotations() - if x and y: + if x or y: trace = dict(type='heatmap', z=z, x=x, y=y, showscale=showscale, **kwargs) + layout = dict(annotations=annotations, + xaxis=dict(ticks='', side='top', + gridcolor='rgb(0, 0, 0)'), + yaxis=dict(ticks='', ticksuffix=' ')) else: trace = dict(type='heatmap', z=z, showscale=showscale, **kwargs) + layout = dict(annotations=annotations, + xaxis=dict(ticks='', side='top', + gridcolor='rgb(0, 0, 0)', + showticklabels=False), + yaxis=dict(ticks='', ticksuffix=' ', + showticklabels=False)) data = [trace] - layout = dict(annotations=annotations, - xaxis=dict(ticks='', side='top', - gridcolor='rgb(0, 0, 0)'), - yaxis=dict(ticks='', ticksuffix=' ')) + return graph_objs.Figure(data=data, layout=layout) @staticmethod - def create_table(z, index=False, - colorscale=[[0, 'purple'], [.5, 'grey'], [1, 'white']], - fontcolor=['#000000'], line_color=[], index_title='', - height_constant=30, showscale=False, - **kwargs): - """ - BETA function that creates Plotly tables + def create_table(text, colorscale=[[0, '#66b2ff'], [.5, '#e6e6e6'], + [1, '#ffffff']], fontcolor=['#000000'], + height_constant=30, index=False, index_title='', + hoverinfo='none', **kwargs): + """ + BETA function that creates data tables + + :param (pandas dataframe | list[list]) text: data for table. + :param (string|list) colorscale: Colorscale for table where the color + at value 0 is the header color, .5 is the first table color and 1 + is the second table color. (Set .5 and 1 to avoid the striped table + effect). Default=[[0, '#66b2ff'], [.5, '#d9d9d9'], [1, '#ffffff']] + :param (list) fontcolor: Color for fonts in table. Can be a single + color, three colors, or a color for each row in the table. + Default=['#000000'] (black text for the entire table) + :param (int) height_constant: Constant multiplied by # of rows to + create table height. Default=30. + :param (bool) index: Create (header-colored) index column index from + Pandas dataframe or list[0] for each list in text. Default=False. + :param (string) index_title: Title for index column. Default=''. + :param kwargs: kwargs passed through plotly.graph_objs.Heatmap. + These kwargs describe other attributes about the annotated Heatmap + trace such as the colorscale. For more information on valid kwargs + call help(plotly.graph_objs.Heatmap) + + Example 1: Simple annotated heatmap with default configuration + ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + ``` """ # TODO: protected until #282 from plotly.graph_objs import graph_objs - # FigureFactory._validate_annotated_heatmap() - table_matrix = _Table(z, index, colorscale, fontcolor, index_title, + FigureFactory._validate_table(text, fontcolor) + table_matrix = _Table(text, colorscale, fontcolor, index, index_title, **kwargs).get_table_matrix() - annotations = _Table(z, index, colorscale, fontcolor, index_title, + annotations = _Table(text, colorscale, fontcolor, index, index_title, **kwargs).make_table_annotations() trace = dict(type='heatmap', z=table_matrix, opacity=.7, colorscale=colorscale, - showscale=showscale, + showscale=False, **kwargs) data = [trace] @@ -2610,14 +2677,12 @@ def create_table(z, index=False, height=len(table_matrix)*height_constant + 200, yaxis=dict(autorange='reversed', zeroline=False, - gridcolor=line_color, gridwidth=2, ticks='', dtick=1, tick0=.5, showticklabels=False), xaxis=dict(zeroline=False, - gridcolor=line_color, gridwidth=2, ticks='', dtick=1, @@ -3043,6 +3108,7 @@ def sum_streamlines(self): streamline_y = sum(self.st_y, []) return streamline_x, streamline_y + class _OHLC(FigureFactory): """ Refer to FigureFactory.create_ohlc_increase() for docstring. @@ -3592,12 +3658,16 @@ def get_text_color(self): ''' Get font color for annotations. - The annotated heatmap can feature 2 text colors: min_color_text and max_color_text - If no The user can define the text color for heatmap values >= - (max_value - min_value)/2 and for heatmap values < - (max_value - min_value)/2 Depending on the colorscale of the heatmap the text will be either - :rtype (string, string) min_color_text, max_color_text: text - color for the annotations + The annotated heatmap can feature 2 text colors: min_text_color and + max_text_color. The min_text_color is applied to annotations for + heatmap values < (max_value - min_value)/2. The user can define these + two colors otherwise the colors are defined logically as black or white + depending on the heatmap's colorscale. + + :rtype (string, string) min_text_color, max_text_color: text + color for annotations for heatmap values < + (max_value - min_value)/2 and text color for annotations for + heatmap values >= (max_value - min_value)/2 ''' colorscales = ['Greys', 'Greens', 'Blues', 'YIGnBu', 'YIOrRd', 'RdBu', @@ -3605,20 +3675,20 @@ def get_text_color(self): 'Earth', 'Electric', 'Viridis'] colorscales_opp = ['Reds'] if self.fontcolor: - min_color_text = self.fontcolor[0] - max_color_text = self.fontcolor[-1] + min_text_color = self.fontcolor[0] + max_text_color = self.fontcolor[-1] elif self.colorscale in colorscales and self.reversescale: - min_color_text = '#000000' - max_color_text = '#FFFFFF' + min_text_color = '#000000' + max_text_color = '#FFFFFF' elif self.colorscale in colorscales: - min_color_text = '#FFFFFF' - max_color_text = '#000000' + min_text_color = '#FFFFFF' + max_text_color = '#000000' elif self.colorscale in colorscales_opp and self.reversescale: - min_color_text = '#FFFFFF' - max_color_text = '#000000' + min_text_color = '#FFFFFF' + max_text_color = '#000000' elif self.colorscale in colorscales_opp: - min_color_text = '#000000' - max_color_text = '#FFFFFF' + min_text_color = '#000000' + max_text_color = '#FFFFFF' elif isinstance(self.colorscale, list): if 'rgb' in self.colorscale[0][1]: min_col = map(int, @@ -3633,83 +3703,82 @@ def get_text_color(self): max_col = [255, 255, 255] if (min_col[0]*0.299 + min_col[1]*0.587 + min_col[2]*0.114) > 186: - min_color_text = '#000000' + min_text_color = '#000000' else: - min_color_text = '#FFFFFF' + min_text_color = '#FFFFFF' if (max_col[0]*0.299 + max_col[1]*0.587 + max_col[2]*0.114) > 186: - max_color_text = '#000000' + max_text_color = '#000000' else: - max_color_text = '#FFFFFF' + max_text_color = '#FFFFFF' else: - min_color_text = '#000000' - max_color_text = '#000000' - return min_color_text, max_color_text + min_text_color = '#000000' + max_text_color = '#000000' + return min_text_color, max_text_color def make_annotations(self): ''' - Generate annotations + Get annotations for each cell of the heatmap with graph_objs.Annotation - :rtype (dict) annotations: + :rtype (list) annotations: list of annotations for each cell of the + heatmap ''' from plotly.graph_objs import graph_objs - min_color_text, max_color_text = _AnnotatedHeatmap.get_text_color(self) + min_text_color, max_text_color = _AnnotatedHeatmap.get_text_color(self) annotations = [] - for n, row in enumerate(self.text): + for n, row in enumerate(self.z): for m, val in enumerate(row): annotations.append( graph_objs.Annotation( - text=str(val), + text=str(self.text[n][m]), x=self.x[m], y=self.y[n], xref='x1', yref='y1', - font=dict(color=max_color_text if val > - max(max(self.z)) / 2 else min_color_text), + font=dict(color=min_text_color if val < + (max(max(self.z))-min(min(self.z))) / 2 + else max_text_color), showarrow=False)) return annotations class _Table(FigureFactory): """ - Refer to TraceFactory.create_annotated_heatmap() for docstring + Refer to TraceFactory.create_table() for docstring """ - def __init__(self, z, index, colorscale, - fontcolor, index_title='', + def __init__(self, text, colorscale, fontcolor, index, index_title='', **kwargs): from plotly.graph_objs import graph_objs - if _pandas_imported and isinstance(z, pd.DataFrame): - headers = z.columns.tolist() - z_index = z.index.tolist() - z = z.values.tolist() - z.insert(0, headers) + if _pandas_imported and isinstance(text, pd.DataFrame): + headers = text.columns.tolist() + text_index = text.index.tolist() + text = text.values.tolist() + text.insert(0, headers) if index: - z_index.insert(0, index_title) - for i in range(len(z)): - z[i].insert(0, z_index[i]) - self.z = z - self.x = range(len(z[0])) - self.y = range(len(z)) - if colorscale: - self.colorscale = colorscale - else: - self.colorscale = 'RdBu' + text_index.insert(0, index_title) + for i in range(len(text)): + text[i].insert(0, text_index[i]) + self.text = text + self.colorscale = colorscale self.fontcolor = fontcolor self.index = index + self.x = range(len(text[0])) + self.y = range(len(text)) def get_table_matrix(self): ''' - Create + Create z matrix to make heatmap with striped table coloring - :rtype (list[list] table_matrix): + :rtype (list[list]) table_matrix: z matrix to make heatmap with striped + table coloring. ''' - header = [0] * len(self.z[0]) - odd_row = [.5] * len(self.z[0]) - even_row = [1] * len(self.z[0]) - table_matrix = [None] * len(self.z) + header = [0] * len(self.text[0]) + odd_row = [.5] * len(self.text[0]) + even_row = [1] * len(self.text[0]) + table_matrix = [None] * len(self.text) table_matrix[0] = header - for i in range(1, len(self.z), 2): + for i in range(1, len(self.text), 2): table_matrix[i] = odd_row - for i in range(2, len(self.z), 2): + for i in range(2, len(self.text), 2): table_matrix[i] = even_row if self.index: for array in table_matrix: @@ -3718,28 +3787,39 @@ def get_table_matrix(self): def get_table_fontcolor(self): ''' - Make fontcolor array if a fontcolor array is not supplied. + Fill fontcolor array. + + Table text color can vary by row so this extends a single color or + creates an array to set a header color and two alternating colors to + create the striped table pattern. + + :rtype (list) all_font_color: list of fontcolors for each row in table. ''' if len(self.fontcolor) == 1: - self.fontcolor *= len(self.z) + all_font_color = self.fontcolor*len(self.text) elif len(self.fontcolor) == 3: - all_font_color = range(len(self.z)) + all_font_color = range(len(self.text)) all_font_color[0] = self.fontcolor[0] - for i in range(1, len(self.z), 2): + for i in range(1, len(self.text), 2): all_font_color[i] = self.fontcolor[1] - for i in range(2, len(self.z), 2): + for i in range(2, len(self.text), 2): all_font_color[i] = self.fontcolor[2] - self.fontcolor = all_font_color + elif len(self.fontcolor) == len(self.text): + all_font_color = self.fontcolor + return all_font_color def make_table_annotations(self): ''' - Generates annotations... + Generate annotations to fill in table text + + :rtype (list) annotations: list of annotations for each cell of the + table. ''' from plotly.graph_objs import graph_objs table_matrix = _Table.get_table_matrix(self) - _Table.get_table_fontcolor(self) + all_font_color = _Table.get_table_fontcolor(self) annotations = [] - for n, row in enumerate(self.z): + for n, row in enumerate(self.text): for m, val in enumerate(row): annotations.append( graph_objs.Annotation( @@ -3751,7 +3831,6 @@ def make_table_annotations(self): yref='y1', align="left", xanchor="left", - font=dict(color=self.fontcolor[n]), showarrow=False)) return annotations From 84ade7ccecf3788c0fa5d99fcd273c2a3e846179 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Wed, 25 Nov 2015 10:35:28 -0500 Subject: [PATCH 03/12] add examples to docstring - doc examples - add hover text option for annotated heatmaps --- plotly/tools.py | 46 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index 3b114bd4ed1..3305eb19f9e 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -2562,8 +2562,8 @@ def create_dendrogram(X, orientation="bottom", labels=None, 'data': dendrogram.data} @staticmethod - def create_annotated_heatmap(z, x=None, y=None, text=[], fontcolor=[], - showscale=False, **kwargs): + def create_annotated_heatmap(z, x=None, y=None, text=[], hover_text=[], + fontcolor=[], showscale=False, **kwargs): """ BETA function that creates annotated heatmaps @@ -2590,6 +2590,14 @@ def create_annotated_heatmap(z, x=None, y=None, text=[], fontcolor=[], ``` import plotly.plotly as py from plotly.tools import FigureFactory as FF + + z = [[0.300000, 0.00000, 0.65, 0.300000], + [1, 0.100005, 0.45, 0.4300], + [0.300000, 0.00000, 0.65, 0.300000], + [1, 0.100005, 0.45, 0.00000]] + + hm = FF.create_annotated_heatmap(z) + py.iplot(hm) ``` """ # TODO: protected until #282 @@ -2603,6 +2611,7 @@ def create_annotated_heatmap(z, x=None, y=None, text=[], fontcolor=[], z=z, x=x, y=y, + text=hover_text, showscale=showscale, **kwargs) layout = dict(annotations=annotations, @@ -2612,6 +2621,8 @@ def create_annotated_heatmap(z, x=None, y=None, text=[], fontcolor=[], else: trace = dict(type='heatmap', z=z, + text=hover_text, + hoverinfo='text', showscale=showscale, **kwargs) layout = dict(annotations=annotations, @@ -2651,10 +2662,38 @@ def create_table(text, colorscale=[[0, '#66b2ff'], [.5, '#e6e6e6'], trace such as the colorscale. For more information on valid kwargs call help(plotly.graph_objs.Heatmap) - Example 1: Simple annotated heatmap with default configuration + Example 1: Simple Plotly Table ``` import plotly.plotly as py from plotly.tools import FigureFactory as FF + + text = [['Country', 'Year', 'Population'], + ['US', 2000, 282200000], + ['Canada', 2000, 27790000], + ['US', 2010, 309000000], + ['Canada', 2010, 34000000]] + + table=FF.create_table(text, + colorscale=[[0, '#000000'], + [.5, '#80beff'], + [1, '#cce5ff']], + fontcolor=['#ffffff', '#000000', + '#000000']) + py.iplot(table) + ``` + + Example 2: Simple Plotly Table with Pandas + ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + + import pandas as pd + + df = pd.read_csv('http://www.stat.ubc.ca/~jenny/notOcto/STAT545A/examples/gapminder/data/gapminderDataFiveYear.txt', sep='\t') + df_p = df[0:25] + + table_simple=FF.create_table(df_p) + py.iplot(table_simple) ``` """ # TODO: protected until #282 @@ -3831,6 +3870,7 @@ def make_table_annotations(self): yref='y1', align="left", xanchor="left", + font=dict(color=all_font_color[n]), showarrow=False)) return annotations From a6694087cc88e78565d6e75f9cc6b04fdb84d16d Mon Sep 17 00:00:00 2001 From: Chelsea Date: Wed, 25 Nov 2015 13:10:42 -0500 Subject: [PATCH 04/12] naming edits - changed `text` to `annotation_text` and `table_text` so the `text` argument can still be used for hover - cleaned up arg names and order --- plotly/tools.py | 117 ++++++++++++++++++++++++++---------------------- 1 file changed, 63 insertions(+), 54 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index 3305eb19f9e..4133eca18ec 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1578,7 +1578,7 @@ def _validate_streamline(x, y): "evenly spaced array") @staticmethod - def _validate_annotated_heatmap(z, text): + def _validate_annotated_heatmap(z, annotation_text): """ Annotated heatmap specific validations @@ -1590,10 +1590,10 @@ def _validate_annotated_heatmap(z, text): :raises: (PlotlyError) If z and text matrices are not the same dimmensions. """ - if text: - FigureFactory._validate_equal_length(z, text) + if annotation_text: + FigureFactory._validate_equal_length(z, annotation_text) for lst in range(len(z)): - if len(z[lst]) != len(text[lst]): + if len(z[lst]) != len(annotation_text[lst]): raise exceptions.PlotlyError("z and text should have the " "same dimmensions") @@ -2562,8 +2562,10 @@ def create_dendrogram(X, orientation="bottom", labels=None, 'data': dendrogram.data} @staticmethod - def create_annotated_heatmap(z, x=None, y=None, text=[], hover_text=[], - fontcolor=[], showscale=False, **kwargs): + def create_annotated_heatmap(z, x=None, y=None, annotation_text=None, + colorscale=None, fontcolor=None, + showscale=False, reversescale=False, + **kwargs): """ BETA function that creates annotated heatmaps @@ -2602,8 +2604,14 @@ def create_annotated_heatmap(z, x=None, y=None, text=[], hover_text=[], """ # TODO: protected until #282 from plotly.graph_objs import graph_objs - FigureFactory._validate_annotated_heatmap(z, text) - annotations = _AnnotatedHeatmap(z, x, y, text, fontcolor, + + # Avoiding mutables in the call signature + annotation_text = \ + annotation_text if annotation_text is not None else [] + fontcolor = fontcolor if fontcolor is not None else [] + FigureFactory._validate_annotated_heatmap(z, annotation_text) + annotations = _AnnotatedHeatmap(z, x, y, annotation_text, + colorscale, fontcolor, reversescale, **kwargs).make_annotations() if x or y: @@ -2611,7 +2619,7 @@ def create_annotated_heatmap(z, x=None, y=None, text=[], hover_text=[], z=z, x=x, y=y, - text=hover_text, + colorscale=colorscale, showscale=showscale, **kwargs) layout = dict(annotations=annotations, @@ -2621,8 +2629,7 @@ def create_annotated_heatmap(z, x=None, y=None, text=[], hover_text=[], else: trace = dict(type='heatmap', z=z, - text=hover_text, - hoverinfo='text', + colorscale=colorscale, showscale=showscale, **kwargs) layout = dict(annotations=annotations, @@ -2637,8 +2644,7 @@ def create_annotated_heatmap(z, x=None, y=None, text=[], hover_text=[], return graph_objs.Figure(data=data, layout=layout) @staticmethod - def create_table(text, colorscale=[[0, '#66b2ff'], [.5, '#e6e6e6'], - [1, '#ffffff']], fontcolor=['#000000'], + def create_table(table_text, colorscale=None, fontcolor=['#000000'], height_constant=30, index=False, index_title='', hoverinfo='none', **kwargs): """ @@ -2698,17 +2704,23 @@ def create_table(text, colorscale=[[0, '#66b2ff'], [.5, '#e6e6e6'], """ # TODO: protected until #282 from plotly.graph_objs import graph_objs - FigureFactory._validate_table(text, fontcolor) - table_matrix = _Table(text, colorscale, fontcolor, index, index_title, - **kwargs).get_table_matrix() - annotations = _Table(text, colorscale, fontcolor, index, index_title, - **kwargs).make_table_annotations() + + colorscale = \ + colorscale if colorscale is not None else [[0, '#66b2ff'], + [.5, '#e6e6e6'], + [1, '#ffffff']] + FigureFactory._validate_table(table_text, fontcolor) + table_matrix = _Table(table_text, colorscale, fontcolor, index, + index_title, **kwargs).get_table_matrix() + annotations = _Table(table_text, colorscale, fontcolor, index, + index_title, **kwargs).make_table_annotations() trace = dict(type='heatmap', z=table_matrix, opacity=.7, colorscale=colorscale, showscale=False, + hoverinfo=hoverinfo, **kwargs) data = [trace] @@ -3669,10 +3681,10 @@ class _AnnotatedHeatmap(FigureFactory): """ Refer to TraceFactory.create_annotated_heatmap() for docstring """ - def __init__(self, z, x=[], y=[], text=[], - fontcolor=[], colorscale=[], - reversescale=False, **kwargs): + def __init__(self, z, x, y, annotation_text, colorscale, + fontcolor, reversescale, **kwargs): from plotly.graph_objs import graph_objs + self.z = z if x: self.x = x @@ -3682,14 +3694,11 @@ def __init__(self, z, x=[], y=[], text=[], self.y = y else: self.y = range(len(z)) - if text: - self.text = text - else: - self.text = self.z - if colorscale: - self.colorscale = colorscale + if annotation_text: + self.annotation_text = annotation_text else: - self.colorscale = 'RdBu' + self.annotation_text = self.z + self.colorscale = colorscale if colorscale is not None else 'Reds' self.reversescale = reversescale self.fontcolor = fontcolor @@ -3768,7 +3777,7 @@ def make_annotations(self): for m, val in enumerate(row): annotations.append( graph_objs.Annotation( - text=str(self.text[n][m]), + text=str(self.annotation_text[n][m]), x=self.x[m], y=self.y[n], xref='x1', @@ -3784,24 +3793,24 @@ class _Table(FigureFactory): """ Refer to TraceFactory.create_table() for docstring """ - def __init__(self, text, colorscale, fontcolor, index, index_title='', - **kwargs): + def __init__(self, table_text, colorscale, fontcolor, index, + index_title='', **kwargs): from plotly.graph_objs import graph_objs - if _pandas_imported and isinstance(text, pd.DataFrame): - headers = text.columns.tolist() - text_index = text.index.tolist() - text = text.values.tolist() - text.insert(0, headers) + if _pandas_imported and isinstance(table_text, pd.DataFrame): + headers = table_text.columns.tolist() + table_text_index = table_text.index.tolist() + table_text = table_text.values.tolist() + table_text.insert(0, headers) if index: - text_index.insert(0, index_title) - for i in range(len(text)): - text[i].insert(0, text_index[i]) - self.text = text + table_text_index.insert(0, index_title) + for i in range(len(table_text)): + table_text[i].insert(0, table_text_index[i]) + self.table_text = table_text self.colorscale = colorscale self.fontcolor = fontcolor self.index = index - self.x = range(len(text[0])) - self.y = range(len(text)) + self.x = range(len(table_text[0])) + self.y = range(len(table_text)) def get_table_matrix(self): ''' @@ -3810,14 +3819,14 @@ def get_table_matrix(self): :rtype (list[list]) table_matrix: z matrix to make heatmap with striped table coloring. ''' - header = [0] * len(self.text[0]) - odd_row = [.5] * len(self.text[0]) - even_row = [1] * len(self.text[0]) - table_matrix = [None] * len(self.text) + header = [0] * len(self.table_text[0]) + odd_row = [.5] * len(self.table_text[0]) + even_row = [1] * len(self.table_text[0]) + table_matrix = [None] * len(self.table_text) table_matrix[0] = header - for i in range(1, len(self.text), 2): + for i in range(1, len(self.table_text), 2): table_matrix[i] = odd_row - for i in range(2, len(self.text), 2): + for i in range(2, len(self.table_text), 2): table_matrix[i] = even_row if self.index: for array in table_matrix: @@ -3835,15 +3844,15 @@ def get_table_fontcolor(self): :rtype (list) all_font_color: list of fontcolors for each row in table. ''' if len(self.fontcolor) == 1: - all_font_color = self.fontcolor*len(self.text) + all_font_color = self.fontcolor*len(self.table_text) elif len(self.fontcolor) == 3: - all_font_color = range(len(self.text)) + all_font_color = range(len(self.table_text)) all_font_color[0] = self.fontcolor[0] - for i in range(1, len(self.text), 2): + for i in range(1, len(self.table_text), 2): all_font_color[i] = self.fontcolor[1] - for i in range(2, len(self.text), 2): + for i in range(2, len(self.table_text), 2): all_font_color[i] = self.fontcolor[2] - elif len(self.fontcolor) == len(self.text): + elif len(self.fontcolor) == len(self.table_text): all_font_color = self.fontcolor return all_font_color @@ -3858,7 +3867,7 @@ def make_table_annotations(self): table_matrix = _Table.get_table_matrix(self) all_font_color = _Table.get_table_fontcolor(self) annotations = [] - for n, row in enumerate(self.text): + for n, row in enumerate(self.table_text): for m, val in enumerate(row): annotations.append( graph_objs.Annotation( From 52eebf1759516c97a1eea9cd5342c1038d57cc20 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Wed, 25 Nov 2015 13:25:39 -0500 Subject: [PATCH 05/12] update tests based on var name updates --- .../test_tools/test_figure_factory.py | 124 +++++++++--------- plotly/tools.py | 9 +- 2 files changed, 68 insertions(+), 65 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index 7b3c585a723..50fa5217be8 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -692,12 +692,12 @@ def test_unequal_z_text_size(self): # check: PlotlyError if z and text are not the same dimmensions - kwargs = {'z': [[1, 2], [1, 2]], 'text': [[1, 2, 3], [1, 2]]} + kwargs = {'z': [[1, 2], [1, 2]], 'annotation_text': [[1, 2, 3], [1]]} self.assertRaises(PlotlyError, tls.FigureFactory.create_annotated_heatmap, **kwargs) - kwargs = {'z': [[1, 2], [1, 2]], 'text': [[1, 2], [1, 2], [1, 2]]} + kwargs = {'z': [[1], [1]], 'annotation_text': [[1], [1], [1]]} self.assertRaises(PlotlyError, tls.FigureFactory.create_annotated_heatmap, **kwargs) @@ -710,45 +710,46 @@ def test_simple_annotated_heatmap(self): z = [[1, 0, .5], [.25, .75, .45]] a_heat = tls.FigureFactory.create_annotated_heatmap(z) expected_a_heat = { - 'data': [{'showscale': False, + 'data': [{'colorscale': None, + 'showscale': False, 'type': 'heatmap', 'z': [[1, 0, 0.5], [0.25, 0.75, 0.45]]}], - 'layout': {'annotations': [{'font': {'color': '#000000'}, + 'layout': {'annotations': [{'font': {'color': '#FFFFFF'}, 'showarrow': False, 'text': '1', 'x': 0, 'xref': 'x1', 'y': 0, 'yref': 'y1'}, - {'font': {'color': '#FFFFFF'}, + {'font': {'color': '#000000'}, 'showarrow': False, 'text': '0', 'x': 1, 'xref': 'x1', 'y': 0, 'yref': 'y1'}, - {'font': {'color': '#000000'}, + {'font': {'color': '#FFFFFF'}, 'showarrow': False, 'text': '0.5', 'x': 2, 'xref': 'x1', 'y': 0, 'yref': 'y1'}, - {'font': {'color': '#FFFFFF'}, + {'font': {'color': '#000000'}, 'showarrow': False, 'text': '0.25', 'x': 0, 'xref': 'x1', 'y': 1, 'yref': 'y1'}, - {'font': {'color': '#000000'}, + {'font': {'color': '#FFFFFF'}, 'showarrow': False, 'text': '0.75', 'x': 1, 'xref': 'x1', 'y': 1, 'yref': 'y1'}, - {'font': {'color': '#000000'}, + {'font': {'color': '#FFFFFF'}, 'showarrow': False, 'text': '0.45', 'x': 2, @@ -774,63 +775,62 @@ def test_more_kwargs(self): x=['A', 'B'], y=['One', 'Two', 'Three'], - text=text, + annotation_text=text, colorscale=[[0, '#ffffff'], [1, '#e6005a']] ) expected_a = {'data': [{'colorscale': [[0, '#ffffff'], [1, '#e6005a']], - 'showscale': False, - 'type': 'heatmap', - 'x': ['A', 'B'], - 'y': ['One', 'Two', 'Three'], - 'z': [[1, 0], [0.25, 0.75], [0.45, 0.5]]}], - 'layout': {'annotations': [{'font': {'color': '#FFFFFF'}, - 'showarrow': False, - 'text': 'first', - 'x': 'A', - 'xref': 'x1', - 'y': 'One', - 'yref': 'y1'}, - {'font': {'color': '#000000'}, - 'showarrow': False, - 'text': 'second', - 'x': 'B', - 'xref': 'x1', - 'y': 'One', - 'yref': 'y1'}, - {'font': {'color': '#000000'}, - 'showarrow': False, - 'text': 'third', - 'x': 'A', - 'xref': 'x1', - 'y': 'Two', - 'yref': 'y1'}, - {'font': {'color': '#FFFFFF'}, - 'showarrow': False, - 'text': 'fourth', - 'x': 'B', - 'xref': 'x1', - 'y': 'Two', - 'yref': 'y1'}, - {'font': {'color': '#FFFFFF'}, - 'showarrow': False, - 'text': 'fifth', - 'x': 'A', - 'xref': 'x1', - 'y': 'Three', - 'yref': 'y1'}, - {'font': {'color': '#FFFFFF'}, - 'showarrow': False, - 'text': 'sixth', - 'x': 'B', - 'xref': 'x1', - 'y': 'Three', - 'yref': 'y1'}], - 'xaxis': {'gridcolor': 'rgb(0, 0, 0)', - 'side': 'top', 'ticks': ''}, - 'yaxis': {'ticks': '', 'ticksuffix': ' '}}} + 'showscale': False, + 'type': 'heatmap', + 'x': ['A', 'B'], + 'y': ['One', 'Two', 'Three'], + 'z': [[1, 0], [0.25, 0.75], [0.45, 0.5]]}], + 'layout': {'annotations': [{'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': 'first', + 'x': 'A', + 'xref': 'x1', + 'y': 'One', + 'yref': 'y1'}, + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'second', + 'x': 'B', + 'xref': 'x1', + 'y': 'One', + 'yref': 'y1'}, + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'third', + 'x': 'A', + 'xref': 'x1', + 'y': 'Two', + 'yref': 'y1'}, + {'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': 'fourth', + 'x': 'B', + 'xref': 'x1', + 'y': 'Two', + 'yref': 'y1'}, + {'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': 'fifth', + 'x': 'A', + 'xref': 'x1', + 'y': 'Three', + 'yref': 'y1'}, + {'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': 'sixth', + 'x': 'B', + 'xref': 'x1', + 'y': 'Three', + 'yref': 'y1'}], + 'xaxis': {'gridcolor': 'rgb(0, 0, 0)', 'side': 'top', 'ticks': ''}, + 'yaxis': {'ticks': '', 'ticksuffix': ' '}}} self.assertEqual(a, expected_a) @@ -840,12 +840,12 @@ def test_fontcolor_input(self): # check: PlotlyError if fontcolor input is incorrect - kwargs = {'text': [['one', 'two'], [1, 2], [1, 2], [1, 2]], + kwargs = {'table_text': [['one', 'two'], [1, 2], [1, 2], [1, 2]], 'fontcolor': '#000000'} self.assertRaises(PlotlyError, tls.FigureFactory.create_table, **kwargs) - kwargs = {'text': [['one', 'two'], [1, 2], [1, 2], [1, 2]], + kwargs = {'table_text': [['one', 'two'], [1, 2], [1, 2], [1, 2]], 'fontcolor': ['red', 'blue']} self.assertRaises(PlotlyError, tls.FigureFactory.create_table, **kwargs) diff --git a/plotly/tools.py b/plotly/tools.py index 4133eca18ec..f375a8cb2f2 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1598,7 +1598,7 @@ def _validate_annotated_heatmap(z, annotation_text): "same dimmensions") @staticmethod - def _validate_table(text, fontcolor): + def _validate_table(table_text, fontcolor): """ Table specific validations @@ -1609,7 +1609,7 @@ def _validate_table(text, fontcolor): See FigureFactory.create_table() for params """ - fontcolor_len_options = [1, 3, len(text)] + fontcolor_len_options = [1, 3, len(table_text)] if len(fontcolor) not in fontcolor_len_options: raise exceptions.PlotlyError("Oops, fontcolor should be a list of " "length 1, 3 or len(text)") @@ -2644,7 +2644,7 @@ def create_annotated_heatmap(z, x=None, y=None, annotation_text=None, return graph_objs.Figure(data=data, layout=layout) @staticmethod - def create_table(table_text, colorscale=None, fontcolor=['#000000'], + def create_table(table_text, colorscale=None, fontcolor=None, height_constant=30, index=False, index_title='', hoverinfo='none', **kwargs): """ @@ -2705,10 +2705,13 @@ def create_table(table_text, colorscale=None, fontcolor=['#000000'], # TODO: protected until #282 from plotly.graph_objs import graph_objs + # Avoiding mutables in the call signature colorscale = \ colorscale if colorscale is not None else [[0, '#66b2ff'], [.5, '#e6e6e6'], [1, '#ffffff']] + fontcolor = fontcolor if fontcolor is not None else ['#000000'] + FigureFactory._validate_table(table_text, fontcolor) table_matrix = _Table(table_text, colorscale, fontcolor, index, index_title, **kwargs).get_table_matrix() From d40621ae2189521d69f5b0ffb046b5ae884ad188 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Wed, 25 Nov 2015 13:59:05 -0500 Subject: [PATCH 06/12] edit table margin and height formula - remove margins from tables --- plotly/tools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plotly/tools.py b/plotly/tools.py index f375a8cb2f2..09612e45e32 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -2728,7 +2728,8 @@ def create_table(table_text, colorscale=None, fontcolor=None, data = [trace] layout = dict(annotations=annotations, - height=len(table_matrix)*height_constant + 200, + height=len(table_matrix)*height_constant + 50, + margin=dict(t=0, b=0, r=0, l=0), yaxis=dict(autorange='reversed', zeroline=False, gridwidth=2, From 27f8efa177b46cdbd3e7e91b2cd69584097a9f69 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Wed, 25 Nov 2015 17:38:58 -0500 Subject: [PATCH 07/12] edits from review - add numpy compatibility - change default table colors - fix for average calculation happening in `make_annotations` - update tests to reflect changes - break up example 1 in table docstring To-Do [ ] update table test to reflect new colours [ ] update table fontcolor logic so `if index` the column font color matches header font color --- .../test_tools/test_figure_factory.py | 379 +++++++++--------- plotly/tools.py | 52 ++- 2 files changed, 231 insertions(+), 200 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index 50fa5217be8..eca9dfbaa76 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -710,18 +710,18 @@ def test_simple_annotated_heatmap(self): z = [[1, 0, .5], [.25, .75, .45]] a_heat = tls.FigureFactory.create_annotated_heatmap(z) expected_a_heat = { - 'data': [{'colorscale': None, + 'data': [{'colorscale': 'RdBu', 'showscale': False, 'type': 'heatmap', 'z': [[1, 0, 0.5], [0.25, 0.75, 0.45]]}], - 'layout': {'annotations': [{'font': {'color': '#FFFFFF'}, + 'layout': {'annotations': [{'font': {'color': '#000000'}, 'showarrow': False, 'text': '1', 'x': 0, 'xref': 'x1', 'y': 0, 'yref': 'y1'}, - {'font': {'color': '#000000'}, + {'font': {'color': '#FFFFFF'}, 'showarrow': False, 'text': '0', 'x': 1, @@ -735,14 +735,14 @@ def test_simple_annotated_heatmap(self): 'xref': 'x1', 'y': 0, 'yref': 'y1'}, - {'font': {'color': '#000000'}, + {'font': {'color': '#FFFFFF'}, 'showarrow': False, 'text': '0.25', 'x': 0, 'xref': 'x1', 'y': 1, 'yref': 'y1'}, - {'font': {'color': '#FFFFFF'}, + {'font': {'color': '#000000'}, 'showarrow': False, 'text': '0.75', 'x': 1, @@ -764,7 +764,7 @@ def test_simple_annotated_heatmap(self): 'ticksuffix': ' '}}} self.assertEqual(a_heat, expected_a_heat) - def test_more_kwargs(self): + def test_annotated_heatmap_kwargs(self): # we should be able to create an annotated heatmap with x and y axes # lables, a defined colorscale, and supplied text. @@ -782,55 +782,56 @@ def test_more_kwargs(self): '#e6005a']] ) expected_a = {'data': [{'colorscale': [[0, '#ffffff'], [1, '#e6005a']], - 'showscale': False, - 'type': 'heatmap', - 'x': ['A', 'B'], - 'y': ['One', 'Two', 'Three'], - 'z': [[1, 0], [0.25, 0.75], [0.45, 0.5]]}], - 'layout': {'annotations': [{'font': {'color': '#FFFFFF'}, - 'showarrow': False, - 'text': 'first', - 'x': 'A', - 'xref': 'x1', - 'y': 'One', - 'yref': 'y1'}, - {'font': {'color': '#000000'}, - 'showarrow': False, - 'text': 'second', - 'x': 'B', - 'xref': 'x1', - 'y': 'One', - 'yref': 'y1'}, - {'font': {'color': '#000000'}, - 'showarrow': False, - 'text': 'third', - 'x': 'A', - 'xref': 'x1', - 'y': 'Two', - 'yref': 'y1'}, - {'font': {'color': '#FFFFFF'}, - 'showarrow': False, - 'text': 'fourth', - 'x': 'B', - 'xref': 'x1', - 'y': 'Two', - 'yref': 'y1'}, - {'font': {'color': '#FFFFFF'}, - 'showarrow': False, - 'text': 'fifth', - 'x': 'A', - 'xref': 'x1', - 'y': 'Three', - 'yref': 'y1'}, - {'font': {'color': '#FFFFFF'}, - 'showarrow': False, - 'text': 'sixth', - 'x': 'B', - 'xref': 'x1', - 'y': 'Three', - 'yref': 'y1'}], - 'xaxis': {'gridcolor': 'rgb(0, 0, 0)', 'side': 'top', 'ticks': ''}, - 'yaxis': {'ticks': '', 'ticksuffix': ' '}}} + 'showscale': False, + 'type': 'heatmap', + 'x': ['A', 'B'], + 'y': ['One', 'Two', 'Three'], + 'z': [[1, 0], [0.25, 0.75], [0.45, 0.5]]}], + 'layout': {'annotations': [{'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': 'first', + 'x': 'A', + 'xref': 'x1', + 'y': 'One', + 'yref': 'y1'}, + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'second', + 'x': 'B', + 'xref': 'x1', + 'y': 'One', + 'yref': 'y1'}, + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'third', + 'x': 'A', + 'xref': 'x1', + 'y': 'Two', + 'yref': 'y1'}, + {'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': 'fourth', + 'x': 'B', + 'xref': 'x1', + 'y': 'Two', + 'yref': 'y1'}, + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'fifth', + 'x': 'A', + 'xref': 'x1', + 'y': 'Three', + 'yref': 'y1'}, + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'sixth', + 'x': 'B', + 'xref': 'x1', + 'y': 'Three', + 'yref': 'y1'}], + 'xaxis': {'gridcolor': 'rgb(0, 0, 0)', 'side': + 'top', 'ticks': ''}, + 'yaxis': {'ticks': '', 'ticksuffix': ' '}}} self.assertEqual(a, expected_a) @@ -850,140 +851,140 @@ def test_fontcolor_input(self): self.assertRaises(PlotlyError, tls.FigureFactory.create_table, **kwargs) - def test_simple_table(self): - - # we should be able to create a striped table by suppling a text matrix - - text = [['Country', 'Year', 'Population'], ['US', 2000, 282200000], - ['Canada', 2000, 27790000], ['US', 1980, 226500000]] - table = tls.FigureFactory.create_table(text) - expected_table = {'data': [{'colorscale': [[0, '#66b2ff'], [0.5, '#e6e6e6'], [1, '#ffffff']], - 'opacity': 0.7, - 'showscale': False, - 'type': 'heatmap', - 'z': [[0, 0, 0], [0.5, 0.5, 0.5], [1, 1, 1], [0.5, 0.5, 0.5]]}], - 'layout': {'annotations': [{'align': 'left', - 'font': {'color': '#000000'}, - 'showarrow': False, - 'text': 'Country', - 'x': -0.45, - 'xanchor': 'left', - 'xref': 'x1', - 'y': 0, - 'yref': 'y1'}, - {'align': 'left', - 'font': {'color': '#000000'}, - 'showarrow': False, - 'text': 'Year', - 'x': 0.55, - 'xanchor': 'left', - 'xref': 'x1', - 'y': 0, - 'yref': 'y1'}, - {'align': 'left', - 'font': {'color': '#000000'}, - 'showarrow': False, - 'text': 'Population', - 'x': 1.55, - 'xanchor': 'left', - 'xref': 'x1', - 'y': 0, - 'yref': 'y1'}, - {'align': 'left', - 'font': {'color': '#000000'}, - 'showarrow': False, - 'text': 'US', - 'x': -0.45, - 'xanchor': 'left', - 'xref': 'x1', - 'y': 1, - 'yref': 'y1'}, - {'align': 'left', - 'font': {'color': '#000000'}, - 'showarrow': False, - 'text': '2000', - 'x': 0.55, - 'xanchor': 'left', - 'xref': 'x1', - 'y': 1, - 'yref': 'y1'}, - {'align': 'left', - 'font': {'color': '#000000'}, - 'showarrow': False, - 'text': '282200000', - 'x': 1.55, - 'xanchor': 'left', - 'xref': 'x1', - 'y': 1, - 'yref': 'y1'}, - {'align': 'left', - 'font': {'color': '#000000'}, - 'showarrow': False, - 'text': 'Canada', - 'x': -0.45, - 'xanchor': 'left', - 'xref': 'x1', - 'y': 2, - 'yref': 'y1'}, - {'align': 'left', - 'font': {'color': '#000000'}, - 'showarrow': False, - 'text': '2000', - 'x': 0.55, - 'xanchor': 'left', - 'xref': 'x1', - 'y': 2, - 'yref': 'y1'}, - {'align': 'left', - 'font': {'color': '#000000'}, - 'showarrow': False, - 'text': '27790000', - 'x': 1.55, - 'xanchor': 'left', - 'xref': 'x1', - 'y': 2, - 'yref': 'y1'}, - {'align': 'left', - 'font': {'color': '#000000'}, - 'showarrow': False, - 'text': 'US', - 'x': -0.45, - 'xanchor': 'left', - 'xref': 'x1', - 'y': 3, - 'yref': 'y1'}, - {'align': 'left', - 'font': {'color': '#000000'}, - 'showarrow': False, - 'text': '1980', - 'x': 0.55, - 'xanchor': 'left', - 'xref': 'x1', - 'y': 3, - 'yref': 'y1'}, - {'align': 'left', - 'font': {'color': '#000000'}, - 'showarrow': False, - 'text': '226500000', - 'x': 1.55, - 'xanchor': 'left', - 'xref': 'x1', - 'y': 3, - 'yref': 'y1'}], - 'height': 320, - 'xaxis': {'dtick': 1, - 'gridwidth': 2, - 'showticklabels': False, - 'tick0': -0.5, - 'ticks': '', - 'zeroline': False}, - 'yaxis': {'autorange': 'reversed', - 'dtick': 1, - 'gridwidth': 2, - 'showticklabels': False, - 'tick0': 0.5, - 'ticks': '', - 'zeroline': False}}} + # def test_simple_table(self): + + # # we should be able to create a striped table by suppling a text matrix + + # text = [['Country', 'Year', 'Population'], ['US', 2000, 282200000], + # ['Canada', 2000, 27790000], ['US', 1980, 226500000]] + # table = tls.FigureFactory.create_table(text) + # expected_table = {'data': [{'colorscale': [[0, '#66b2ff'], [0.5, '#e6e6e6'], [1, '#ffffff']], + # 'opacity': 0.7, + # 'showscale': False, + # 'type': 'heatmap', + # 'z': [[0, 0, 0], [0.5, 0.5, 0.5], [1, 1, 1], [0.5, 0.5, 0.5]]}], + # 'layout': {'annotations': [{'align': 'left', + # 'font': {'color': '#000000'}, + # 'showarrow': False, + # 'text': 'Country', + # 'x': -0.45, + # 'xanchor': 'left', + # 'xref': 'x1', + # 'y': 0, + # 'yref': 'y1'}, + # {'align': 'left', + # 'font': {'color': '#000000'}, + # 'showarrow': False, + # 'text': 'Year', + # 'x': 0.55, + # 'xanchor': 'left', + # 'xref': 'x1', + # 'y': 0, + # 'yref': 'y1'}, + # {'align': 'left', + # 'font': {'color': '#000000'}, + # 'showarrow': False, + # 'text': 'Population', + # 'x': 1.55, + # 'xanchor': 'left', + # 'xref': 'x1', + # 'y': 0, + # 'yref': 'y1'}, + # {'align': 'left', + # 'font': {'color': '#000000'}, + # 'showarrow': False, + # 'text': 'US', + # 'x': -0.45, + # 'xanchor': 'left', + # 'xref': 'x1', + # 'y': 1, + # 'yref': 'y1'}, + # {'align': 'left', + # 'font': {'color': '#000000'}, + # 'showarrow': False, + # 'text': '2000', + # 'x': 0.55, + # 'xanchor': 'left', + # 'xref': 'x1', + # 'y': 1, + # 'yref': 'y1'}, + # {'align': 'left', + # 'font': {'color': '#000000'}, + # 'showarrow': False, + # 'text': '282200000', + # 'x': 1.55, + # 'xanchor': 'left', + # 'xref': 'x1', + # 'y': 1, + # 'yref': 'y1'}, + # {'align': 'left', + # 'font': {'color': '#000000'}, + # 'showarrow': False, + # 'text': 'Canada', + # 'x': -0.45, + # 'xanchor': 'left', + # 'xref': 'x1', + # 'y': 2, + # 'yref': 'y1'}, + # {'align': 'left', + # 'font': {'color': '#000000'}, + # 'showarrow': False, + # 'text': '2000', + # 'x': 0.55, + # 'xanchor': 'left', + # 'xref': 'x1', + # 'y': 2, + # 'yref': 'y1'}, + # {'align': 'left', + # 'font': {'color': '#000000'}, + # 'showarrow': False, + # 'text': '27790000', + # 'x': 1.55, + # 'xanchor': 'left', + # 'xref': 'x1', + # 'y': 2, + # 'yref': 'y1'}, + # {'align': 'left', + # 'font': {'color': '#000000'}, + # 'showarrow': False, + # 'text': 'US', + # 'x': -0.45, + # 'xanchor': 'left', + # 'xref': 'x1', + # 'y': 3, + # 'yref': 'y1'}, + # {'align': 'left', + # 'font': {'color': '#000000'}, + # 'showarrow': False, + # 'text': '1980', + # 'x': 0.55, + # 'xanchor': 'left', + # 'xref': 'x1', + # 'y': 3, + # 'yref': 'y1'}, + # {'align': 'left', + # 'font': {'color': '#000000'}, + # 'showarrow': False, + # 'text': '226500000', + # 'x': 1.55, + # 'xanchor': 'left', + # 'xref': 'x1', + # 'y': 3, + # 'yref': 'y1'}], + # 'height': 320, + # 'xaxis': {'dtick': 1, + # 'gridwidth': 2, + # 'showticklabels': False, + # 'tick0': -0.5, + # 'ticks': '', + # 'zeroline': False}, + # 'yaxis': {'autorange': 'reversed', + # 'dtick': 1, + # 'gridwidth': 2, + # 'showticklabels': False, + # 'tick0': 0.5, + # 'ticks': '', + # 'zeroline': False}}} # class TestDistplot(TestCase): diff --git a/plotly/tools.py b/plotly/tools.py index 09612e45e32..1ec2c44f2d9 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1590,7 +1590,7 @@ def _validate_annotated_heatmap(z, annotation_text): :raises: (PlotlyError) If z and text matrices are not the same dimmensions. """ - if annotation_text: + if annotation_text is not None and isinstance(annotation_text, list): FigureFactory._validate_equal_length(z, annotation_text) for lst in range(len(z)): if len(z[lst]) != len(annotation_text[lst]): @@ -2606,8 +2606,7 @@ def create_annotated_heatmap(z, x=None, y=None, annotation_text=None, from plotly.graph_objs import graph_objs # Avoiding mutables in the call signature - annotation_text = \ - annotation_text if annotation_text is not None else [] + colorscale = colorscale if colorscale is not None else 'RdBu' fontcolor = fontcolor if fontcolor is not None else [] FigureFactory._validate_annotated_heatmap(z, annotation_text) annotations = _AnnotatedHeatmap(z, x, y, annotation_text, @@ -2673,6 +2672,21 @@ def create_table(table_text, colorscale=None, fontcolor=None, import plotly.plotly as py from plotly.tools import FigureFactory as FF + text = [['Country', 'Year', 'Population'], + ['US', 2000, 282200000], + ['Canada', 2000, 27790000], + ['US', 2010, 309000000], + ['Canada', 2010, 34000000]] + + table=FF.create_table(text) + py.iplot(table) + ``` + + Example 2: Table with Custom Coloring + ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + text = [['Country', 'Year', 'Population'], ['US', 2000, 282200000], ['Canada', 2000, 27790000], @@ -2687,8 +2701,7 @@ def create_table(table_text, colorscale=None, fontcolor=None, '#000000']) py.iplot(table) ``` - - Example 2: Simple Plotly Table with Pandas + Example 3: Simple Plotly Table with Pandas ``` import plotly.plotly as py from plotly.tools import FigureFactory as FF @@ -2707,10 +2720,12 @@ def create_table(table_text, colorscale=None, fontcolor=None, # Avoiding mutables in the call signature colorscale = \ - colorscale if colorscale is not None else [[0, '#66b2ff'], - [.5, '#e6e6e6'], + colorscale if colorscale is not None else [[0, '#3D4E6F'], + [.5, '#F5F5F5'], [1, '#ffffff']] - fontcolor = fontcolor if fontcolor is not None else ['#000000'] + fontcolor = fontcolor if fontcolor is not None else ['#ffffff', + '#000000', + '#000000'] FigureFactory._validate_table(table_text, fontcolor) table_matrix = _Table(table_text, colorscale, fontcolor, index, @@ -3698,11 +3713,11 @@ def __init__(self, z, x, y, annotation_text, colorscale, self.y = y else: self.y = range(len(z)) - if annotation_text: + if annotation_text is not None: self.annotation_text = annotation_text else: self.annotation_text = self.z - self.colorscale = colorscale if colorscale is not None else 'Reds' + self.colorscale = colorscale self.reversescale = reversescale self.fontcolor = fontcolor @@ -3767,6 +3782,20 @@ def get_text_color(self): max_text_color = '#000000' return min_text_color, max_text_color + def get_min_max(self): + ''' + Get min and max vals of z matrix + + :rtype (float, float) z_min, z_max: min and max vals from z matrix + ''' + if _numpy_imported and isinstance(self.z, np.ndarray): + z_min = np.amin(self.z) + z_max = np.amax(self.z) + else: + z_min = min(min(self.z)) + z_max = max(max(self.z)) + return z_min, z_max + def make_annotations(self): ''' Get annotations for each cell of the heatmap with graph_objs.Annotation @@ -3776,6 +3805,7 @@ def make_annotations(self): ''' from plotly.graph_objs import graph_objs min_text_color, max_text_color = _AnnotatedHeatmap.get_text_color(self) + z_min, z_max = _AnnotatedHeatmap.get_min_max(self) annotations = [] for n, row in enumerate(self.z): for m, val in enumerate(row): @@ -3787,7 +3817,7 @@ def make_annotations(self): xref='x1', yref='y1', font=dict(color=min_text_color if val < - (max(max(self.z))-min(min(self.z))) / 2 + (z_max+z_min) / 2 else max_text_color), showarrow=False)) return annotations From af5e0e543c70ba595ad397a140ab38e50b80f20d Mon Sep 17 00:00:00 2001 From: Chelsea Date: Thu, 26 Nov 2015 12:55:52 -0500 Subject: [PATCH 08/12] updates from review - add validation for `x` and `y` in `annotated_heatmap` - correct/improve docstrings --- .../test_tools/test_figure_factory.py | 2 +- plotly/tools.py | 313 +++++++++--------- 2 files changed, 165 insertions(+), 150 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index eca9dfbaa76..cdf048d93b9 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -690,7 +690,7 @@ class TestAnnotatedHeatmap(TestCase): def test_unequal_z_text_size(self): - # check: PlotlyError if z and text are not the same dimmensions + # check: PlotlyError if z and text are not the same dimensions kwargs = {'z': [[1, 2], [1, 2]], 'annotation_text': [[1, 2, 3], [1]]} self.assertRaises(PlotlyError, diff --git a/plotly/tools.py b/plotly/tools.py index 1ec2c44f2d9..62a01da3dfa 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1439,7 +1439,7 @@ class FigureFactory(object): FigureFactory.create_quiver, FigureFactory.create_streamline, FigureFactory.create_distplot, FigureFactory.create_dendrogram, FigureFactory.create_annotated_heatmap, or FigureFactory.create_table for - more infomation and examples of a specific chart type. + more information and examples of a specific chart type. """ @staticmethod @@ -1500,7 +1500,7 @@ def _validate_ohlc(open, high, low, close, direction, **kwargs): @staticmethod def _validate_distplot(hist_data, curve_type): """ - distplot specific validations + Distplot-specific validations :raises: (PlotlyError) If hist_data is not a list of lists :raises: (PlotlyError) If curve_type is not valid (i.e. not 'kde' or @@ -1554,7 +1554,7 @@ def _validate_positive_scalars(**kwargs): @staticmethod def _validate_streamline(x, y): """ - Streamline specific validations + Streamline-specific validations Specifically, this checks that x and y are both evenly spaced, and that the package numpy is available. @@ -1578,41 +1578,53 @@ def _validate_streamline(x, y): "evenly spaced array") @staticmethod - def _validate_annotated_heatmap(z, annotation_text): + def _validate_annotated_heatmap(z, x, y, annotation_text): """ - Annotated heatmap specific validations + Annotated-heatmap-specific validations - Check that if a text matrix is supplied, it is the same - dimmensions as the z matrix. + Check that if a text matrix is supplied, it has the same + dimensions as the z matrix. See FigureFactory.create_annotated_heatmap() for params - :raises: (PlotlyError) If z and text matrices are not the same - dimmensions. + :raises: (PlotlyError) If z and text matrices do not have the same + dimensions. """ if annotation_text is not None and isinstance(annotation_text, list): FigureFactory._validate_equal_length(z, annotation_text) for lst in range(len(z)): if len(z[lst]) != len(annotation_text[lst]): raise exceptions.PlotlyError("z and text should have the " - "same dimmensions") + "same dimensions") + + if x: + if len(x) != len(z[0]): + raise exceptions.PlotlyError("oops, the x list that you " + "provided does not match the " + "width of your z matrix ") + + if y: + if len(y) != len(z): + raise exceptions.PlotlyError("oops, the y list that you " + "provided does not match the " + "length of your z matrix ") @staticmethod - def _validate_table(table_text, fontcolor): + def _validate_table(table_text, font_colors): """ - Table specific validations + Table-specific validations - Check that the fontcolor is supplied correctly (1, 3, or len(text) + Check that font_colors is supplied correctly (1, 3, or len(text) colors). - :raises: (PlotlyError) If fontcolor is supplied incorretly. + :raises: (PlotlyError) If font_colors is supplied incorretly. See FigureFactory.create_table() for params """ - fontcolor_len_options = [1, 3, len(table_text)] - if len(fontcolor) not in fontcolor_len_options: - raise exceptions.PlotlyError("Oops, fontcolor should be a list of " - "length 1, 3 or len(text)") + font_colors_len_options = [1, 3, len(table_text)] + if len(font_colors) not in font_colors_len_options: + raise exceptions.PlotlyError("Oops, font_colors should be a list " + "of length 1, 3 or len(text)") @staticmethod def _flatten(array): @@ -1632,10 +1644,18 @@ def _flatten(array): @staticmethod def _hex_to_rgb(value): - value = value.lstrip('#') - lv = len(value) - return tuple(int(value[i:i + lv // 3], 16) - for i in range(0, lv, lv // 3)) + """ + Calculates rgb values from a hex color code. + + :param (string) value: Hex color string + + :rtype (tuple) (r_value, g_value, b_value): tuple of rgb values + """ + hex_value = value.lstrip('#') + hex_total_length = len(value) + rgb_section_length = hex_total_length // 3 + return tuple(int(value[i:i + rgb_section_length], 16) + for i in range(0, hex_total_length, rgb_section_length)) @staticmethod def create_quiver(x, y, u, v, scale=.1, arrow_scale=.3, @@ -2563,24 +2583,26 @@ def create_dendrogram(X, orientation="bottom", labels=None, @staticmethod def create_annotated_heatmap(z, x=None, y=None, annotation_text=None, - colorscale=None, fontcolor=None, + colorscale='RdBu', font_colors=None, showscale=False, reversescale=False, **kwargs): """ BETA function that creates annotated heatmaps - This functions adds annotations to each cell of the heatmap. + This function adds annotations to each cell of the heatmap. - :param (list[list]) z: z matrix to create heatmap. + :param (list[list]|ndarray) z: z matrix to create heatmap. :param (list) x: x axis labels. :param (list) y: y axis labels. - :param (list[list]) text: Text strings for annotations. Should be the - same dimmensions as the z matrix. If no text is added, the the - values of the z matrix are annotated. Default = z matrix values. - :param (list) fontcolor: List of two colorstrings: [min_text_color, + :param (list[list]|ndarray) annotation_text: Text strings for + annotations. Should have the same dimensions as the z matrix. If no + text is added, the values of the z matrix are annotated. Default = + z matrix values. + :param (list|str) colorscale: heatmap colorscale. + :param (list) font_colors: List of two color strings: [min_text_color, max_text_color] where min_text_color is applied to annotations for - heatmap values < (max_value - min_value)/2. If fontcolor is not - defined the colors are defined logically as black or white + heatmap values < (max_value - min_value)/2. If font_colors is not + defined, the colors are defined logically as black or white depending on the heatmap's colorscale. :param (bool) showscale: Display colorscale. Default = False :param kwargs: kwargs passed through plotly.graph_objs.Heatmap. @@ -2593,44 +2615,35 @@ def create_annotated_heatmap(z, x=None, y=None, annotation_text=None, import plotly.plotly as py from plotly.tools import FigureFactory as FF - z = [[0.300000, 0.00000, 0.65, 0.300000], + z = [[0.300000, 0.00000, 0.65, 0.300000], [1, 0.100005, 0.45, 0.4300], [0.300000, 0.00000, 0.65, 0.300000], [1, 0.100005, 0.45, 0.00000]] - hm = FF.create_annotated_heatmap(z) - py.iplot(hm) + figure = FF.create_annotated_heatmap(z) + py.iplot(figure) ``` """ # TODO: protected until #282 from plotly.graph_objs import graph_objs # Avoiding mutables in the call signature - colorscale = colorscale if colorscale is not None else 'RdBu' - fontcolor = fontcolor if fontcolor is not None else [] - FigureFactory._validate_annotated_heatmap(z, annotation_text) + font_colors = font_colors if font_colors is not None else [] + FigureFactory._validate_annotated_heatmap(z, x, y, annotation_text) annotations = _AnnotatedHeatmap(z, x, y, annotation_text, - colorscale, fontcolor, reversescale, + colorscale, font_colors, reversescale, **kwargs).make_annotations() if x or y: - trace = dict(type='heatmap', - z=z, - x=x, - y=y, - colorscale=colorscale, - showscale=showscale, - **kwargs) + trace = dict(type='heatmap', z=z, x=x, y=y, colorscale=colorscale, + showscale=showscale, **kwargs) layout = dict(annotations=annotations, - xaxis=dict(ticks='', side='top', + xaxis=dict(ticks='', dtick=1, side='top', gridcolor='rgb(0, 0, 0)'), - yaxis=dict(ticks='', ticksuffix=' ')) + yaxis=dict(ticks='', dtick=1, ticksuffix=' ')) else: - trace = dict(type='heatmap', - z=z, - colorscale=colorscale, - showscale=showscale, - **kwargs) + trace = dict(type='heatmap', z=z, colorscale=colorscale, + showscale=showscale, **kwargs) layout = dict(annotations=annotations, xaxis=dict(ticks='', side='top', gridcolor='rgb(0, 0, 0)', @@ -2643,18 +2656,19 @@ def create_annotated_heatmap(z, x=None, y=None, annotation_text=None, return graph_objs.Figure(data=data, layout=layout) @staticmethod - def create_table(table_text, colorscale=None, fontcolor=None, - height_constant=30, index=False, index_title='', - hoverinfo='none', **kwargs): + def create_table(table_text, colorscale=None, font_colors=None, + index=False, index_title='', annotation_offset=.45, + height_constant=30, hoverinfo='none', **kwargs): """ BETA function that creates data tables - :param (pandas dataframe | list[list]) text: data for table. - :param (string|list) colorscale: Colorscale for table where the color - at value 0 is the header color, .5 is the first table color and 1 - is the second table color. (Set .5 and 1 to avoid the striped table - effect). Default=[[0, '#66b2ff'], [.5, '#d9d9d9'], [1, '#ffffff']] - :param (list) fontcolor: Color for fonts in table. Can be a single + :param (pandas.Dataframe | list[list]) text: data for table. + :param (str|list[list]) colorscale: Colorscale for table where the + color at value 0 is the header color, .5 is the first table color + and 1 is the second table color. (Set .5 and 1 to avoid the striped + table effect). Default=[[0, '#66b2ff'], [.5, '#d9d9d9'], + [1, '#ffffff']] + :param (list) font_colors: Color for fonts in table. Can be a single color, three colors, or a color for each row in the table. Default=['#000000'] (black text for the entire table) :param (int) height_constant: Constant multiplied by # of rows to @@ -2678,7 +2692,7 @@ def create_table(table_text, colorscale=None, fontcolor=None, ['US', 2010, 309000000], ['Canada', 2010, 34000000]] - table=FF.create_table(text) + table = FF.create_table(text) py.iplot(table) ``` @@ -2697,7 +2711,7 @@ def create_table(table_text, colorscale=None, fontcolor=None, colorscale=[[0, '#000000'], [.5, '#80beff'], [1, '#cce5ff']], - fontcolor=['#ffffff', '#000000', + font_colors=['#ffffff', '#000000', '#000000']) py.iplot(table) ``` @@ -2711,7 +2725,7 @@ def create_table(table_text, colorscale=None, fontcolor=None, df = pd.read_csv('http://www.stat.ubc.ca/~jenny/notOcto/STAT545A/examples/gapminder/data/gapminderDataFiveYear.txt', sep='\t') df_p = df[0:25] - table_simple=FF.create_table(df_p) + table_simple = FF.create_table(df_p) py.iplot(table_simple) ``` """ @@ -2720,44 +2734,34 @@ def create_table(table_text, colorscale=None, fontcolor=None, # Avoiding mutables in the call signature colorscale = \ - colorscale if colorscale is not None else [[0, '#3D4E6F'], - [.5, '#F5F5F5'], + colorscale if colorscale is not None else [[0, '#00083e'], + [.5, '#ededee'], [1, '#ffffff']] - fontcolor = fontcolor if fontcolor is not None else ['#ffffff', - '#000000', - '#000000'] - - FigureFactory._validate_table(table_text, fontcolor) - table_matrix = _Table(table_text, colorscale, fontcolor, index, - index_title, **kwargs).get_table_matrix() - annotations = _Table(table_text, colorscale, fontcolor, index, - index_title, **kwargs).make_table_annotations() - - trace = dict(type='heatmap', - z=table_matrix, - opacity=.7, - colorscale=colorscale, - showscale=False, - hoverinfo=hoverinfo, - **kwargs) + font_colors = font_colors if font_colors is not None else ['#ffffff', + '#000000', + '#000000'] + + FigureFactory._validate_table(table_text, font_colors) + table_matrix = _Table(table_text, colorscale, font_colors, index, + index_title, annotation_offset, + **kwargs).get_table_matrix() + annotations = _Table(table_text, colorscale, font_colors, index, + index_title, annotation_offset, + **kwargs).make_table_annotations() + + trace = dict(type='heatmap', z=table_matrix, opacity=.75, + colorscale=colorscale, showscale=False, + hoverinfo=hoverinfo, **kwargs) data = [trace] layout = dict(annotations=annotations, height=len(table_matrix)*height_constant + 50, margin=dict(t=0, b=0, r=0, l=0), - yaxis=dict(autorange='reversed', - zeroline=False, - gridwidth=2, - ticks='', - dtick=1, - tick0=.5, + yaxis=dict(autorange='reversed', zeroline=False, + gridwidth=2, ticks='', dtick=1, tick0=.5, showticklabels=False), - xaxis=dict(zeroline=False, - gridwidth=2, - ticks='', - dtick=1, - tick0=-0.5, - showticklabels=False)) + xaxis=dict(zeroline=False, gridwidth=2, ticks='', + dtick=1, tick0=-0.5, showticklabels=False)) return graph_objs.Figure(data=data, layout=layout) @@ -3701,7 +3705,7 @@ class _AnnotatedHeatmap(FigureFactory): Refer to TraceFactory.create_annotated_heatmap() for docstring """ def __init__(self, z, x, y, annotation_text, colorscale, - fontcolor, reversescale, **kwargs): + font_colors, reversescale, **kwargs): from plotly.graph_objs import graph_objs self.z = z @@ -3719,41 +3723,43 @@ def __init__(self, z, x, y, annotation_text, colorscale, self.annotation_text = self.z self.colorscale = colorscale self.reversescale = reversescale - self.fontcolor = fontcolor + self.font_colors = font_colors def get_text_color(self): - ''' + """ Get font color for annotations. - The annotated heatmap can feature 2 text colors: min_text_color and + The annotated heatmap can feature two text colors: min_text_color and max_text_color. The min_text_color is applied to annotations for heatmap values < (max_value - min_value)/2. The user can define these - two colors otherwise the colors are defined logically as black or white - depending on the heatmap's colorscale. + two colors. Otherwise the colors are defined logically as black or + white depending on the heatmap's colorscale. :rtype (string, string) min_text_color, max_text_color: text color for annotations for heatmap values < (max_value - min_value)/2 and text color for annotations for heatmap values >= (max_value - min_value)/2 - ''' + """ + # Plotly colorscales ranging from a lighter shade to a darker shade colorscales = ['Greys', 'Greens', 'Blues', 'YIGnBu', 'YIOrRd', 'RdBu', 'Picnic', 'Jet', 'Hot', 'Blackbody', 'Earth', 'Electric', 'Viridis'] - colorscales_opp = ['Reds'] - if self.fontcolor: - min_text_color = self.fontcolor[0] - max_text_color = self.fontcolor[-1] + # Plotly colorscales ranging from a darker shade to a lighter shade + colorscales_reverse = ['Reds'] + if self.font_colors: + min_text_color = self.font_colors[0] + max_text_color = self.font_colors[-1] elif self.colorscale in colorscales and self.reversescale: min_text_color = '#000000' max_text_color = '#FFFFFF' elif self.colorscale in colorscales: min_text_color = '#FFFFFF' max_text_color = '#000000' - elif self.colorscale in colorscales_opp and self.reversescale: + elif self.colorscale in colorscales_reverse and self.reversescale: min_text_color = '#FFFFFF' max_text_color = '#000000' - elif self.colorscale in colorscales_opp: + elif self.colorscale in colorscales_reverse: min_text_color = '#000000' max_text_color = '#FFFFFF' elif isinstance(self.colorscale, list): @@ -3782,33 +3788,35 @@ def get_text_color(self): max_text_color = '#000000' return min_text_color, max_text_color - def get_min_max(self): - ''' - Get min and max vals of z matrix + def get_z_mid(self): + """ + Get the mid value of z matrix - :rtype (float, float) z_min, z_max: min and max vals from z matrix - ''' + :rtype (float) z_avg: average val from z matrix + """ if _numpy_imported and isinstance(self.z, np.ndarray): z_min = np.amin(self.z) z_max = np.amax(self.z) else: z_min = min(min(self.z)) z_max = max(max(self.z)) - return z_min, z_max + z_mid = (z_max+z_min) / 2 + return z_mid def make_annotations(self): - ''' + """ Get annotations for each cell of the heatmap with graph_objs.Annotation - :rtype (list) annotations: list of annotations for each cell of the - heatmap - ''' + :rtype (list[dict]) annotations: list of annotations for each cell of + the heatmap + """ from plotly.graph_objs import graph_objs min_text_color, max_text_color = _AnnotatedHeatmap.get_text_color(self) - z_min, z_max = _AnnotatedHeatmap.get_min_max(self) + z_mid = _AnnotatedHeatmap.get_z_mid(self) annotations = [] for n, row in enumerate(self.z): for m, val in enumerate(row): + font_color = min_text_color if val < z_mid else max_text_color annotations.append( graph_objs.Annotation( text=str(self.annotation_text[n][m]), @@ -3816,9 +3824,7 @@ def make_annotations(self): y=self.y[n], xref='x1', yref='y1', - font=dict(color=min_text_color if val < - (z_max+z_min) / 2 - else max_text_color), + font=dict(color=font_color), showarrow=False)) return annotations @@ -3827,8 +3833,8 @@ class _Table(FigureFactory): """ Refer to TraceFactory.create_table() for docstring """ - def __init__(self, table_text, colorscale, fontcolor, index, - index_title='', **kwargs): + def __init__(self, table_text, colorscale, font_colors, index, + index_title, annotation_offset, **kwargs): from plotly.graph_objs import graph_objs if _pandas_imported and isinstance(table_text, pd.DataFrame): headers = table_text.columns.tolist() @@ -3841,18 +3847,19 @@ def __init__(self, table_text, colorscale, fontcolor, index, table_text[i].insert(0, table_text_index[i]) self.table_text = table_text self.colorscale = colorscale - self.fontcolor = fontcolor + self.font_colors = font_colors self.index = index + self.annotation_offset = annotation_offset self.x = range(len(table_text[0])) self.y = range(len(table_text)) def get_table_matrix(self): - ''' + """ Create z matrix to make heatmap with striped table coloring :rtype (list[list]) table_matrix: z matrix to make heatmap with striped table coloring. - ''' + """ header = [0] * len(self.table_text[0]) odd_row = [.5] * len(self.table_text[0]) even_row = [1] * len(self.table_text[0]) @@ -3867,53 +3874,61 @@ def get_table_matrix(self): array[0] = 0 return table_matrix - def get_table_fontcolor(self): - ''' - Fill fontcolor array. + def get_table_font_color(self): + """ + Fill font-color array. Table text color can vary by row so this extends a single color or creates an array to set a header color and two alternating colors to create the striped table pattern. - :rtype (list) all_font_color: list of fontcolors for each row in table. - ''' - if len(self.fontcolor) == 1: - all_font_color = self.fontcolor*len(self.table_text) - elif len(self.fontcolor) == 3: - all_font_color = range(len(self.table_text)) - all_font_color[0] = self.fontcolor[0] + :rtype (list[list]) all_font_colors: list of font colors for each row + in table. + """ + if len(self.font_colors) == 1: + all_font_colors = self.font_colors*len(self.table_text) + elif len(self.font_colors) == 3: + all_font_colors = range(len(self.table_text)) + all_font_colors[0] = self.font_colors[0] for i in range(1, len(self.table_text), 2): - all_font_color[i] = self.fontcolor[1] + all_font_colors[i] = self.font_colors[1] for i in range(2, len(self.table_text), 2): - all_font_color[i] = self.fontcolor[2] - elif len(self.fontcolor) == len(self.table_text): - all_font_color = self.fontcolor - return all_font_color + all_font_colors[i] = self.font_colors[2] + elif len(self.font_colors) == len(self.table_text): + all_font_colors = self.font_colors + else: + all_font_colors = ['#000000']*len(self.table_text) + return all_font_colors def make_table_annotations(self): - ''' + """ Generate annotations to fill in table text :rtype (list) annotations: list of annotations for each cell of the table. - ''' + """ from plotly.graph_objs import graph_objs table_matrix = _Table.get_table_matrix(self) - all_font_color = _Table.get_table_fontcolor(self) + all_font_colors = _Table.get_table_font_color(self) annotations = [] for n, row in enumerate(self.table_text): for m, val in enumerate(row): + # Bold text in header and index + format_text = ('' + str(val) + '' if n == 0 or + self.index and m < 1 else str(val)) + # Match font color of index to font color of header + font_color = (self.font_colors[0] if self.index and m == 0 + else all_font_colors[n]) annotations.append( graph_objs.Annotation( - text=('' + str(val) + '' if n < 1 or - self.index and m < 1 else str(val)), - x=self.x[m] - .45, + text=format_text, + x=self.x[m] - self.annotation_offset, y=self.y[n], xref='x1', yref='y1', align="left", xanchor="left", - font=dict(color=all_font_color[n]), + font=dict(color=font_color), showarrow=False)) return annotations From f5badba6164b81e30d56b8c8bd8f63644c174e6f Mon Sep 17 00:00:00 2001 From: Chelsea Date: Thu, 26 Nov 2015 14:04:24 -0500 Subject: [PATCH 09/12] update tests - uncomment and update table test - add test for `x` and `y` `annotated_heatmap` validation - (also fixed a pep8 mistake from review) --- .../test_tools/test_figure_factory.py | 488 +++++++++++------- plotly/tools.py | 4 +- 2 files changed, 316 insertions(+), 176 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index cdf048d93b9..32744650bcb 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -702,6 +702,24 @@ def test_unequal_z_text_size(self): tls.FigureFactory.create_annotated_heatmap, **kwargs) + def test_incorrect_x_size(self): + + # check: PlotlyError if x is the wrong size + + kwargs = {'z': [[1, 2], [1, 2]], 'x': ['A']} + self.assertRaises(PlotlyError, + tls.FigureFactory.create_annotated_heatmap, + **kwargs) + + def test_incorrect_y_size(self): + + # check: PlotlyError if y is the wrong size + + kwargs = {'z': [[1, 2], [1, 2]], 'y': [1, 2, 3]} + self.assertRaises(PlotlyError, + tls.FigureFactory.create_annotated_heatmap, + **kwargs) + def test_simple_annotated_heatmap(self): # we should be able to create a heatmap with annotated values with a @@ -771,8 +789,7 @@ def test_annotated_heatmap_kwargs(self): z = [[1, 0], [.25, .75], [.45, .5]] text = [['first', 'second'], ['third', 'fourth'], ['fifth', 'sixth']] - a = tls.FigureFactory.create_annotated_heatmap(z, - x=['A', 'B'], + a = tls.FigureFactory.create_annotated_heatmap(z, x=['A', 'B'], y=['One', 'Two', 'Three'], annotation_text=text, @@ -794,44 +811,47 @@ def test_annotated_heatmap_kwargs(self): 'xref': 'x1', 'y': 'One', 'yref': 'y1'}, - {'font': {'color': '#000000'}, - 'showarrow': False, - 'text': 'second', - 'x': 'B', - 'xref': 'x1', - 'y': 'One', - 'yref': 'y1'}, - {'font': {'color': '#000000'}, - 'showarrow': False, - 'text': 'third', - 'x': 'A', - 'xref': 'x1', - 'y': 'Two', - 'yref': 'y1'}, - {'font': {'color': '#FFFFFF'}, - 'showarrow': False, - 'text': 'fourth', - 'x': 'B', - 'xref': 'x1', - 'y': 'Two', - 'yref': 'y1'}, - {'font': {'color': '#000000'}, - 'showarrow': False, - 'text': 'fifth', - 'x': 'A', - 'xref': 'x1', - 'y': 'Three', - 'yref': 'y1'}, - {'font': {'color': '#000000'}, - 'showarrow': False, - 'text': 'sixth', - 'x': 'B', - 'xref': 'x1', - 'y': 'Three', - 'yref': 'y1'}], - 'xaxis': {'gridcolor': 'rgb(0, 0, 0)', 'side': - 'top', 'ticks': ''}, - 'yaxis': {'ticks': '', 'ticksuffix': ' '}}} + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'second', + 'x': 'B', + 'xref': 'x1', + 'y': 'One', + 'yref': 'y1'}, + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'third', + 'x': 'A', + 'xref': 'x1', + 'y': 'Two', + 'yref': 'y1'}, + {'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': 'fourth', + 'x': 'B', + 'xref': 'x1', + 'y': 'Two', + 'yref': 'y1'}, + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'fifth', + 'x': 'A', + 'xref': 'x1', + 'y': 'Three', + 'yref': 'y1'}, + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'sixth', + 'x': 'B', + 'xref': 'x1', + 'y': 'Three', + 'yref': 'y1'}], + 'xaxis': {'dtick': 1, + 'gridcolor': 'rgb(0, 0, 0)', + 'side': 'top', + 'ticks': ''}, + 'yaxis': {'dtick': 1, 'ticks': '', + 'ticksuffix': ' '}}} self.assertEqual(a, expected_a) @@ -851,140 +871,260 @@ def test_fontcolor_input(self): self.assertRaises(PlotlyError, tls.FigureFactory.create_table, **kwargs) - # def test_simple_table(self): - - # # we should be able to create a striped table by suppling a text matrix - - # text = [['Country', 'Year', 'Population'], ['US', 2000, 282200000], - # ['Canada', 2000, 27790000], ['US', 1980, 226500000]] - # table = tls.FigureFactory.create_table(text) - # expected_table = {'data': [{'colorscale': [[0, '#66b2ff'], [0.5, '#e6e6e6'], [1, '#ffffff']], - # 'opacity': 0.7, - # 'showscale': False, - # 'type': 'heatmap', - # 'z': [[0, 0, 0], [0.5, 0.5, 0.5], [1, 1, 1], [0.5, 0.5, 0.5]]}], - # 'layout': {'annotations': [{'align': 'left', - # 'font': {'color': '#000000'}, - # 'showarrow': False, - # 'text': 'Country', - # 'x': -0.45, - # 'xanchor': 'left', - # 'xref': 'x1', - # 'y': 0, - # 'yref': 'y1'}, - # {'align': 'left', - # 'font': {'color': '#000000'}, - # 'showarrow': False, - # 'text': 'Year', - # 'x': 0.55, - # 'xanchor': 'left', - # 'xref': 'x1', - # 'y': 0, - # 'yref': 'y1'}, - # {'align': 'left', - # 'font': {'color': '#000000'}, - # 'showarrow': False, - # 'text': 'Population', - # 'x': 1.55, - # 'xanchor': 'left', - # 'xref': 'x1', - # 'y': 0, - # 'yref': 'y1'}, - # {'align': 'left', - # 'font': {'color': '#000000'}, - # 'showarrow': False, - # 'text': 'US', - # 'x': -0.45, - # 'xanchor': 'left', - # 'xref': 'x1', - # 'y': 1, - # 'yref': 'y1'}, - # {'align': 'left', - # 'font': {'color': '#000000'}, - # 'showarrow': False, - # 'text': '2000', - # 'x': 0.55, - # 'xanchor': 'left', - # 'xref': 'x1', - # 'y': 1, - # 'yref': 'y1'}, - # {'align': 'left', - # 'font': {'color': '#000000'}, - # 'showarrow': False, - # 'text': '282200000', - # 'x': 1.55, - # 'xanchor': 'left', - # 'xref': 'x1', - # 'y': 1, - # 'yref': 'y1'}, - # {'align': 'left', - # 'font': {'color': '#000000'}, - # 'showarrow': False, - # 'text': 'Canada', - # 'x': -0.45, - # 'xanchor': 'left', - # 'xref': 'x1', - # 'y': 2, - # 'yref': 'y1'}, - # {'align': 'left', - # 'font': {'color': '#000000'}, - # 'showarrow': False, - # 'text': '2000', - # 'x': 0.55, - # 'xanchor': 'left', - # 'xref': 'x1', - # 'y': 2, - # 'yref': 'y1'}, - # {'align': 'left', - # 'font': {'color': '#000000'}, - # 'showarrow': False, - # 'text': '27790000', - # 'x': 1.55, - # 'xanchor': 'left', - # 'xref': 'x1', - # 'y': 2, - # 'yref': 'y1'}, - # {'align': 'left', - # 'font': {'color': '#000000'}, - # 'showarrow': False, - # 'text': 'US', - # 'x': -0.45, - # 'xanchor': 'left', - # 'xref': 'x1', - # 'y': 3, - # 'yref': 'y1'}, - # {'align': 'left', - # 'font': {'color': '#000000'}, - # 'showarrow': False, - # 'text': '1980', - # 'x': 0.55, - # 'xanchor': 'left', - # 'xref': 'x1', - # 'y': 3, - # 'yref': 'y1'}, - # {'align': 'left', - # 'font': {'color': '#000000'}, - # 'showarrow': False, - # 'text': '226500000', - # 'x': 1.55, - # 'xanchor': 'left', - # 'xref': 'x1', - # 'y': 3, - # 'yref': 'y1'}], - # 'height': 320, - # 'xaxis': {'dtick': 1, - # 'gridwidth': 2, - # 'showticklabels': False, - # 'tick0': -0.5, - # 'ticks': '', - # 'zeroline': False}, - # 'yaxis': {'autorange': 'reversed', - # 'dtick': 1, - # 'gridwidth': 2, - # 'showticklabels': False, - # 'tick0': 0.5, - # 'ticks': '', - # 'zeroline': False}}} + def test_simple_table(self): + + # we should be able to create a striped table by suppling a text matrix + + text = [['Country', 'Year', 'Population'], ['US', 2000, 282200000], + ['Canada', 2000, 27790000], ['US', 1980, 226500000]] + table = tls.FigureFactory.create_table(text) + expected_table = {'data': [{'colorscale': [[0, '#00083e'], + [0.5, '#ededee'], + [1, '#ffffff']], + 'hoverinfo': 'none', + 'opacity': 0.75, + 'showscale': False, + 'type': 'heatmap', + 'z': [[0, 0, 0], [0.5, 0.5, 0.5], + [1, 1, 1], [0.5, 0.5, 0.5]]}], + 'layout': {'annotations': [{'align': 'left', + 'font': {'color': '#ffffff'}, + 'showarrow': False, + 'text': 'Country', + 'x': -0.45, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 0, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#ffffff'}, + 'showarrow': False, + 'text': 'Year', + 'x': 0.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 0, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#ffffff'}, + 'showarrow': False, + 'text': 'Population', + 'x': 1.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 0, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'US', + 'x': -0.45, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 1, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '2000', + 'x': 0.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 1, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '282200000', + 'x': 1.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 1, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'Canada', + 'x': -0.45, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 2, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '2000', + 'x': 0.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 2, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '27790000', + 'x': 1.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 2, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'US', + 'x': -0.45, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 3, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '1980', + 'x': 0.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 3, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '226500000', + 'x': 1.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 3, + 'yref': 'y1'}], + 'height': 170, + 'margin': {'b': 0, 'l': 0, 'r': 0, 't': 0}, + 'xaxis': {'dtick': 1, + 'gridwidth': 2, + 'showticklabels': False, + 'tick0': -0.5, + 'ticks': '', + 'zeroline': False}, + 'yaxis': {'autorange': 'reversed', + 'dtick': 1, + 'gridwidth': 2, + 'showticklabels': False, + 'tick0': 0.5, + 'ticks': '', + 'zeroline': False}}} + self.assertEqual(table, expected_table) + + def test_table_with_index(self): + + # we should be able to create a striped table where the first column + # matches the coloring of the header + + text = [['Country', 'Year', 'Population'], ['US', 2000, 282200000], + ['Canada', 2000, 27790000]] + index_table = tls.FigureFactory.create_table(text, index=True, + index_title='Title') + exp_index_table = {'data': [{'colorscale': [[0, '#00083e'], [0.5, '#ededee'], [1, '#ffffff']], + 'hoverinfo': 'none', + 'opacity': 0.75, + 'showscale': False, + 'type': 'heatmap', + 'z': [[0, 0, 0], [0, 0.5, 0.5], [0, 1, 1]]}], + 'layout': {'annotations': [{'align': 'left', + 'font': {'color': '#ffffff'}, + 'showarrow': False, + 'text': 'Country', + 'x': -0.45, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 0, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#ffffff'}, + 'showarrow': False, + 'text': 'Year', + 'x': 0.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 0, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#ffffff'}, + 'showarrow': False, + 'text': 'Population', + 'x': 1.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 0, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#ffffff'}, + 'showarrow': False, + 'text': 'US', + 'x': -0.45, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 1, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '2000', + 'x': 0.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 1, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '282200000', + 'x': 1.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 1, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#ffffff'}, + 'showarrow': False, + 'text': 'Canada', + 'x': -0.45, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 2, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '2000', + 'x': 0.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 2, + 'yref': 'y1'}, + {'align': 'left', + 'font': {'color': '#000000'}, + 'showarrow': False, + 'text': '27790000', + 'x': 1.55, + 'xanchor': 'left', + 'xref': 'x1', + 'y': 2, + 'yref': 'y1'}], + 'height': 140, + 'margin': {'b': 0, 'l': 0, 'r': 0, 't': 0}, + 'xaxis': {'dtick': 1, + 'gridwidth': 2, + 'showticklabels': False, + 'tick0': -0.5, + 'ticks': '', + 'zeroline': False}, + 'yaxis': {'autorange': 'reversed', + 'dtick': 1, + 'gridwidth': 2, + 'showticklabels': False, + 'tick0': 0.5, + 'ticks': '', + 'zeroline': False}}} + self.assertEqual(index_table, exp_index_table) + # class TestDistplot(TestCase): diff --git a/plotly/tools.py b/plotly/tools.py index 62a01da3dfa..af9db5b3fbd 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1651,7 +1651,7 @@ def _hex_to_rgb(value): :rtype (tuple) (r_value, g_value, b_value): tuple of rgb values """ - hex_value = value.lstrip('#') + value = value.lstrip('#') hex_total_length = len(value) rgb_section_length = hex_total_length // 3 return tuple(int(value[i:i + rgb_section_length], 16) @@ -2707,7 +2707,7 @@ def create_table(table_text, colorscale=None, font_colors=None, ['US', 2010, 309000000], ['Canada', 2010, 34000000]] - table=FF.create_table(text, + table = FF.create_table(text, colorscale=[[0, '#000000'], [.5, '#80beff'], [1, '#cce5ff']], From 29452a0035e4eff93d47f5ec60ee0fb0112070ae Mon Sep 17 00:00:00 2001 From: Chelsea Date: Thu, 26 Nov 2015 15:58:42 -0500 Subject: [PATCH 10/12] add `list()` for python3 - `range()` was resulting in an `obj` in python3 where a `list` was needed --- plotly/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plotly/tools.py b/plotly/tools.py index af9db5b3fbd..71835639ec6 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -3888,7 +3888,7 @@ def get_table_font_color(self): if len(self.font_colors) == 1: all_font_colors = self.font_colors*len(self.table_text) elif len(self.font_colors) == 3: - all_font_colors = range(len(self.table_text)) + all_font_colors = list(range(len(self.table_text))) all_font_colors[0] = self.font_colors[0] for i in range(1, len(self.table_text), 2): all_font_colors[i] = self.font_colors[1] From 704e7af5772e5f7bca7749aaf2495ce017b497c3 Mon Sep 17 00:00:00 2001 From: Chelsea Date: Thu, 26 Nov 2015 16:22:24 -0500 Subject: [PATCH 11/12] Update CHANGELOG.md - added annotated heatmaps and tables to FigureFactory --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cdf5ed2b46..0f97382ec45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## [1.9.1] - 2015-11-26 +### Added +- The FigureFactory can now create annotated heatmaps with `.create_annotated_heatmap`. +- The FigureFactory can now create tables with `.create_table`. + ## [1.9.0] - 2015-11-15 - Previously, using plotly offline required a paid license. No more: `plotly.js` is now shipped inside this package to allow From e77c0550aa4f2c3f53b4d6ca01fa40590b39e17e Mon Sep 17 00:00:00 2001 From: Chelsea Date: Thu, 26 Nov 2015 16:16:19 -0500 Subject: [PATCH 12/12] bump version --- plotly/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plotly/version.py b/plotly/version.py index e5102d30158..35424e875ea 100644 --- a/plotly/version.py +++ b/plotly/version.py @@ -1 +1 @@ -__version__ = '1.9.0' +__version__ = '1.9.1'