From ba423d51c33e267b56f07ce46402f0eb261a3609 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Mon, 20 Nov 2017 15:20:43 -0500 Subject: [PATCH 01/19] add annotations func to utils in figure_factory --- plotly/figure_factory/utils.py | 66 ++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/plotly/figure_factory/utils.py b/plotly/figure_factory/utils.py index baffeeb68a4..5cebad78458 100644 --- a/plotly/figure_factory/utils.py +++ b/plotly/figure_factory/utils.py @@ -488,3 +488,69 @@ def endpts_to_intervals(endpts): # add +inf to intervals intervals.append([endpts[length - 1], float('inf')]) return intervals + +def annotation_dict_for_label(text, lane, num_of_lanes, subplot_spacing, + row_col='col', flipped=True, right_side=True, + text_color='#0f0f0f'): + """ + Returns annotation dict for label of n labels of a 1xn or nx1 subplot. + + :param (str) text: the text for a label. + :param (int) lane: the label number for text. From 1 to n inclusive. + :param (int) num_of_lanes: the number 'n' of rows or columns in subplot. + :param (float) subplot_spacing: the value for the horizontal_spacing and + vertical_spacing params in your plotly.tools.make_subplots() call. + :param (str) row_col: choose whether labels are placed along rows or + columns. + :param (bool) flipped: flips text by 90 degrees. Text is printed + horizontally if set to True and row_col='row', or if False and + row_col='col'. + :param (bool) right_side: only applicable if row_col is set to 'row'. + :param (str) text_color: color of the text. + """ + l = (1 - (num_of_lanes - 1) * subplot_spacing) / (num_of_lanes) + if not flipped: + xanchor = 'center' + yanchor = 'middle' + if row_col == 'col': + x = (lane - 1) * (l + subplot_spacing) + 0.5 * l + y = 1.03 + textangle = 0 + elif row_col == 'row': + y = (lane - 1) * (l + subplot_spacing) + 0.5 * l + x = 1.03 + textangle = 90 + else: + if row_col == 'col': + xanchor = 'center' + yanchor = 'bottom' + x = (lane - 1) * (l + subplot_spacing) + 0.5 * l + y = 1.0 + textangle = 270 + elif row_col == 'row': + yanchor = 'middle' + y = (lane - 1) * (l + subplot_spacing) + 0.5 * l + if right_side: + x = 1.0 + xanchor = 'left' + else: + x = -0.01 + xanchor = 'right' + textangle = 0 + + annotation_dict = dict( + textangle=textangle, + xanchor=xanchor, + yanchor=yanchor, + x=x, + y=y, + showarrow=False, + xref='paper', + yref='paper', + text=text, + font=dict( + size=13, + color=text_color + ) + ) + return annotation_dict From 3c5990a362e5fdf8c6264930173c7071acf4cf50 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Mon, 20 Nov 2017 16:01:00 -0500 Subject: [PATCH 02/19] fix annotation function for subplot_spacing==0 --- plotly/figure_factory/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plotly/figure_factory/utils.py b/plotly/figure_factory/utils.py index 5cebad78458..7dc3574bfcd 100644 --- a/plotly/figure_factory/utils.py +++ b/plotly/figure_factory/utils.py @@ -489,6 +489,7 @@ def endpts_to_intervals(endpts): intervals.append([endpts[length - 1], float('inf')]) return intervals + def annotation_dict_for_label(text, lane, num_of_lanes, subplot_spacing, row_col='col', flipped=True, right_side=True, text_color='#0f0f0f'): @@ -508,7 +509,7 @@ def annotation_dict_for_label(text, lane, num_of_lanes, subplot_spacing, :param (bool) right_side: only applicable if row_col is set to 'row'. :param (str) text_color: color of the text. """ - l = (1 - (num_of_lanes - 1) * subplot_spacing) / (num_of_lanes) + l = (1.0 - (num_of_lanes - 1) * subplot_spacing) / (num_of_lanes) if not flipped: xanchor = 'center' yanchor = 'middle' From 85ebed669640850237f2d0c4357d261e23ccea82 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Wed, 29 Nov 2017 12:58:45 -0500 Subject: [PATCH 03/19] add sparkline file and import in __init__ --- plotly/figure_factory/__init__.py | 1 + plotly/figure_factory/_bullet.py | 1 + plotly/figure_factory/utils.py | 34 ++++++++++++++++++++++++------- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/plotly/figure_factory/__init__.py b/plotly/figure_factory/__init__.py index 5aa4e97e281..977dca6cdb9 100644 --- a/plotly/figure_factory/__init__.py +++ b/plotly/figure_factory/__init__.py @@ -14,6 +14,7 @@ from plotly.figure_factory._ohlc import create_ohlc from plotly.figure_factory._quiver import create_quiver from plotly.figure_factory._scatterplot import create_scatterplotmatrix +from plotly.figure_factory._sparkline import create_sparkline from plotly.figure_factory._streamline import create_streamline from plotly.figure_factory._table import create_table from plotly.figure_factory._trisurf import create_trisurf diff --git a/plotly/figure_factory/_bullet.py b/plotly/figure_factory/_bullet.py index c23eaeb0e0b..6316ad9dcd1 100644 --- a/plotly/figure_factory/_bullet.py +++ b/plotly/figure_factory/_bullet.py @@ -12,6 +12,7 @@ pd = optional_imports.get_module('pandas') +# TODO: replace by same function in plotly/figure_factory/utils.py def is_sequence(obj): return (isinstance(obj, collections.Sequence) and not isinstance(obj, str)) diff --git a/plotly/figure_factory/utils.py b/plotly/figure_factory/utils.py index 4d22db01bb1..d5fc5d229d2 100644 --- a/plotly/figure_factory/utils.py +++ b/plotly/figure_factory/utils.py @@ -1,5 +1,6 @@ from __future__ import absolute_import +import collections import decimal from plotly import exceptions @@ -33,6 +34,11 @@ } +def is_sequence(obj): + return (isinstance(obj, collections.Sequence) and + not isinstance(obj, str)) + + def validate_index(index_vals): """ Validates if a list contains all numbers or all strings @@ -379,6 +385,7 @@ def validate_colors_dict(colors, colortype='tuple'): return colors + def colorscale_to_colors(colorscale): """ Extracts the colors from colorscale as a list @@ -492,7 +499,7 @@ def endpts_to_intervals(endpts): def annotation_dict_for_label(text, lane, num_of_lanes, subplot_spacing, row_col='col', flipped=True, right_side=True, - text_color='#0f0f0f'): + text_color='#0f0f0f', column_width=None): """ Returns annotation dict for label of n labels of a 1xn or nx1 subplot. @@ -508,29 +515,42 @@ def annotation_dict_for_label(text, lane, num_of_lanes, subplot_spacing, row_col='col'. :param (bool) right_side: only applicable if row_col is set to 'row'. :param (str) text_color: color of the text. - """ - l = (1.0 - (num_of_lanes - 1) * subplot_spacing) / (num_of_lanes) + :param (list|tuple) column_width: a sequence of numbers that indicate the + proportions that each subplot lane (row/column) take relative to one + another. + """ + if not column_width: + column_width = [1.0] * num_of_lanes + + cum_sum = float(sum(column_width)) + len_without_spacing = (1.0 - (num_of_lanes - 1) * subplot_spacing) + lane_len = (len_without_spacing * (column_width[lane - 1] / cum_sum)) + len_before_lane = ( + len_without_spacing * (sum(column_width[:lane - 1]) / cum_sum) + + (lane - 1) * subplot_spacing + ) + if not flipped: xanchor = 'center' yanchor = 'middle' if row_col == 'col': - x = (lane - 1) * (l + subplot_spacing) + 0.5 * l + x = len_before_lane + 0.5 * lane_len y = 1.03 textangle = 0 elif row_col == 'row': - y = (lane - 1) * (l + subplot_spacing) + 0.5 * l + y = len_before_lane + 0.5 * lane_len x = 1.03 textangle = 90 else: if row_col == 'col': xanchor = 'center' yanchor = 'bottom' - x = (lane - 1) * (l + subplot_spacing) + 0.5 * l + x = len_before_lane + 0.5 * lane_len y = 1.0 textangle = 270 elif row_col == 'row': yanchor = 'middle' - y = (lane - 1) * (l + subplot_spacing) + 0.5 * l + y = len_before_lane + 0.5 * lane_len if right_side: x = 1.0 xanchor = 'left' From 590330b87642c54c6ad0eab798d11cdd3f624b08 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Thu, 30 Nov 2017 14:24:01 -0500 Subject: [PATCH 04/19] added colors and finished up tests --- plotly/figure_factory/_bullet.py | 1 + .../test_optional/test_figure_factory.py | 511 +++++++++++++++++- 2 files changed, 511 insertions(+), 1 deletion(-) diff --git a/plotly/figure_factory/_bullet.py b/plotly/figure_factory/_bullet.py index 6316ad9dcd1..cbdc954384e 100644 --- a/plotly/figure_factory/_bullet.py +++ b/plotly/figure_factory/_bullet.py @@ -165,6 +165,7 @@ def _bullet(df, markers, measures, ranges, subtitles, titles, orientation, return fig +# TODO: replace scatter_options with None in function signature def create_bullet(data, markers=None, measures=None, ranges=None, subtitles=None, titles=None, orientation='h', range_colors=('rgb(200, 200, 200)', 'rgb(245, 245, 245)'), diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index d0a5755b371..7e40544c1f3 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -2638,7 +2638,7 @@ def test_full_bullet(self): 'showarrow': False, 'text': 'New Customers', 'textangle': 0, - 'x': 0.74, + 'x': 0.7400000000000001, 'xanchor': 'center', 'xref': 'paper', 'y': 1.03, @@ -2718,3 +2718,512 @@ def test_full_bullet(self): 'zeroline': False}} } self.assert_dict_equal(fig, exp_fig) + + +class TestSparkline(NumpyTestUtilsMixin, TestCase): + + def test_is_dataframe(self): + df = 'not going to work' + message = 'df must be a pandas DataFrame' + + self.assertRaisesRegexp(PlotlyError, message, ff.create_sparkline, df) + + def test_valid_chart_type(self): + df = pd.DataFrame( + [ + [5, 2, 9], + [6, 5, 4], + [4, 7, 6] + ], + columns=['foo', 'bar', 'baz'], + ) + VALID_CHART_TYPES = ['name', 'bullet', 'line', 'avg', 'bar'] + message = ( + 'Your chart type must be a list and may only contain any ' + 'combination of the keys {}'.format( + utils.list_of_options(VALID_CHART_TYPES, 'or') + ) + ) + + self.assertRaisesRegexp(PlotlyError, message, ff.create_sparkline, + df, chart_types=['love']) + + def test_is_colors_a_list(self): + df = pd.DataFrame( + [ + [5, 2, 9], + [6, 5, 4], + [4, 7, 6] + ], + columns=['foo', 'bar', 'baz'], + ) + + message = 'colors must be a list/tuple' + self.assertRaisesRegexp(PlotlyError, message, ff.create_sparkline, + df, colors='not going to work') + + def test_colors_correct_length(self): + df = pd.DataFrame( + [ + [5, 2, 9], + [6, 5, 4], + [4, 7, 6] + ], + columns=['foo', 'bar', 'baz'], + ) + + message = 'colors must be a list/tuple with 2 colors inside' + self.assertRaisesRegexp(PlotlyError, message, ff.create_sparkline, + df, colors=['rgb(0, 0, 0)']) + + def test_full_sparkline_fig(self): + + df = pd.DataFrame( + [ + [5, 2, 9], + [6, 5, 4], + [4, 7, 6] + ], + columns=['foo', 'bar', 'baz'], + ) + + exp_fig = { + 'data': [{'type': 'bar', + 'visible': False, + 'x': [0], + 'xaxis': 'x1', + 'y': [0], + 'yaxis': 'y1'}, + {'marker': {'color': 'rgb(181,221,232)'}, + 'orientation': 'h', + 'type': 'bar', + 'x': [5.0], + 'xaxis': 'x2', + 'y': [0], + 'yaxis': 'y2'}, + {'marker': {'color': 'rgb(62,151,169)'}, + 'offset': -0.1, + 'orientation': 'h', + 'type': 'bar', + 'width': 0.2, + 'x': [4], + 'xaxis': 'x2', + 'y': [0], + 'yaxis': 'y2'}, + {'marker': {'color': 'rgb(0,0,0)', 'size': 9, 'symbol': 'diamond-tall'}, + 'type': 'scatter', + 'x': [6], + 'xaxis': 'x2', + 'y': [0], + 'yaxis': 'y2'}, + {'marker': {'color': 'rgb(181,221,232)'}, + 'mode': 'lines', + 'type': 'scatter', + 'x': [0, 1, 2], + 'xaxis': 'x3', + 'y': [5, 6, 4], + 'yaxis': 'y3'}, + {'marker': {'color': 'rgb(62,151,169)'}, + 'mode': 'markers', + 'type': 'scatter', + 'x': [2], + 'xaxis': 'x3', + 'y': [4], + 'yaxis': 'y3'}, + {'type': 'bar', + 'visible': False, + 'x': [0], + 'xaxis': 'x4', + 'y': [0], + 'yaxis': 'y4'}, + {'marker': {'color': ['rgb(181,221,232)', + 'rgb(181,221,232)', + 'rgb(62,151,169)']}, + 'type': 'bar', + 'x': [0, 1, 2], + 'xaxis': 'x5', + 'y': [5, 6, 4], + 'yaxis': 'y5'}, + {'type': 'bar', + 'visible': False, + 'x': [0], + 'xaxis': 'x6', + 'y': [0], + 'yaxis': 'y6'}, + {'marker': {'color': 'rgb(181,221,232)'}, + 'orientation': 'h', + 'type': 'bar', + 'x': [4.67], + 'xaxis': 'x7', + 'y': [0], + 'yaxis': 'y7'}, + {'marker': {'color': 'rgb(62,151,169)'}, + 'offset': -0.1, + 'orientation': 'h', + 'type': 'bar', + 'width': 0.2, + 'x': [7], + 'xaxis': 'x7', + 'y': [0], + 'yaxis': 'y7'}, + {'marker': {'color': 'rgb(0,0,0)', 'size': 9, 'symbol': 'diamond-tall'}, + 'type': 'scatter', + 'x': [7], + 'xaxis': 'x7', + 'y': [0], + 'yaxis': 'y7'}, + {'marker': {'color': 'rgb(181,221,232)'}, + 'mode': 'lines', + 'type': 'scatter', + 'x': [0, 1, 2], + 'xaxis': 'x8', + 'y': [2, 5, 7], + 'yaxis': 'y8'}, + {'marker': {'color': 'rgb(62,151,169)'}, + 'mode': 'markers', + 'type': 'scatter', + 'x': [2], + 'xaxis': 'x8', + 'y': [7], + 'yaxis': 'y8'}, + {'type': 'bar', + 'visible': False, + 'x': [0], + 'xaxis': 'x9', + 'y': [0], + 'yaxis': 'y9'}, + {'marker': {'color': ['rgb(181,221,232)', + 'rgb(181,221,232)', + 'rgb(62,151,169)']}, + 'type': 'bar', + 'x': [0, 1, 2], + 'xaxis': 'x10', + 'y': [2, 5, 7], + 'yaxis': 'y10'}, + {'type': 'bar', + 'visible': False, + 'x': [0], + 'xaxis': 'x11', + 'y': [0], + 'yaxis': 'y11'}, + {'marker': {'color': 'rgb(181,221,232)'}, + 'orientation': 'h', + 'type': 'bar', + 'x': [6.33], + 'xaxis': 'x12', + 'y': [0], + 'yaxis': 'y12'}, + {'marker': {'color': 'rgb(62,151,169)'}, + 'offset': -0.1, + 'orientation': 'h', + 'type': 'bar', + 'width': 0.2, + 'x': [6], + 'xaxis': 'x12', + 'y': [0], + 'yaxis': 'y12'}, + {'marker': {'color': 'rgb(0,0,0)', 'size': 9, 'symbol': 'diamond-tall'}, + 'type': 'scatter', + 'x': [9], + 'xaxis': 'x12', + 'y': [0], + 'yaxis': 'y12'}, + {'marker': {'color': 'rgb(181,221,232)'}, + 'mode': 'lines', + 'type': 'scatter', + 'x': [0, 1, 2], + 'xaxis': 'x13', + 'y': [9, 4, 6], + 'yaxis': 'y13'}, + {'marker': {'color': 'rgb(62,151,169)'}, + 'mode': 'markers', + 'type': 'scatter', + 'x': [2], + 'xaxis': 'x13', + 'y': [6], + 'yaxis': 'y13'}, + {'type': 'bar', + 'visible': False, + 'x': [0], + 'xaxis': 'x14', + 'y': [0], + 'yaxis': 'y14'}, + {'marker': {'color': ['rgb(181,221,232)', + 'rgb(181,221,232)', + 'rgb(62,151,169)']}, + 'type': 'bar', + 'x': [0, 1, 2], + 'xaxis': 'x15', + 'y': [9, 4, 6], + 'yaxis': 'y15'}], + 'layout': {'annotations': [{'font': {'size': 15}, + 'showarrow': False, + 'text': 'foo', + 'x': 1, + 'xanchor': 'right', + 'xref': 'x1', + 'y': 0.5, + 'yref': 'y1'}, + {'font': {'size': 15}, + 'showarrow': False, + 'text': '5.0', + 'x': 1, + 'xanchor': 'right', + 'xref': 'x4', + 'y': 0.5, + 'yref': 'y4'}, + {'font': {'size': 15}, + 'showarrow': False, + 'text': 'bar', + 'x': 1, + 'xanchor': 'right', + 'xref': 'x6', + 'y': 0.5, + 'yref': 'y6'}, + {'font': {'size': 15}, + 'showarrow': False, + 'text': '4.67', + 'x': 1, + 'xanchor': 'right', + 'xref': 'x9', + 'y': 0.5, + 'yref': 'y9'}, + {'font': {'size': 15}, + 'showarrow': False, + 'text': 'baz', + 'x': 1, + 'xanchor': 'right', + 'xref': 'x11', + 'y': 0.5, + 'yref': 'y11'}, + {'font': {'size': 15}, + 'showarrow': False, + 'text': '6.33', + 'x': 1, + 'xanchor': 'right', + 'xref': 'x14', + 'y': 0.5, + 'yref': 'y14'}, + {'font': {'color': '#0f0f0f', 'size': 13}, + 'showarrow': False, + 'text': 'name', + 'textangle': 0, + 'x': 0.03333333333333333, + 'xanchor': 'center', + 'xref': 'paper', + 'y': 1.03, + 'yanchor': 'middle', + 'yref': 'paper'}, + {'font': {'color': '#0f0f0f', 'size': 13}, + 'showarrow': False, + 'text': 'bullet', + 'textangle': 0, + 'x': 0.13333333333333333, + 'xanchor': 'center', + 'xref': 'paper', + 'y': 1.03, + 'yanchor': 'middle', + 'yref': 'paper'}, + {'font': {'color': '#0f0f0f', 'size': 13}, + 'showarrow': False, + 'text': 'line', + 'textangle': 0, + 'x': 0.30000000000000004, + 'xanchor': 'center', + 'xref': 'paper', + 'y': 1.03, + 'yanchor': 'middle', + 'yref': 'paper'}, + {'font': {'color': '#0f0f0f', 'size': 13}, + 'showarrow': False, + 'text': 'avg', + 'textangle': 0, + 'x': 0.5333333333333333, + 'xanchor': 'center', + 'xref': 'paper', + 'y': 1.03, + 'yanchor': 'middle', + 'yref': 'paper'}, + {'font': {'color': '#0f0f0f', 'size': 13}, + 'showarrow': False, + 'text': 'bar', + 'textangle': 0, + 'x': 0.8333333333333333, + 'xanchor': 'center', + 'xref': 'paper', + 'y': 1.03, + 'yanchor': 'middle', + 'yref': 'paper'}], + 'showlegend': False, + 'title': 'this is a test', + 'xaxis1': {'anchor': 'y1', + 'domain': [0.0, 0.06666666666666667], + 'range': [0, 1], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'xaxis10': {'anchor': 'y10', + 'domain': [0.6666666666666667, 1.0], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'xaxis11': {'anchor': 'y11', + 'domain': [0.0, 0.06666666666666667], + 'range': [0, 1], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'xaxis12': {'anchor': 'y12', + 'domain': [0.06666666666666667, 0.2], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'xaxis13': {'anchor': 'y13', + 'domain': [0.2, 0.4], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'xaxis14': {'anchor': 'y14', + 'domain': [0.4, 0.6666666666666667], + 'range': [0, 1], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'xaxis15': {'anchor': 'y15', + 'domain': [0.6666666666666667, 1.0], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'xaxis2': {'anchor': 'y2', + 'domain': [0.06666666666666667, 0.2], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'xaxis3': {'anchor': 'y3', + 'domain': [0.2, 0.4], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'xaxis4': {'anchor': 'y4', + 'domain': [0.4, 0.6666666666666667], + 'range': [0, 1], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'xaxis5': {'anchor': 'y5', + 'domain': [0.6666666666666667, 1.0], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'xaxis6': {'anchor': 'y6', + 'domain': [0.0, 0.06666666666666667], + 'range': [0, 1], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'xaxis7': {'anchor': 'y7', + 'domain': [0.06666666666666667, 0.2], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'xaxis8': {'anchor': 'y8', + 'domain': [0.2, 0.4], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'xaxis9': {'anchor': 'y9', + 'domain': [0.4, 0.6666666666666667], + 'range': [0, 1], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'yaxis1': {'anchor': 'x1', + 'domain': [0.6666666666666666, 1.0], + 'range': [0, 1], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'yaxis10': {'anchor': 'x10', + 'domain': [0.3333333333333333, 0.6666666666666666], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'yaxis11': {'anchor': 'x11', + 'domain': [0.0, 0.3333333333333333], + 'range': [0, 1], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'yaxis12': {'anchor': 'x12', + 'domain': [0.0, 0.3333333333333333], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'yaxis13': {'anchor': 'x13', + 'domain': [0.0, 0.3333333333333333], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'yaxis14': {'anchor': 'x14', + 'domain': [0.0, 0.3333333333333333], + 'range': [0, 1], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'yaxis15': {'anchor': 'x15', + 'domain': [0.0, 0.3333333333333333], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'yaxis2': {'anchor': 'x2', + 'domain': [0.6666666666666666, 1.0], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'yaxis3': {'anchor': 'x3', + 'domain': [0.6666666666666666, 1.0], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'yaxis4': {'anchor': 'x4', + 'domain': [0.6666666666666666, 1.0], + 'range': [0, 1], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'yaxis5': {'anchor': 'x5', + 'domain': [0.6666666666666666, 1.0], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'yaxis6': {'anchor': 'x6', + 'domain': [0.3333333333333333, 0.6666666666666666], + 'range': [0, 1], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'yaxis7': {'anchor': 'x7', + 'domain': [0.3333333333333333, 0.6666666666666666], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'yaxis8': {'anchor': 'x8', + 'domain': [0.3333333333333333, 0.6666666666666666], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}, + 'yaxis9': {'anchor': 'x9', + 'domain': [0.3333333333333333, 0.6666666666666666], + 'range': [0, 1], + 'showgrid': False, + 'showticklabels': False, + 'zeroline': False}} + } + + fig = ff.create_sparkline( + df, chart_types=['name', 'bullet', 'line', 'avg', 'bar'], + column_width=[1, 2, 3, 4, 5], scatter_options={'marker': {'color': 'rgb(0,0,0)'}}, + title='this is a test' + ) + + self.assert_dict_equal(fig, exp_fig) From c65886b040cb1edd4a166561defdab4255cc5b0f Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Thu, 30 Nov 2017 15:04:52 -0500 Subject: [PATCH 05/19] add sparkline file --- plotly/figure_factory/_sparkline.py | 257 ++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 plotly/figure_factory/_sparkline.py diff --git a/plotly/figure_factory/_sparkline.py b/plotly/figure_factory/_sparkline.py new file mode 100644 index 00000000000..83387166462 --- /dev/null +++ b/plotly/figure_factory/_sparkline.py @@ -0,0 +1,257 @@ +from __future__ import absolute_import + +from plotly import exceptions, optional_imports +from plotly.figure_factory import utils + +import plotly +import plotly.graph_objs as go + +import numpy as np +pd = optional_imports.get_module('pandas') + +VALID_CHART_TYPES = ['name', 'bullet', 'line', 'avg', 'bar'] + + +def create_sparkline(df, chart_types=('name', 'bullet', 'line', 'avg', 'bar'), + colors=('rgb(181,221,232)', 'rgb(62,151,169)'), + column_width=None, show_titles=True, left_aligned=False, + scatter_options=None, **layout_options): + """ + Returns figure for sparkline. + + :param (pd.DataFrame | list | tuple) df: either a list/tuple of + dictionaries or a pandas DataFrame. + :param (list|tuple) chart_types: a sequence of any combination of valid + chart types. The valid chart types are 'name', 'bullet', 'line', 'avg' + and 'bar' + :param (list|tuple) colors: a sequence of exactly 2 colors which are used + to color the charts. Set the first color to your ___ color and the + second color as your ___ + Default = ('rgb(181,221,232)', 'rgb(62,151,169)') + :param (list) column_width: Specify a list that contains numbers where + the amount of numbers in the list is equal to `chart_types`. Call + `help(plotly.tools.make_subplots)` for more info on this subplot param + :param (bool) show_titles: determines if title of chart type is displayed + above their respective column + :param (bool) left_aligned: determines if text cells are left-algined or + right-aligned + :param (dict) scatter_options: describes attributes for the scatter point + in each bullet chart such as name and marker size. Call + help(plotly.graph_objs.Scatter) for more information on valid params. + :param layout_options: describes attributes for the layout of the figure + such as title, height and width. Call help(plotly.graph_objs.Layout) + for more information on valid params + """ + # validate dataframe + if not pd: + raise exceptions.ImportError( + "'pandas' must be installed for this figure factory." + ) + + elif not isinstance(df, pd.DataFrame): + raise exceptions.PlotlyError( + 'df must be a pandas DataFrame' + ) + + # validate list/tuple of colors + if not utils.is_sequence(colors): + raise exceptions.PlotlyError( + 'colors must be a list/tuple' + ) + + if len(colors) < 2: + raise exceptions.PlotlyError( + 'colors must be a list/tuple with 2 colors inside' + ) + plotly.colors.validate_colors(colors) + + num_of_chart_types = len(chart_types) + # narrow columns that are 'name' or 'avg' + narrow_cols = ['name', 'avg'] + narrow_idxs = [] + for i, chart in enumerate(chart_types): + if chart in narrow_cols: + narrow_idxs.append(i) + + if not column_width: + column_width = [3.0] * num_of_chart_types + for idx in narrow_idxs: + column_width[idx] = 1.0 + + fig = plotly.tools.make_subplots( + len(df.columns), num_of_chart_types, print_grid=False, + shared_xaxes=False, shared_yaxes=False, + horizontal_spacing=0, vertical_spacing=0, + column_width=column_width + ) + + # layout options + fig['layout'].update( + title='Sparkline Chart', + annotations=[], + showlegend=False + ) + + # update layout + fig['layout'].update(layout_options) + + for key in fig['layout'].keys(): + if 'axis' in key: + fig['layout'][key].update( + showgrid=False, + zeroline=False, + showticklabels=False + ) + + # left aligned + x = 0 if left_aligned else 1 + xanchor = 'left' if left_aligned else 'right' + + # scatter options + default_scatter = { + 'mode': 'markers', + 'marker': {'size': 9, + 'symbol': 'diamond-tall', + 'color': colors[0]} + } + + if not scatter_options: + scatter_options = {} + + if scatter_options == {}: + scatter_options.update(default_scatter) + else: + # add default options to scatter_options if they are not present + for k in default_scatter['marker']: + if k not in scatter_options['marker']: + scatter_options['marker'][k] = default_scatter['marker'][k] + + # create and insert charts + for j, key in enumerate(df): + for c, chart in enumerate(chart_types): + mean = np.mean(df[key]) + rounded_mean = round(mean, 2) + if chart == 'name': + fig['layout']['annotations'].append( + dict( + x=x, + y=0.5, + xref='x{}'.format(j * num_of_chart_types + c + 1), + yref='y{}'.format(j * num_of_chart_types + c + 1), + xanchor=xanchor, + text=key, + showarrow=False, + font=dict(size=15), + ) + ) + empty_data = go.Bar( + x=[0], + y=[0], + visible=False + ) + fig.append_trace(empty_data, j + 1, c + 1) + + elif chart == 'bullet': + bullet_range = go.Bar( + x=[rounded_mean], + y=[0], + marker=dict( + color=colors[0] + ), + orientation='h' + ) + + bullet_measure = go.Bar( + x=[list(df[key])[-1]], + y=[0], + marker=dict( + color=colors[1] + ), + orientation='h', + width=0.2, + offset=-0.1 + ) + + bullet_pt = go.Scatter( + x=[max(df[key])], + y=[0], + **scatter_options + ) + fig.append_trace(bullet_range, j + 1, c + 1) + fig.append_trace(bullet_measure, j + 1, c + 1) + fig.append_trace(bullet_pt, j + 1, c + 1) + elif chart == 'line': + trace_line = go.Scatter( + x=range(len(df[key])), + y=df[key].tolist(), + mode='lines', + marker=dict( + color=colors[0] + ) + ) + fig.append_trace(trace_line, j + 1, c + 1) + + trace_line_pt = go.Scatter( + x=[len(df[key]) - 1], + y=[list(df[key])[-1]], + mode='markers', + marker=dict( + color=colors[1] + ) + ) + fig.append_trace(trace_line_pt, j + 1, c + 1) + elif chart == 'avg': + fig['layout']['annotations'].append( + dict( + xref='x{}'.format(j * num_of_chart_types + c + 1), + yref='y{}'.format(j * num_of_chart_types + c + 1), + x=x, + y=0.5, + xanchor=xanchor, + text='{}'.format(rounded_mean), + showarrow=False, + font=dict(size=15), + ) + ) + empty_data = go.Bar( + x=[0], + y=[0], + visible=False + ) + fig.append_trace(empty_data, j + 1, c + 1) + elif chart == 'bar': + trace_bar = go.Bar( + x=range(len(df[key])), + y=df[key].tolist(), + marker=dict( + color=[colors[0] for _ in + range(len(df[key]) - 1)] + [colors[1]] + ) + ) + fig.append_trace(trace_bar, j + 1, c + 1) + else: + raise exceptions.PlotlyError( + 'Your chart type must be a list and may only contain any ' + 'combination of the keys {}'.format( + utils.list_of_options( + VALID_CHART_TYPES, 'or') + ) + ) + + # show titles + if show_titles: + for k, header in enumerate(chart_types): + label = utils.annotation_dict_for_label( + header, k + 1, 5, subplot_spacing=0, row_col='col', + flipped=False, column_width=column_width + ) + fig['layout']['annotations'].append(label) + + # narrow columns with 'name' or 'avg' chart type + for j in range(len(df.columns)): + for idx in narrow_idxs: + for axis in ['xaxis', 'yaxis']: + fig['layout']['{}{}'.format( + axis, j * num_of_chart_types + idx + 1 + )]['range'] = [0, 1] + return fig From f194fa447278ceb2c00b0731abf0eb894b8bd24f Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Fri, 8 Dec 2017 15:26:22 -0500 Subject: [PATCH 06/19] added alternate row colors and cleaned up tests --- plotly/figure_factory/_sparkline.py | 145 ++++++- .../test_optional/test_figure_factory.py | 355 ++++++++++++------ 2 files changed, 367 insertions(+), 133 deletions(-) diff --git a/plotly/figure_factory/_sparkline.py b/plotly/figure_factory/_sparkline.py index 83387166462..7a5011ddb04 100644 --- a/plotly/figure_factory/_sparkline.py +++ b/plotly/figure_factory/_sparkline.py @@ -9,12 +9,16 @@ import numpy as np pd = optional_imports.get_module('pandas') -VALID_CHART_TYPES = ['name', 'bullet', 'line', 'avg', 'bar'] +VALID_CHART_TYPES = ('name', 'bullet', 'line', 'avg', 'bar') -def create_sparkline(df, chart_types=('name', 'bullet', 'line', 'avg', 'bar'), +def create_sparkline(df, chart_types=VALID_CHART_TYPES, colors=('rgb(181,221,232)', 'rgb(62,151,169)'), - column_width=None, show_titles=True, left_aligned=False, + column_width=None, show_titles=False, textalign='center', + horizontal_spacing=0.03, vertical_spacing=0, + alternate_row_color=True, + lane_colors=('rgba(249, 247, 244, 0.5)', + 'rgba(255, 253, 250, 0.5)'), scatter_options=None, **layout_options): """ Returns figure for sparkline. @@ -33,8 +37,14 @@ def create_sparkline(df, chart_types=('name', 'bullet', 'line', 'avg', 'bar'), `help(plotly.tools.make_subplots)` for more info on this subplot param :param (bool) show_titles: determines if title of chart type is displayed above their respective column - :param (bool) left_aligned: determines if text cells are left-algined or - right-aligned + :param (str) textalign: aligns name and avg cells. Use either 'center', + 'left', or 'right'. Default='center'. + :param (float) horizontal_spacing: + :param (float) vertical_spacing : + :param (float) alternate_row_color: set to True to enable the alternate + row coloring of the chart. Uses the colors from param 'lane_colors' + :param (list) lane_colors: a list/tuple of two colors that are used to + alternately color the rows of the chart. :param (dict) scatter_options: describes attributes for the scatter point in each bullet chart such as name and marker size. Call help(plotly.graph_objs.Scatter) for more information on valid params. @@ -81,8 +91,8 @@ def create_sparkline(df, chart_types=('name', 'bullet', 'line', 'avg', 'bar'), fig = plotly.tools.make_subplots( len(df.columns), num_of_chart_types, print_grid=False, shared_xaxes=False, shared_yaxes=False, - horizontal_spacing=0, vertical_spacing=0, - column_width=column_width + horizontal_spacing=horizontal_spacing, + vertical_spacing=vertical_spacing, column_width=column_width ) # layout options @@ -103,16 +113,25 @@ def create_sparkline(df, chart_types=('name', 'bullet', 'line', 'avg', 'bar'), showticklabels=False ) - # left aligned - x = 0 if left_aligned else 1 - xanchor = 'left' if left_aligned else 'right' + # text alignment + xanchor = textalign + if textalign == 'left': + x = 0 + elif textalign == 'center': + x = 0.5 + elif textalign == 'right': + x = 1 + else: + raise exceptions.PlotlyError( + 'textalign must be left, center or right' + ) # scatter options default_scatter = { 'mode': 'markers', 'marker': {'size': 9, 'symbol': 'diamond-tall', - 'color': colors[0]} + 'color': colors[1]} } if not scatter_options: @@ -149,37 +168,75 @@ def create_sparkline(df, chart_types=('name', 'bullet', 'line', 'avg', 'bar'), y=[0], visible=False ) + if alternate_row_color: + bkgcolor = go.Scatter( + x=[0, 1], + y=[1.2, 1.2], + fill='tozeroy', + mode='line', + hoverinfo='none', + line=dict( + color=(lane_colors[0] if (j + 1) % 2 == 0 + else lane_colors[1]), + width=0 + ), + ) + fig.append_trace(bkgcolor, j + 1, c + 1) fig.append_trace(empty_data, j + 1, c + 1) elif chart == 'bullet': bullet_range = go.Bar( x=[rounded_mean], - y=[0], + y=[0.5], marker=dict( color=colors[0] ), - orientation='h' + hoverinfo='x', + orientation='h', + width=0.5 ) bullet_measure = go.Bar( x=[list(df[key])[-1]], - y=[0], + y=[0.5], marker=dict( color=colors[1] ), + hoverinfo='x', orientation='h', - width=0.2, - offset=-0.1 + width=0.14, + offset=-0.07 ) bullet_pt = go.Scatter( x=[max(df[key])], - y=[0], + y=[0.5], + hoverinfo='x', **scatter_options ) + + if alternate_row_color: + bkgcolor = go.Scatter( + x=[0, 2 * max(df[key])], + y=[1, 1], + fill='tozeroy', + mode='lines', + hoverinfo='none', + line=dict( + color=(lane_colors[0] if (j + 1) % 2 == 0 + else lane_colors[1]), + width=0 + ), + ) + + fig.append_trace(bkgcolor, j + 1, c + 1) fig.append_trace(bullet_range, j + 1, c + 1) fig.append_trace(bullet_measure, j + 1, c + 1) fig.append_trace(bullet_pt, j + 1, c + 1) + + fig['layout']['yaxis{}'.format( + j * num_of_chart_types + (c + 1) + )]['range'] = [0, 1] elif chart == 'line': trace_line = go.Scatter( x=range(len(df[key])), @@ -199,7 +256,26 @@ def create_sparkline(df, chart_types=('name', 'bullet', 'line', 'avg', 'bar'), color=colors[1] ) ) + + if alternate_row_color: + bkgcolor = go.Scatter( + x=[0, len(df[key])], + y=[2 * max(df[key].tolist())] * 2, + fill='tozeroy', + mode='lines', + hoverinfo='none', + line=dict( + color=(lane_colors[0] if (j + 1) % 2 == 0 + else lane_colors[1]), + width=0 + ), + ) + fig.append_trace(bkgcolor, j + 1, c + 1) fig.append_trace(trace_line_pt, j + 1, c + 1) + + fig['layout']['yaxis{}'.format( + j * num_of_chart_types + (c + 1) + )]['range'] = [0, 2 * max(df[key].tolist())] elif chart == 'avg': fig['layout']['annotations'].append( dict( @@ -218,6 +294,21 @@ def create_sparkline(df, chart_types=('name', 'bullet', 'line', 'avg', 'bar'), y=[0], visible=False ) + + if alternate_row_color: + bkgcolor = go.Scatter( + x=[0, 2], + y=[1.2, 1.2], + fill='tozeroy', + mode='line', + hoverinfo='none', + line=dict( + color=(lane_colors[0] if (j + 1) % 2 == 0 + else lane_colors[1]), + width=0 + ), + ) + fig.append_trace(bkgcolor, j + 1, c + 1) fig.append_trace(empty_data, j + 1, c + 1) elif chart == 'bar': trace_bar = go.Bar( @@ -228,7 +319,26 @@ def create_sparkline(df, chart_types=('name', 'bullet', 'line', 'avg', 'bar'), range(len(df[key]) - 1)] + [colors[1]] ) ) + + if alternate_row_color: + bkgcolor = go.Scatter( + x=[-1, len(df[key])], + y=[2 * max(df[key].tolist())] * 2, + fill='tozeroy', + mode='lines', + hoverinfo='none', + line=dict( + color=(lane_colors[0] if (j + 1) % 2 == 0 + else lane_colors[1]), + width=0 + ), + ) + fig.append_trace(bkgcolor, j + 1, c + 1) fig.append_trace(trace_bar, j + 1, c + 1) + + fig['layout']['yaxis{}'.format( + j * num_of_chart_types + (c + 1) + )]['range'] = [0, 2 * max(df[key].tolist())] else: raise exceptions.PlotlyError( 'Your chart type must be a list and may only contain any ' @@ -254,4 +364,5 @@ def create_sparkline(df, chart_types=('name', 'bullet', 'line', 'avg', 'bar'), fig['layout']['{}{}'.format( axis, j * num_of_chart_types + idx + 1 )]['range'] = [0, 1] + return fig diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index 7e40544c1f3..5e67fd26c40 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -2737,7 +2737,7 @@ def test_valid_chart_type(self): ], columns=['foo', 'bar', 'baz'], ) - VALID_CHART_TYPES = ['name', 'bullet', 'line', 'avg', 'bar'] + VALID_CHART_TYPES = ('name', 'bullet', 'line', 'avg', 'bar') message = ( 'Your chart type must be a list and may only contain any ' 'combination of the keys {}'.format( @@ -2776,6 +2776,20 @@ def test_colors_correct_length(self): self.assertRaisesRegexp(PlotlyError, message, ff.create_sparkline, df, colors=['rgb(0, 0, 0)']) + def test_valid_textalign_value(self): + df = pd.DataFrame( + [ + [5, 2, 9], + [6, 5, 4], + [4, 7, 6] + ], + columns=['foo', 'bar', 'baz'], + ) + + message = 'textalign must be left, center or right' + self.assertRaisesRegexp(PlotlyError, message, ff.create_sparkline, + df, textalign='will not work') + def test_full_sparkline_fig(self): df = pd.DataFrame( @@ -2787,34 +2801,63 @@ def test_full_sparkline_fig(self): columns=['foo', 'bar', 'baz'], ) + fig = ff.create_sparkline( + df, chart_types=('name', 'bullet', 'line', 'avg', 'bar'), + column_width=[1, 2, 3, 4, 5], + scatter_options={'marker': {'color': 'rgb(0,0,0)'}}, + title='this is a test' + ) + exp_fig = { - 'data': [{'type': 'bar', - 'visible': False, - 'x': [0], - 'xaxis': 'x1', - 'y': [0], - 'yaxis': 'y1'}, - {'marker': {'color': 'rgb(181,221,232)'}, + 'data': [{'fill': 'tozeroy', + 'hoverinfo': 'none', + 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'mode': 'line', + 'type': 'scatter', + 'x': [0, 1], + 'xaxis': 'x1', + 'y': [1.2, 1.2], + 'yaxis': 'y1'}, + {'type': 'bar', + 'visible': False, + 'x': [0], + 'xaxis': 'x1', + 'y': [0], + 'yaxis': 'y1'}, + {'fill': 'tozeroy', + 'hoverinfo': 'none', + 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'mode': 'lines', + 'type': 'scatter', + 'x': [0, 12], + 'xaxis': 'x2', + 'y': [1, 1], + 'yaxis': 'y2'}, + {'hoverinfo': 'x', + 'marker': {'color': 'rgb(181,221,232)'}, 'orientation': 'h', 'type': 'bar', + 'width': 0.5, 'x': [5.0], 'xaxis': 'x2', - 'y': [0], + 'y': [0.5], 'yaxis': 'y2'}, - {'marker': {'color': 'rgb(62,151,169)'}, - 'offset': -0.1, + {'hoverinfo': 'x', + 'marker': {'color': 'rgb(62,151,169)'}, + 'offset': -0.07, 'orientation': 'h', 'type': 'bar', - 'width': 0.2, + 'width': 0.14, 'x': [4], 'xaxis': 'x2', - 'y': [0], + 'y': [0.5], 'yaxis': 'y2'}, - {'marker': {'color': 'rgb(0,0,0)', 'size': 9, 'symbol': 'diamond-tall'}, + {'hoverinfo': 'x', + 'marker': {'color': 'rgb(0,0,0)', 'size': 9, 'symbol': 'diamond-tall'}, 'type': 'scatter', 'x': [6], 'xaxis': 'x2', - 'y': [0], + 'y': [0.5], 'yaxis': 'y2'}, {'marker': {'color': 'rgb(181,221,232)'}, 'mode': 'lines', @@ -2823,6 +2866,15 @@ def test_full_sparkline_fig(self): 'xaxis': 'x3', 'y': [5, 6, 4], 'yaxis': 'y3'}, + {'fill': 'tozeroy', + 'hoverinfo': 'none', + 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'mode': 'lines', + 'type': 'scatter', + 'x': [0, 3], + 'xaxis': 'x3', + 'y': [12, 12], + 'yaxis': 'y3'}, {'marker': {'color': 'rgb(62,151,169)'}, 'mode': 'markers', 'type': 'scatter', @@ -2830,12 +2882,30 @@ def test_full_sparkline_fig(self): 'xaxis': 'x3', 'y': [4], 'yaxis': 'y3'}, + {'fill': 'tozeroy', + 'hoverinfo': 'none', + 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'mode': 'line', + 'type': 'scatter', + 'x': [0, 2], + 'xaxis': 'x4', + 'y': [1.2, 1.2], + 'yaxis': 'y4'}, {'type': 'bar', 'visible': False, 'x': [0], 'xaxis': 'x4', 'y': [0], 'yaxis': 'y4'}, + {'fill': 'tozeroy', + 'hoverinfo': 'none', + 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'mode': 'lines', + 'type': 'scatter', + 'x': [-1, 3], + 'xaxis': 'x5', + 'y': [12, 12], + 'yaxis': 'y5'}, {'marker': {'color': ['rgb(181,221,232)', 'rgb(181,221,232)', 'rgb(62,151,169)']}, @@ -2844,33 +2914,55 @@ def test_full_sparkline_fig(self): 'xaxis': 'x5', 'y': [5, 6, 4], 'yaxis': 'y5'}, + {'fill': 'tozeroy', + 'hoverinfo': 'none', + 'line': {'color': 'rgba(249, 247, 244, 0.5)', 'width': 0}, + 'mode': 'line', + 'type': 'scatter', + 'x': [0, 1], + 'xaxis': 'x6', + 'y': [1.2, 1.2], + 'yaxis': 'y6'}, {'type': 'bar', 'visible': False, 'x': [0], 'xaxis': 'x6', 'y': [0], 'yaxis': 'y6'}, - {'marker': {'color': 'rgb(181,221,232)'}, + {'fill': 'tozeroy', + 'hoverinfo': 'none', + 'line': {'color': 'rgba(249, 247, 244, 0.5)', 'width': 0}, + 'mode': 'lines', + 'type': 'scatter', + 'x': [0, 14], + 'xaxis': 'x7', + 'y': [1, 1], + 'yaxis': 'y7'}, + {'hoverinfo': 'x', + 'marker': {'color': 'rgb(181,221,232)'}, 'orientation': 'h', 'type': 'bar', + 'width': 0.5, 'x': [4.67], 'xaxis': 'x7', - 'y': [0], + 'y': [0.5], 'yaxis': 'y7'}, - {'marker': {'color': 'rgb(62,151,169)'}, - 'offset': -0.1, + {'hoverinfo': 'x', + 'marker': {'color': 'rgb(62,151,169)'}, + 'offset': -0.07, 'orientation': 'h', 'type': 'bar', - 'width': 0.2, + 'width': 0.14, 'x': [7], 'xaxis': 'x7', - 'y': [0], + 'y': [0.5], 'yaxis': 'y7'}, - {'marker': {'color': 'rgb(0,0,0)', 'size': 9, 'symbol': 'diamond-tall'}, + {'hoverinfo': 'x', + 'marker': {'color': 'rgb(0,0,0)', 'size': 9, 'symbol': 'diamond-tall'}, 'type': 'scatter', 'x': [7], 'xaxis': 'x7', - 'y': [0], + 'y': [0.5], 'yaxis': 'y7'}, {'marker': {'color': 'rgb(181,221,232)'}, 'mode': 'lines', @@ -2879,6 +2971,15 @@ def test_full_sparkline_fig(self): 'xaxis': 'x8', 'y': [2, 5, 7], 'yaxis': 'y8'}, + {'fill': 'tozeroy', + 'hoverinfo': 'none', + 'line': {'color': 'rgba(249, 247, 244, 0.5)', 'width': 0}, + 'mode': 'lines', + 'type': 'scatter', + 'x': [0, 3], + 'xaxis': 'x8', + 'y': [14, 14], + 'yaxis': 'y8'}, {'marker': {'color': 'rgb(62,151,169)'}, 'mode': 'markers', 'type': 'scatter', @@ -2886,12 +2987,30 @@ def test_full_sparkline_fig(self): 'xaxis': 'x8', 'y': [7], 'yaxis': 'y8'}, + {'fill': 'tozeroy', + 'hoverinfo': 'none', + 'line': {'color': 'rgba(249, 247, 244, 0.5)', 'width': 0}, + 'mode': 'line', + 'type': 'scatter', + 'x': [0, 2], + 'xaxis': 'x9', + 'y': [1.2, 1.2], + 'yaxis': 'y9'}, {'type': 'bar', 'visible': False, 'x': [0], 'xaxis': 'x9', 'y': [0], 'yaxis': 'y9'}, + {'fill': 'tozeroy', + 'hoverinfo': 'none', + 'line': {'color': 'rgba(249, 247, 244, 0.5)', 'width': 0}, + 'mode': 'lines', + 'type': 'scatter', + 'x': [-1, 3], + 'xaxis': 'x10', + 'y': [14, 14], + 'yaxis': 'y10'}, {'marker': {'color': ['rgb(181,221,232)', 'rgb(181,221,232)', 'rgb(62,151,169)']}, @@ -2900,33 +3019,55 @@ def test_full_sparkline_fig(self): 'xaxis': 'x10', 'y': [2, 5, 7], 'yaxis': 'y10'}, + {'fill': 'tozeroy', + 'hoverinfo': 'none', + 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'mode': 'line', + 'type': 'scatter', + 'x': [0, 1], + 'xaxis': 'x11', + 'y': [1.2, 1.2], + 'yaxis': 'y11'}, {'type': 'bar', 'visible': False, 'x': [0], 'xaxis': 'x11', 'y': [0], 'yaxis': 'y11'}, - {'marker': {'color': 'rgb(181,221,232)'}, + {'fill': 'tozeroy', + 'hoverinfo': 'none', + 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'mode': 'lines', + 'type': 'scatter', + 'x': [0, 18], + 'xaxis': 'x12', + 'y': [1, 1], + 'yaxis': 'y12'}, + {'hoverinfo': 'x', + 'marker': {'color': 'rgb(181,221,232)'}, 'orientation': 'h', 'type': 'bar', + 'width': 0.5, 'x': [6.33], 'xaxis': 'x12', - 'y': [0], + 'y': [0.5], 'yaxis': 'y12'}, - {'marker': {'color': 'rgb(62,151,169)'}, - 'offset': -0.1, + {'hoverinfo': 'x', + 'marker': {'color': 'rgb(62,151,169)'}, + 'offset': -0.07, 'orientation': 'h', 'type': 'bar', - 'width': 0.2, + 'width': 0.14, 'x': [6], 'xaxis': 'x12', - 'y': [0], + 'y': [0.5], 'yaxis': 'y12'}, - {'marker': {'color': 'rgb(0,0,0)', 'size': 9, 'symbol': 'diamond-tall'}, + {'hoverinfo': 'x', + 'marker': {'color': 'rgb(0,0,0)', 'size': 9, 'symbol': 'diamond-tall'}, 'type': 'scatter', 'x': [9], 'xaxis': 'x12', - 'y': [0], + 'y': [0.5], 'yaxis': 'y12'}, {'marker': {'color': 'rgb(181,221,232)'}, 'mode': 'lines', @@ -2935,6 +3076,15 @@ def test_full_sparkline_fig(self): 'xaxis': 'x13', 'y': [9, 4, 6], 'yaxis': 'y13'}, + {'fill': 'tozeroy', + 'hoverinfo': 'none', + 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'mode': 'lines', + 'type': 'scatter', + 'x': [0, 3], + 'xaxis': 'x13', + 'y': [18, 18], + 'yaxis': 'y13'}, {'marker': {'color': 'rgb(62,151,169)'}, 'mode': 'markers', 'type': 'scatter', @@ -2942,12 +3092,30 @@ def test_full_sparkline_fig(self): 'xaxis': 'x13', 'y': [6], 'yaxis': 'y13'}, + {'fill': 'tozeroy', + 'hoverinfo': 'none', + 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'mode': 'line', + 'type': 'scatter', + 'x': [0, 2], + 'xaxis': 'x14', + 'y': [1.2, 1.2], + 'yaxis': 'y14'}, {'type': 'bar', 'visible': False, 'x': [0], 'xaxis': 'x14', 'y': [0], 'yaxis': 'y14'}, + {'fill': 'tozeroy', + 'hoverinfo': 'none', + 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'mode': 'lines', + 'type': 'scatter', + 'x': [-1, 3], + 'xaxis': 'x15', + 'y': [18, 18], + 'yaxis': 'y15'}, {'marker': {'color': ['rgb(181,221,232)', 'rgb(181,221,232)', 'rgb(62,151,169)']}, @@ -2959,180 +3127,130 @@ def test_full_sparkline_fig(self): 'layout': {'annotations': [{'font': {'size': 15}, 'showarrow': False, 'text': 'foo', - 'x': 1, - 'xanchor': 'right', + 'x': 0.5, + 'xanchor': 'center', 'xref': 'x1', 'y': 0.5, 'yref': 'y1'}, {'font': {'size': 15}, 'showarrow': False, 'text': '5.0', - 'x': 1, - 'xanchor': 'right', + 'x': 0.5, + 'xanchor': 'center', 'xref': 'x4', 'y': 0.5, 'yref': 'y4'}, {'font': {'size': 15}, 'showarrow': False, 'text': 'bar', - 'x': 1, - 'xanchor': 'right', + 'x': 0.5, + 'xanchor': 'center', 'xref': 'x6', 'y': 0.5, 'yref': 'y6'}, {'font': {'size': 15}, 'showarrow': False, 'text': '4.67', - 'x': 1, - 'xanchor': 'right', + 'x': 0.5, + 'xanchor': 'center', 'xref': 'x9', 'y': 0.5, 'yref': 'y9'}, {'font': {'size': 15}, 'showarrow': False, 'text': 'baz', - 'x': 1, - 'xanchor': 'right', + 'x': 0.5, + 'xanchor': 'center', 'xref': 'x11', 'y': 0.5, 'yref': 'y11'}, {'font': {'size': 15}, 'showarrow': False, 'text': '6.33', - 'x': 1, - 'xanchor': 'right', + 'x': 0.5, + 'xanchor': 'center', 'xref': 'x14', 'y': 0.5, - 'yref': 'y14'}, - {'font': {'color': '#0f0f0f', 'size': 13}, - 'showarrow': False, - 'text': 'name', - 'textangle': 0, - 'x': 0.03333333333333333, - 'xanchor': 'center', - 'xref': 'paper', - 'y': 1.03, - 'yanchor': 'middle', - 'yref': 'paper'}, - {'font': {'color': '#0f0f0f', 'size': 13}, - 'showarrow': False, - 'text': 'bullet', - 'textangle': 0, - 'x': 0.13333333333333333, - 'xanchor': 'center', - 'xref': 'paper', - 'y': 1.03, - 'yanchor': 'middle', - 'yref': 'paper'}, - {'font': {'color': '#0f0f0f', 'size': 13}, - 'showarrow': False, - 'text': 'line', - 'textangle': 0, - 'x': 0.30000000000000004, - 'xanchor': 'center', - 'xref': 'paper', - 'y': 1.03, - 'yanchor': 'middle', - 'yref': 'paper'}, - {'font': {'color': '#0f0f0f', 'size': 13}, - 'showarrow': False, - 'text': 'avg', - 'textangle': 0, - 'x': 0.5333333333333333, - 'xanchor': 'center', - 'xref': 'paper', - 'y': 1.03, - 'yanchor': 'middle', - 'yref': 'paper'}, - {'font': {'color': '#0f0f0f', 'size': 13}, - 'showarrow': False, - 'text': 'bar', - 'textangle': 0, - 'x': 0.8333333333333333, - 'xanchor': 'center', - 'xref': 'paper', - 'y': 1.03, - 'yanchor': 'middle', - 'yref': 'paper'}], + 'yref': 'y14'}], 'showlegend': False, 'title': 'this is a test', 'xaxis1': {'anchor': 'y1', - 'domain': [0.0, 0.06666666666666667], + 'domain': [0.0, 0.058666666666666666], 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis10': {'anchor': 'y10', - 'domain': [0.6666666666666667, 1.0], + 'domain': [0.7066666666666667, 1.0], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis11': {'anchor': 'y11', - 'domain': [0.0, 0.06666666666666667], + 'domain': [0.0, 0.058666666666666666], 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis12': {'anchor': 'y12', - 'domain': [0.06666666666666667, 0.2], + 'domain': [0.08866666666666667, 0.20600000000000002], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis13': {'anchor': 'y13', - 'domain': [0.2, 0.4], + 'domain': [0.236, 0.41200000000000003], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis14': {'anchor': 'y14', - 'domain': [0.4, 0.6666666666666667], + 'domain': [0.44199999999999995, 0.6766666666666666], 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis15': {'anchor': 'y15', - 'domain': [0.6666666666666667, 1.0], + 'domain': [0.7066666666666667, 1.0], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis2': {'anchor': 'y2', - 'domain': [0.06666666666666667, 0.2], + 'domain': [0.08866666666666667, 0.20600000000000002], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis3': {'anchor': 'y3', - 'domain': [0.2, 0.4], + 'domain': [0.236, 0.41200000000000003], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis4': {'anchor': 'y4', - 'domain': [0.4, 0.6666666666666667], + 'domain': [0.44199999999999995, 0.6766666666666666], 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis5': {'anchor': 'y5', - 'domain': [0.6666666666666667, 1.0], + 'domain': [0.7066666666666667, 1.0], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis6': {'anchor': 'y6', - 'domain': [0.0, 0.06666666666666667], + 'domain': [0.0, 0.058666666666666666], 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis7': {'anchor': 'y7', - 'domain': [0.06666666666666667, 0.2], + 'domain': [0.08866666666666667, 0.20600000000000002], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis8': {'anchor': 'y8', - 'domain': [0.2, 0.4], + 'domain': [0.236, 0.41200000000000003], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis9': {'anchor': 'y9', - 'domain': [0.4, 0.6666666666666667], + 'domain': [0.44199999999999995, 0.6766666666666666], 'range': [0, 1], 'showgrid': False, 'showticklabels': False, @@ -3145,6 +3263,7 @@ def test_full_sparkline_fig(self): 'zeroline': False}, 'yaxis10': {'anchor': 'x10', 'domain': [0.3333333333333333, 0.6666666666666666], + 'range': [0, 14], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, @@ -3156,11 +3275,13 @@ def test_full_sparkline_fig(self): 'zeroline': False}, 'yaxis12': {'anchor': 'x12', 'domain': [0.0, 0.3333333333333333], + 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis13': {'anchor': 'x13', 'domain': [0.0, 0.3333333333333333], + 'range': [0, 18], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, @@ -3172,16 +3293,19 @@ def test_full_sparkline_fig(self): 'zeroline': False}, 'yaxis15': {'anchor': 'x15', 'domain': [0.0, 0.3333333333333333], + 'range': [0, 18], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis2': {'anchor': 'x2', 'domain': [0.6666666666666666, 1.0], + 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis3': {'anchor': 'x3', 'domain': [0.6666666666666666, 1.0], + 'range': [0, 12], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, @@ -3193,6 +3317,7 @@ def test_full_sparkline_fig(self): 'zeroline': False}, 'yaxis5': {'anchor': 'x5', 'domain': [0.6666666666666666, 1.0], + 'range': [0, 12], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, @@ -3204,11 +3329,13 @@ def test_full_sparkline_fig(self): 'zeroline': False}, 'yaxis7': {'anchor': 'x7', 'domain': [0.3333333333333333, 0.6666666666666666], + 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis8': {'anchor': 'x8', 'domain': [0.3333333333333333, 0.6666666666666666], + 'range': [0, 14], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, @@ -3220,10 +3347,6 @@ def test_full_sparkline_fig(self): 'zeroline': False}} } - fig = ff.create_sparkline( - df, chart_types=['name', 'bullet', 'line', 'avg', 'bar'], - column_width=[1, 2, 3, 4, 5], scatter_options={'marker': {'color': 'rgb(0,0,0)'}}, - title='this is a test' - ) - - self.assert_dict_equal(fig, exp_fig) + #self.assert_dict_equal(fig['data'], exp_fig['data']) + self.assertEqual(fig['data'], exp_fig['data']) + self.assertEqual(fig['layout'], exp_fig['layout']) From 50a78e334f1ef894ef7552358903b46ee6ec95e1 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Fri, 8 Dec 2017 15:33:11 -0500 Subject: [PATCH 07/19] default horizontal_spacing to 0.0 --- plotly/figure_factory/_sparkline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plotly/figure_factory/_sparkline.py b/plotly/figure_factory/_sparkline.py index 7a5011ddb04..a1241329837 100644 --- a/plotly/figure_factory/_sparkline.py +++ b/plotly/figure_factory/_sparkline.py @@ -15,7 +15,7 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, colors=('rgb(181,221,232)', 'rgb(62,151,169)'), column_width=None, show_titles=False, textalign='center', - horizontal_spacing=0.03, vertical_spacing=0, + horizontal_spacing=0.0, vertical_spacing=0.0, alternate_row_color=True, lane_colors=('rgba(249, 247, 244, 0.5)', 'rgba(255, 253, 250, 0.5)'), From 002d30e9dc31ce634f2bae80b7663f8157967602 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Mon, 11 Dec 2017 12:01:46 -0500 Subject: [PATCH 08/19] disabled zoom --- plotly/figure_factory/_sparkline.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/plotly/figure_factory/_sparkline.py b/plotly/figure_factory/_sparkline.py index a1241329837..7d6ba16b53b 100644 --- a/plotly/figure_factory/_sparkline.py +++ b/plotly/figure_factory/_sparkline.py @@ -17,8 +17,8 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, column_width=None, show_titles=False, textalign='center', horizontal_spacing=0.0, vertical_spacing=0.0, alternate_row_color=True, - lane_colors=('rgba(249, 247, 244, 0.5)', - 'rgba(255, 253, 250, 0.5)'), + lane_colors=('rgba(249, 247, 244, 1.0)', + 'rgba(255, 253, 250, 1.0)'), scatter_options=None, **layout_options): """ Returns figure for sparkline. @@ -39,8 +39,10 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, above their respective column :param (str) textalign: aligns name and avg cells. Use either 'center', 'left', or 'right'. Default='center'. - :param (float) horizontal_spacing: - :param (float) vertical_spacing : + :param (float) horizontal_spacing: Space between subplot columns. + Applied to all columns + :param (float) vertical_spacing: Space between subplot rows. + Applied to all rows :param (float) alternate_row_color: set to True to enable the alternate row coloring of the chart. Uses the colors from param 'lane_colors' :param (list) lane_colors: a list/tuple of two colors that are used to @@ -97,7 +99,7 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, # layout options fig['layout'].update( - title='Sparkline Chart', + title='', annotations=[], showlegend=False ) @@ -217,7 +219,7 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, if alternate_row_color: bkgcolor = go.Scatter( - x=[0, 2 * max(df[key])], + x=[0, max(df[key])], y=[1, 1], fill='tozeroy', mode='lines', @@ -236,7 +238,7 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, fig['layout']['yaxis{}'.format( j * num_of_chart_types + (c + 1) - )]['range'] = [0, 1] + )]['range'] = [0, 1] elif chart == 'line': trace_line = go.Scatter( x=range(len(df[key])), @@ -347,6 +349,10 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, VALID_CHART_TYPES, 'or') ) ) + for x_y in ['xaxis', 'yaxis']: + fig['layout']['{}{}'.format( + x_y, j * num_of_chart_types + (c + 1) + )]['fixedrange'] = True # show titles if show_titles: From 9c0953feb62cf18b23296dfbfa503a8ce4900772 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Mon, 11 Dec 2017 12:10:12 -0500 Subject: [PATCH 09/19] update tests --- .../test_optional/test_figure_factory.py | 99 ++++++++++++------- 1 file changed, 64 insertions(+), 35 deletions(-) diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index 5e67fd26c40..fccf50ee307 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -2809,9 +2809,9 @@ def test_full_sparkline_fig(self): ) exp_fig = { - 'data': [{'fill': 'tozeroy', + 'data': [{'fill': 'tozeroy', 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, 'mode': 'line', 'type': 'scatter', 'x': [0, 1], @@ -2826,10 +2826,10 @@ def test_full_sparkline_fig(self): 'yaxis': 'y1'}, {'fill': 'tozeroy', 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, 'mode': 'lines', 'type': 'scatter', - 'x': [0, 12], + 'x': [0, 6], 'xaxis': 'x2', 'y': [1, 1], 'yaxis': 'y2'}, @@ -2868,7 +2868,7 @@ def test_full_sparkline_fig(self): 'yaxis': 'y3'}, {'fill': 'tozeroy', 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, 'mode': 'lines', 'type': 'scatter', 'x': [0, 3], @@ -2884,7 +2884,7 @@ def test_full_sparkline_fig(self): 'yaxis': 'y3'}, {'fill': 'tozeroy', 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, 'mode': 'line', 'type': 'scatter', 'x': [0, 2], @@ -2899,7 +2899,7 @@ def test_full_sparkline_fig(self): 'yaxis': 'y4'}, {'fill': 'tozeroy', 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, 'mode': 'lines', 'type': 'scatter', 'x': [-1, 3], @@ -2916,7 +2916,7 @@ def test_full_sparkline_fig(self): 'yaxis': 'y5'}, {'fill': 'tozeroy', 'hoverinfo': 'none', - 'line': {'color': 'rgba(249, 247, 244, 0.5)', 'width': 0}, + 'line': {'color': 'rgba(249, 247, 244, 1.0)', 'width': 0}, 'mode': 'line', 'type': 'scatter', 'x': [0, 1], @@ -2931,10 +2931,10 @@ def test_full_sparkline_fig(self): 'yaxis': 'y6'}, {'fill': 'tozeroy', 'hoverinfo': 'none', - 'line': {'color': 'rgba(249, 247, 244, 0.5)', 'width': 0}, + 'line': {'color': 'rgba(249, 247, 244, 1.0)', 'width': 0}, 'mode': 'lines', 'type': 'scatter', - 'x': [0, 14], + 'x': [0, 7], 'xaxis': 'x7', 'y': [1, 1], 'yaxis': 'y7'}, @@ -2973,7 +2973,7 @@ def test_full_sparkline_fig(self): 'yaxis': 'y8'}, {'fill': 'tozeroy', 'hoverinfo': 'none', - 'line': {'color': 'rgba(249, 247, 244, 0.5)', 'width': 0}, + 'line': {'color': 'rgba(249, 247, 244, 1.0)', 'width': 0}, 'mode': 'lines', 'type': 'scatter', 'x': [0, 3], @@ -2989,7 +2989,7 @@ def test_full_sparkline_fig(self): 'yaxis': 'y8'}, {'fill': 'tozeroy', 'hoverinfo': 'none', - 'line': {'color': 'rgba(249, 247, 244, 0.5)', 'width': 0}, + 'line': {'color': 'rgba(249, 247, 244, 1.0)', 'width': 0}, 'mode': 'line', 'type': 'scatter', 'x': [0, 2], @@ -3004,7 +3004,7 @@ def test_full_sparkline_fig(self): 'yaxis': 'y9'}, {'fill': 'tozeroy', 'hoverinfo': 'none', - 'line': {'color': 'rgba(249, 247, 244, 0.5)', 'width': 0}, + 'line': {'color': 'rgba(249, 247, 244, 1.0)', 'width': 0}, 'mode': 'lines', 'type': 'scatter', 'x': [-1, 3], @@ -3021,7 +3021,7 @@ def test_full_sparkline_fig(self): 'yaxis': 'y10'}, {'fill': 'tozeroy', 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, 'mode': 'line', 'type': 'scatter', 'x': [0, 1], @@ -3036,10 +3036,10 @@ def test_full_sparkline_fig(self): 'yaxis': 'y11'}, {'fill': 'tozeroy', 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, 'mode': 'lines', 'type': 'scatter', - 'x': [0, 18], + 'x': [0, 9], 'xaxis': 'x12', 'y': [1, 1], 'yaxis': 'y12'}, @@ -3078,7 +3078,7 @@ def test_full_sparkline_fig(self): 'yaxis': 'y13'}, {'fill': 'tozeroy', 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, 'mode': 'lines', 'type': 'scatter', 'x': [0, 3], @@ -3094,7 +3094,7 @@ def test_full_sparkline_fig(self): 'yaxis': 'y13'}, {'fill': 'tozeroy', 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, 'mode': 'line', 'type': 'scatter', 'x': [0, 2], @@ -3109,7 +3109,7 @@ def test_full_sparkline_fig(self): 'yaxis': 'y14'}, {'fill': 'tozeroy', 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 0.5)', 'width': 0}, + 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, 'mode': 'lines', 'type': 'scatter', 'x': [-1, 3], @@ -3175,178 +3175,207 @@ def test_full_sparkline_fig(self): 'showlegend': False, 'title': 'this is a test', 'xaxis1': {'anchor': 'y1', - 'domain': [0.0, 0.058666666666666666], + 'domain': [0.0, 0.06666666666666667], + 'fixedrange': True, 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis10': {'anchor': 'y10', - 'domain': [0.7066666666666667, 1.0], + 'domain': [0.6666666666666667, 1.0], + 'fixedrange': True, 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis11': {'anchor': 'y11', - 'domain': [0.0, 0.058666666666666666], + 'domain': [0.0, 0.06666666666666667], + 'fixedrange': True, 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis12': {'anchor': 'y12', - 'domain': [0.08866666666666667, 0.20600000000000002], + 'domain': [0.06666666666666667, 0.2], + 'fixedrange': True, 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis13': {'anchor': 'y13', - 'domain': [0.236, 0.41200000000000003], + 'domain': [0.2, 0.4], + 'fixedrange': True, 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis14': {'anchor': 'y14', - 'domain': [0.44199999999999995, 0.6766666666666666], + 'domain': [0.4, 0.6666666666666667], + 'fixedrange': True, 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis15': {'anchor': 'y15', - 'domain': [0.7066666666666667, 1.0], + 'domain': [0.6666666666666667, 1.0], + 'fixedrange': True, 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis2': {'anchor': 'y2', - 'domain': [0.08866666666666667, 0.20600000000000002], + 'domain': [0.06666666666666667, 0.2], + 'fixedrange': True, 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis3': {'anchor': 'y3', - 'domain': [0.236, 0.41200000000000003], + 'domain': [0.2, 0.4], + 'fixedrange': True, 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis4': {'anchor': 'y4', - 'domain': [0.44199999999999995, 0.6766666666666666], + 'domain': [0.4, 0.6666666666666667], + 'fixedrange': True, 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis5': {'anchor': 'y5', - 'domain': [0.7066666666666667, 1.0], + 'domain': [0.6666666666666667, 1.0], + 'fixedrange': True, 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis6': {'anchor': 'y6', - 'domain': [0.0, 0.058666666666666666], + 'domain': [0.0, 0.06666666666666667], + 'fixedrange': True, 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis7': {'anchor': 'y7', - 'domain': [0.08866666666666667, 0.20600000000000002], + 'domain': [0.06666666666666667, 0.2], + 'fixedrange': True, 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis8': {'anchor': 'y8', - 'domain': [0.236, 0.41200000000000003], + 'domain': [0.2, 0.4], + 'fixedrange': True, 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'xaxis9': {'anchor': 'y9', - 'domain': [0.44199999999999995, 0.6766666666666666], + 'domain': [0.4, 0.6666666666666667], + 'fixedrange': True, 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis1': {'anchor': 'x1', 'domain': [0.6666666666666666, 1.0], + 'fixedrange': True, 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis10': {'anchor': 'x10', 'domain': [0.3333333333333333, 0.6666666666666666], + 'fixedrange': True, 'range': [0, 14], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis11': {'anchor': 'x11', 'domain': [0.0, 0.3333333333333333], + 'fixedrange': True, 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis12': {'anchor': 'x12', 'domain': [0.0, 0.3333333333333333], + 'fixedrange': True, 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis13': {'anchor': 'x13', 'domain': [0.0, 0.3333333333333333], + 'fixedrange': True, 'range': [0, 18], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis14': {'anchor': 'x14', 'domain': [0.0, 0.3333333333333333], + 'fixedrange': True, 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis15': {'anchor': 'x15', 'domain': [0.0, 0.3333333333333333], + 'fixedrange': True, 'range': [0, 18], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis2': {'anchor': 'x2', 'domain': [0.6666666666666666, 1.0], + 'fixedrange': True, 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis3': {'anchor': 'x3', 'domain': [0.6666666666666666, 1.0], + 'fixedrange': True, 'range': [0, 12], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis4': {'anchor': 'x4', 'domain': [0.6666666666666666, 1.0], + 'fixedrange': True, 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis5': {'anchor': 'x5', 'domain': [0.6666666666666666, 1.0], + 'fixedrange': True, 'range': [0, 12], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis6': {'anchor': 'x6', 'domain': [0.3333333333333333, 0.6666666666666666], + 'fixedrange': True, 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis7': {'anchor': 'x7', 'domain': [0.3333333333333333, 0.6666666666666666], + 'fixedrange': True, 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis8': {'anchor': 'x8', 'domain': [0.3333333333333333, 0.6666666666666666], + 'fixedrange': True, 'range': [0, 14], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, 'yaxis9': {'anchor': 'x9', 'domain': [0.3333333333333333, 0.6666666666666666], + 'fixedrange': True, 'range': [0, 1], 'showgrid': False, 'showticklabels': False, 'zeroline': False}} } - #self.assert_dict_equal(fig['data'], exp_fig['data']) self.assertEqual(fig['data'], exp_fig['data']) self.assertEqual(fig['layout'], exp_fig['layout']) From 8d5b11424cf254e29de7b5acfc7e27ae23e4395b Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Mon, 11 Dec 2017 16:13:06 -0500 Subject: [PATCH 10/19] scatter_options affect line and bullet minicharts --- plotly/figure_factory/_sparkline.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plotly/figure_factory/_sparkline.py b/plotly/figure_factory/_sparkline.py index 7d6ba16b53b..ba1330f7564 100644 --- a/plotly/figure_factory/_sparkline.py +++ b/plotly/figure_factory/_sparkline.py @@ -162,7 +162,7 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, xanchor=xanchor, text=key, showarrow=False, - font=dict(size=15), + font=dict(size=12), ) ) empty_data = go.Bar( @@ -217,9 +217,10 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, **scatter_options ) + range_e = max(df[key]) + 0.5 * rounded_mean if alternate_row_color: bkgcolor = go.Scatter( - x=[0, max(df[key])], + x=[0, range_e], y=[1, 1], fill='tozeroy', mode='lines', @@ -236,6 +237,9 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, fig.append_trace(bullet_measure, j + 1, c + 1) fig.append_trace(bullet_pt, j + 1, c + 1) + fig['layout']['xaxis{}'.format( + j * num_of_chart_types + (c + 1) + )]['range'] = [0, range_e] fig['layout']['yaxis{}'.format( j * num_of_chart_types + (c + 1) )]['range'] = [0, 1] @@ -256,7 +260,8 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, mode='markers', marker=dict( color=colors[1] - ) + ), + **scatter_options ) if alternate_row_color: @@ -288,7 +293,7 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, xanchor=xanchor, text='{}'.format(rounded_mean), showarrow=False, - font=dict(size=15), + font=dict(size=12), ) ) empty_data = go.Bar( From 20e98be262d76bf9e80ba0b7ac7540db3a63bfd6 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Thu, 14 Dec 2017 16:10:44 -0500 Subject: [PATCH 11/19] reworked row_colors and trace_colors to be robust --- plotly/figure_factory/_sparkline.py | 324 ++++++++++------- .../test_optional/test_figure_factory.py | 340 +++++++++--------- 2 files changed, 364 insertions(+), 300 deletions(-) diff --git a/plotly/figure_factory/_sparkline.py b/plotly/figure_factory/_sparkline.py index ba1330f7564..3ea648ea5ef 100644 --- a/plotly/figure_factory/_sparkline.py +++ b/plotly/figure_factory/_sparkline.py @@ -9,46 +9,76 @@ import numpy as np pd = optional_imports.get_module('pandas') -VALID_CHART_TYPES = ('name', 'bullet', 'line', 'avg', 'bar') +VALID_CHART_TYPES = ('label', 'bullet', 'line', 'avg', 'bar', 'area') + + +def rect(xref, yref, x0, x1, y0, y1, color): + shape = { + 'layer': 'below', + 'xref': xref, + 'yref': yref, + 'x0': x0, + 'x1': x1, + 'y0': y0, + 'y1': y1, + 'fillcolor': color, + 'line': {'width': 0} + } + return shape -def create_sparkline(df, chart_types=VALID_CHART_TYPES, - colors=('rgb(181,221,232)', 'rgb(62,151,169)'), - column_width=None, show_titles=False, textalign='center', - horizontal_spacing=0.0, vertical_spacing=0.0, - alternate_row_color=True, - lane_colors=('rgba(249, 247, 244, 1.0)', - 'rgba(255, 253, 250, 1.0)'), - scatter_options=None, **layout_options): +def create_sparkline(df, chart_types=VALID_CHART_TYPES, trace_colors=None, + column_width=None, show_titles=False, + text_align='center', horizontal_spacing=0.0, + vertical_spacing=0.0, alternate_row_color=True, + row_colors=('rgb(247, 247, 242)', + 'rgb(255, 253, 250)'), + line_width=2, scatter_options=None, **layout_options): """ Returns figure for sparkline. :param (pd.DataFrame | list | tuple) df: either a list/tuple of dictionaries or a pandas DataFrame. :param (list|tuple) chart_types: a sequence of any combination of valid - chart types. The valid chart types are 'name', 'bullet', 'line', 'avg' + chart types. The valid chart types are 'label', 'bullet', 'line', 'avg' and 'bar' - :param (list|tuple) colors: a sequence of exactly 2 colors which are used - to color the charts. Set the first color to your ___ color and the - second color as your ___ + :param (list|tuple) trace_colors: a list of colors or a list of lists of + two colors. Each row uses two colors: a darker one and a lighter one. + Options: + - list of 2 colors: first color is dark color for all traces and + second is light for all traces. + - 1D list of more than 2 colors: the nth color in the list is the + nth dark color for the traces and the associated light color is + just 0.5 times the opacity of the dark color + - lists of lists: each inner list must have exactly 2 colors in it + and the first and second color of the nth inner list represent + the dark and light color for the nth row repsectively + - list of lists and colors: this is a combination of the previous + options + Whenever trace_colors has fewer colors than the number of rows of the + figure, the colors will repeat from the start of the list Default = ('rgb(181,221,232)', 'rgb(62,151,169)') :param (list) column_width: Specify a list that contains numbers where the amount of numbers in the list is equal to `chart_types`. Call `help(plotly.tools.make_subplots)` for more info on this subplot param :param (bool) show_titles: determines if title of chart type is displayed above their respective column - :param (str) textalign: aligns name and avg cells. Use either 'center', + :param (str) text_align: aligns label and avg cells. Use either 'center', 'left', or 'right'. Default='center'. :param (float) horizontal_spacing: Space between subplot columns. Applied to all columns :param (float) vertical_spacing: Space between subplot rows. Applied to all rows :param (float) alternate_row_color: set to True to enable the alternate - row coloring of the chart. Uses the colors from param 'lane_colors' - :param (list) lane_colors: a list/tuple of two colors that are used to - alternately color the rows of the chart. + row coloring of the chart. Uses the trace_colors from param 'row_colors' + :param (list) row_colors: a list/tuple of colors that are used to + alternately color the rows of the chart. If the number of colors in the + list is fewer than the number of rows, the active color for the layout + will be looped back to the first in the list + :param (float) line_width: sets the width of the lines used in 'area' or + filled area line charts :param (dict) scatter_options: describes attributes for the scatter point - in each bullet chart such as name and marker size. Call + in each bullet chart such as label and marker size. Call help(plotly.graph_objs.Scatter) for more information on valid params. :param layout_options: describes attributes for the layout of the figure such as title, height and width. Call help(plotly.graph_objs.Layout) @@ -65,21 +95,34 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, 'df must be a pandas DataFrame' ) - # validate list/tuple of colors - if not utils.is_sequence(colors): + # validate list/tuple of trace_colors + if not trace_colors: + trace_colors = [['rgb(62,151,169)', 'rgb(181,221,232)']] + if not utils.is_sequence(trace_colors): raise exceptions.PlotlyError( - 'colors must be a list/tuple' + 'trace_colors must be a list/tuple' ) - if len(colors) < 2: - raise exceptions.PlotlyError( - 'colors must be a list/tuple with 2 colors inside' - ) - plotly.colors.validate_colors(colors) + trace_colors_2d = [] + for i, item in enumerate(trace_colors): + plotly.colors.validate_colors(item) + if utils.is_sequence(item): + trace_colors_2d.append(item) + else: + # if hex convert to rgb + if '#' in item: + tuple_item = plotly.colors.hex_to_rgb(item) + rgb_item = plotly.colors.label_rgb(tuple_item) + else: + rgb_item = item + light_c = plotly.colors.find_intermediate_color( + rgb_item, 'rgb(255, 255, 255)', 0.5, colortype='rgb' + ) + trace_colors_2d.append([rgb_item, light_c]) num_of_chart_types = len(chart_types) - # narrow columns that are 'name' or 'avg' - narrow_cols = ['name', 'avg'] + # narrow columns that are 'label' or 'avg' + narrow_cols = ['label', 'avg'] narrow_idxs = [] for i, chart in enumerate(chart_types): if chart in narrow_cols: @@ -101,6 +144,7 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, fig['layout'].update( title='', annotations=[], + shapes=[], showlegend=False ) @@ -116,24 +160,23 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, ) # text alignment - xanchor = textalign - if textalign == 'left': + xanchor = text_align + if text_align == 'left': x = 0 - elif textalign == 'center': + elif text_align == 'center': x = 0.5 - elif textalign == 'right': + elif text_align == 'right': x = 1 else: raise exceptions.PlotlyError( - 'textalign must be left, center or right' + 'text_align must be left, center or right' ) # scatter options default_scatter = { 'mode': 'markers', 'marker': {'size': 9, - 'symbol': 'diamond-tall', - 'color': colors[1]} + 'symbol': 'diamond-tall'} } if not scatter_options: @@ -144,15 +187,35 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, else: # add default options to scatter_options if they are not present for k in default_scatter['marker']: - if k not in scatter_options['marker']: + try: + if k not in scatter_options['marker']: + scatter_options['marker'][k] = default_scatter['marker'][k] + except KeyError: + scatter_options['marker'] = {} scatter_options['marker'][k] = default_scatter['marker'][k] + if 'marker' in scatter_options and 'color' in scatter_options['marker']: + new_marker_color = True + else: + new_marker_color = False + # create and insert charts + c_idx = 0 + trace_c_idx = 0 for j, key in enumerate(df): for c, chart in enumerate(chart_types): mean = np.mean(df[key]) rounded_mean = round(mean, 2) - if chart == 'name': + # update indices + if c_idx >= len(row_colors): + c_idx = 0 + r_color = row_colors[c_idx] + + if trace_c_idx >= len(trace_colors_2d): + trace_c_idx = 0 + dark_color = trace_colors_2d[trace_c_idx][0] + light_color = trace_colors_2d[trace_c_idx][1] + if chart == 'label': fig['layout']['annotations'].append( dict( x=x, @@ -171,19 +234,16 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, visible=False ) if alternate_row_color: - bkgcolor = go.Scatter( - x=[0, 1], - y=[1.2, 1.2], - fill='tozeroy', - mode='line', - hoverinfo='none', - line=dict( - color=(lane_colors[0] if (j + 1) % 2 == 0 - else lane_colors[1]), - width=0 - ), + bkg = rect( + 'x{}'.format(j * num_of_chart_types + c + 1), + 'y{}'.format(j * num_of_chart_types + c + 1), + x0=-0.1, x1=1.1, + y0=0, y1=1.2, + color=( + r_color + ) ) - fig.append_trace(bkgcolor, j + 1, c + 1) + fig['layout']['shapes'].append(bkg) fig.append_trace(empty_data, j + 1, c + 1) elif chart == 'bullet': @@ -191,7 +251,7 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, x=[rounded_mean], y=[0.5], marker=dict( - color=colors[0] + color=light_color ), hoverinfo='x', orientation='h', @@ -202,14 +262,15 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, x=[list(df[key])[-1]], y=[0.5], marker=dict( - color=colors[1] + color=dark_color ), hoverinfo='x', orientation='h', width=0.14, offset=-0.07 ) - + if not new_marker_color: + scatter_options['marker']['color'] = dark_color bullet_pt = go.Scatter( x=[max(df[key])], y=[0.5], @@ -217,72 +278,80 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, **scatter_options ) - range_e = max(df[key]) + 0.5 * rounded_mean + xrange_r = max(df[key]) + 0.5 * rounded_mean if alternate_row_color: - bkgcolor = go.Scatter( - x=[0, range_e], - y=[1, 1], - fill='tozeroy', - mode='lines', - hoverinfo='none', - line=dict( - color=(lane_colors[0] if (j + 1) % 2 == 0 - else lane_colors[1]), - width=0 - ), + bkg = rect( + 'x{}'.format(j * num_of_chart_types + c + 1), + 'y{}'.format(j * num_of_chart_types + c + 1), + x0=0, x1=xrange_r, + y0=0, y1=1, + color=( + r_color + ) ) - - fig.append_trace(bkgcolor, j + 1, c + 1) + fig['layout']['shapes'].append(bkg) fig.append_trace(bullet_range, j + 1, c + 1) fig.append_trace(bullet_measure, j + 1, c + 1) fig.append_trace(bullet_pt, j + 1, c + 1) fig['layout']['xaxis{}'.format( j * num_of_chart_types + (c + 1) - )]['range'] = [0, range_e] + )]['range'] = [0, xrange_r] fig['layout']['yaxis{}'.format( j * num_of_chart_types + (c + 1) )]['range'] = [0, 1] - elif chart == 'line': - trace_line = go.Scatter( - x=range(len(df[key])), - y=df[key].tolist(), - mode='lines', - marker=dict( - color=colors[0] + elif chart in ['line', 'area']: + if chart == 'line': + trace_line = go.Scatter( + x=range(len(df[key])), + y=df[key].tolist(), + mode='lines', + marker=dict( + color=dark_color + ) ) - ) - fig.append_trace(trace_line, j + 1, c + 1) - + else: + trace_line = go.Scatter( + x=range(len(df[key])), + y=df[key].tolist(), + mode='lines', + fill='tozeroy', + fillcolor=light_color, + line=dict(width=line_width, color=dark_color) + ) + if not new_marker_color: + scatter_options['marker']['color'] = dark_color trace_line_pt = go.Scatter( x=[len(df[key]) - 1], - y=[list(df[key])[-1]], - mode='markers', - marker=dict( - color=colors[1] - ), + y=[df[key].tolist()[-1]], **scatter_options ) + std = np.std(df[key]) + if std == 0: + extra_space = 0.3 * abs(df[key][0]) + yrange_top = df[key].tolist()[0] + extra_space + yrange_bottom = df[key].tolist()[0] - extra_space + else: + yrange_top = max(df[key].tolist()) + std + yrange_bottom = min(df[key].tolist()) - std if alternate_row_color: - bkgcolor = go.Scatter( - x=[0, len(df[key])], - y=[2 * max(df[key].tolist())] * 2, - fill='tozeroy', - mode='lines', - hoverinfo='none', - line=dict( - color=(lane_colors[0] if (j + 1) % 2 == 0 - else lane_colors[1]), - width=0 - ), + bkg = rect( + 'x{}'.format(j * num_of_chart_types + c + 1), + 'y{}'.format(j * num_of_chart_types + c + 1), + x0=0, x1=len(df[key]), + y0=yrange_bottom, y1=yrange_top, + color=( + r_color + ) ) - fig.append_trace(bkgcolor, j + 1, c + 1) + fig['layout']['shapes'].append(bkg) + fig.append_trace(trace_line, j + 1, c + 1) fig.append_trace(trace_line_pt, j + 1, c + 1) fig['layout']['yaxis{}'.format( j * num_of_chart_types + (c + 1) - )]['range'] = [0, 2 * max(df[key].tolist())] + )]['range'] = [yrange_bottom, yrange_top] elif chart == 'avg': fig['layout']['annotations'].append( dict( @@ -303,49 +372,56 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, ) if alternate_row_color: - bkgcolor = go.Scatter( - x=[0, 2], - y=[1.2, 1.2], - fill='tozeroy', - mode='line', - hoverinfo='none', - line=dict( - color=(lane_colors[0] if (j + 1) % 2 == 0 - else lane_colors[1]), - width=0 - ), + bkg = rect( + 'x{}'.format(j * num_of_chart_types + c + 1), + 'y{}'.format(j * num_of_chart_types + c + 1), + x0=0, x1=2, + y0=0, y1=1.2, + color=( + r_color + ) ) - fig.append_trace(bkgcolor, j + 1, c + 1) + fig['layout']['shapes'].append(bkg) fig.append_trace(empty_data, j + 1, c + 1) elif chart == 'bar': + std = np.std(df[key]) + if std == 0: + extra_space = 0.3 * abs(df[key][0]) + if df[key][0] < 0: + yrange_top = 0 + yrange_bottom = (df[key].tolist()[0] - extra_space) + elif df[key][0] >= 0: + yrange_top = (df[key].tolist()[0] + extra_space) + yrange_bottom = 0 + else: + yrange_top = max(df[key].tolist()) + std + yrange_bottom = min(df[key].tolist()) - std + trace_bar = go.Bar( x=range(len(df[key])), y=df[key].tolist(), marker=dict( - color=[colors[0] for _ in - range(len(df[key]) - 1)] + [colors[1]] + color=[light_color for k in + range(len(df[key]) - 1)] + [dark_color] ) ) if alternate_row_color: - bkgcolor = go.Scatter( - x=[-1, len(df[key])], - y=[2 * max(df[key].tolist())] * 2, - fill='tozeroy', - mode='lines', - hoverinfo='none', - line=dict( - color=(lane_colors[0] if (j + 1) % 2 == 0 - else lane_colors[1]), - width=0 - ), + bkg = rect( + 'x{}'.format(j * num_of_chart_types + c + 1), + 'y{}'.format(j * num_of_chart_types + c + 1), + x0=-1, x1=len(df[key]), + y0=yrange_bottom, y1=yrange_top, + color=( + r_color + ) ) - fig.append_trace(bkgcolor, j + 1, c + 1) + fig['layout']['shapes'].append(bkg) fig.append_trace(trace_bar, j + 1, c + 1) fig['layout']['yaxis{}'.format( j * num_of_chart_types + (c + 1) - )]['range'] = [0, 2 * max(df[key].tolist())] + )]['range'] = [yrange_bottom, yrange_top] else: raise exceptions.PlotlyError( 'Your chart type must be a list and may only contain any ' @@ -358,6 +434,8 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, fig['layout']['{}{}'.format( x_y, j * num_of_chart_types + (c + 1) )]['fixedrange'] = True + c_idx += 1 + trace_c_idx += 1 # show titles if show_titles: @@ -368,7 +446,7 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, ) fig['layout']['annotations'].append(label) - # narrow columns with 'name' or 'avg' chart type + # narrow columns with 'label' or 'avg' chart type for j in range(len(df.columns)): for idx in narrow_idxs: for axis in ['xaxis', 'yaxis']: diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index fccf50ee307..992d296b544 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -2737,7 +2737,7 @@ def test_valid_chart_type(self): ], columns=['foo', 'bar', 'baz'], ) - VALID_CHART_TYPES = ('name', 'bullet', 'line', 'avg', 'bar') + VALID_CHART_TYPES = ('label', 'bullet', 'line', 'avg', 'bar', 'area') message = ( 'Your chart type must be a list and may only contain any ' 'combination of the keys {}'.format( @@ -2758,23 +2758,9 @@ def test_is_colors_a_list(self): columns=['foo', 'bar', 'baz'], ) - message = 'colors must be a list/tuple' + message = 'trace_colors must be a list/tuple' self.assertRaisesRegexp(PlotlyError, message, ff.create_sparkline, - df, colors='not going to work') - - def test_colors_correct_length(self): - df = pd.DataFrame( - [ - [5, 2, 9], - [6, 5, 4], - [4, 7, 6] - ], - columns=['foo', 'bar', 'baz'], - ) - - message = 'colors must be a list/tuple with 2 colors inside' - self.assertRaisesRegexp(PlotlyError, message, ff.create_sparkline, - df, colors=['rgb(0, 0, 0)']) + df, trace_colors='not going to work') def test_valid_textalign_value(self): df = pd.DataFrame( @@ -2786,9 +2772,9 @@ def test_valid_textalign_value(self): columns=['foo', 'bar', 'baz'], ) - message = 'textalign must be left, center or right' + message = 'text_align must be left, center or right' self.assertRaisesRegexp(PlotlyError, message, ff.create_sparkline, - df, textalign='will not work') + df, text_align='will not work') def test_full_sparkline_fig(self): @@ -2802,37 +2788,19 @@ def test_full_sparkline_fig(self): ) fig = ff.create_sparkline( - df, chart_types=('name', 'bullet', 'line', 'avg', 'bar'), + df, chart_types=('label', 'bullet', 'line', 'avg', 'bar'), column_width=[1, 2, 3, 4, 5], scatter_options={'marker': {'color': 'rgb(0,0,0)'}}, title='this is a test' ) exp_fig = { - 'data': [{'fill': 'tozeroy', - 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, - 'mode': 'line', - 'type': 'scatter', - 'x': [0, 1], - 'xaxis': 'x1', - 'y': [1.2, 1.2], - 'yaxis': 'y1'}, - {'type': 'bar', + 'data': [{'type': 'bar', 'visible': False, 'x': [0], 'xaxis': 'x1', 'y': [0], 'yaxis': 'y1'}, - {'fill': 'tozeroy', - 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, - 'mode': 'lines', - 'type': 'scatter', - 'x': [0, 6], - 'xaxis': 'x2', - 'y': [1, 1], - 'yaxis': 'y2'}, {'hoverinfo': 'x', 'marker': {'color': 'rgb(181,221,232)'}, 'orientation': 'h', @@ -2859,53 +2827,25 @@ def test_full_sparkline_fig(self): 'xaxis': 'x2', 'y': [0.5], 'yaxis': 'y2'}, - {'marker': {'color': 'rgb(181,221,232)'}, + {'marker': {'color': 'rgb(62,151,169)'}, 'mode': 'lines', 'type': 'scatter', 'x': [0, 1, 2], 'xaxis': 'x3', 'y': [5, 6, 4], 'yaxis': 'y3'}, - {'fill': 'tozeroy', - 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, - 'mode': 'lines', - 'type': 'scatter', - 'x': [0, 3], - 'xaxis': 'x3', - 'y': [12, 12], - 'yaxis': 'y3'}, - {'marker': {'color': 'rgb(62,151,169)'}, - 'mode': 'markers', + {'marker': {'color': 'rgb(0,0,0)', 'size': 9, 'symbol': 'diamond-tall'}, 'type': 'scatter', 'x': [2], 'xaxis': 'x3', 'y': [4], 'yaxis': 'y3'}, - {'fill': 'tozeroy', - 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, - 'mode': 'line', - 'type': 'scatter', - 'x': [0, 2], - 'xaxis': 'x4', - 'y': [1.2, 1.2], - 'yaxis': 'y4'}, {'type': 'bar', 'visible': False, 'x': [0], 'xaxis': 'x4', 'y': [0], 'yaxis': 'y4'}, - {'fill': 'tozeroy', - 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, - 'mode': 'lines', - 'type': 'scatter', - 'x': [-1, 3], - 'xaxis': 'x5', - 'y': [12, 12], - 'yaxis': 'y5'}, {'marker': {'color': ['rgb(181,221,232)', 'rgb(181,221,232)', 'rgb(62,151,169)']}, @@ -2914,30 +2854,12 @@ def test_full_sparkline_fig(self): 'xaxis': 'x5', 'y': [5, 6, 4], 'yaxis': 'y5'}, - {'fill': 'tozeroy', - 'hoverinfo': 'none', - 'line': {'color': 'rgba(249, 247, 244, 1.0)', 'width': 0}, - 'mode': 'line', - 'type': 'scatter', - 'x': [0, 1], - 'xaxis': 'x6', - 'y': [1.2, 1.2], - 'yaxis': 'y6'}, {'type': 'bar', 'visible': False, 'x': [0], 'xaxis': 'x6', 'y': [0], 'yaxis': 'y6'}, - {'fill': 'tozeroy', - 'hoverinfo': 'none', - 'line': {'color': 'rgba(249, 247, 244, 1.0)', 'width': 0}, - 'mode': 'lines', - 'type': 'scatter', - 'x': [0, 7], - 'xaxis': 'x7', - 'y': [1, 1], - 'yaxis': 'y7'}, {'hoverinfo': 'x', 'marker': {'color': 'rgb(181,221,232)'}, 'orientation': 'h', @@ -2964,53 +2886,25 @@ def test_full_sparkline_fig(self): 'xaxis': 'x7', 'y': [0.5], 'yaxis': 'y7'}, - {'marker': {'color': 'rgb(181,221,232)'}, + {'marker': {'color': 'rgb(62,151,169)'}, 'mode': 'lines', 'type': 'scatter', 'x': [0, 1, 2], 'xaxis': 'x8', 'y': [2, 5, 7], 'yaxis': 'y8'}, - {'fill': 'tozeroy', - 'hoverinfo': 'none', - 'line': {'color': 'rgba(249, 247, 244, 1.0)', 'width': 0}, - 'mode': 'lines', - 'type': 'scatter', - 'x': [0, 3], - 'xaxis': 'x8', - 'y': [14, 14], - 'yaxis': 'y8'}, - {'marker': {'color': 'rgb(62,151,169)'}, - 'mode': 'markers', + {'marker': {'color': 'rgb(0,0,0)', 'size': 9, 'symbol': 'diamond-tall'}, 'type': 'scatter', 'x': [2], 'xaxis': 'x8', 'y': [7], 'yaxis': 'y8'}, - {'fill': 'tozeroy', - 'hoverinfo': 'none', - 'line': {'color': 'rgba(249, 247, 244, 1.0)', 'width': 0}, - 'mode': 'line', - 'type': 'scatter', - 'x': [0, 2], - 'xaxis': 'x9', - 'y': [1.2, 1.2], - 'yaxis': 'y9'}, {'type': 'bar', 'visible': False, 'x': [0], 'xaxis': 'x9', 'y': [0], 'yaxis': 'y9'}, - {'fill': 'tozeroy', - 'hoverinfo': 'none', - 'line': {'color': 'rgba(249, 247, 244, 1.0)', 'width': 0}, - 'mode': 'lines', - 'type': 'scatter', - 'x': [-1, 3], - 'xaxis': 'x10', - 'y': [14, 14], - 'yaxis': 'y10'}, {'marker': {'color': ['rgb(181,221,232)', 'rgb(181,221,232)', 'rgb(62,151,169)']}, @@ -3019,30 +2913,12 @@ def test_full_sparkline_fig(self): 'xaxis': 'x10', 'y': [2, 5, 7], 'yaxis': 'y10'}, - {'fill': 'tozeroy', - 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, - 'mode': 'line', - 'type': 'scatter', - 'x': [0, 1], - 'xaxis': 'x11', - 'y': [1.2, 1.2], - 'yaxis': 'y11'}, {'type': 'bar', 'visible': False, 'x': [0], 'xaxis': 'x11', 'y': [0], 'yaxis': 'y11'}, - {'fill': 'tozeroy', - 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, - 'mode': 'lines', - 'type': 'scatter', - 'x': [0, 9], - 'xaxis': 'x12', - 'y': [1, 1], - 'yaxis': 'y12'}, {'hoverinfo': 'x', 'marker': {'color': 'rgb(181,221,232)'}, 'orientation': 'h', @@ -3069,53 +2945,25 @@ def test_full_sparkline_fig(self): 'xaxis': 'x12', 'y': [0.5], 'yaxis': 'y12'}, - {'marker': {'color': 'rgb(181,221,232)'}, + {'marker': {'color': 'rgb(62,151,169)'}, 'mode': 'lines', 'type': 'scatter', 'x': [0, 1, 2], 'xaxis': 'x13', 'y': [9, 4, 6], 'yaxis': 'y13'}, - {'fill': 'tozeroy', - 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, - 'mode': 'lines', - 'type': 'scatter', - 'x': [0, 3], - 'xaxis': 'x13', - 'y': [18, 18], - 'yaxis': 'y13'}, - {'marker': {'color': 'rgb(62,151,169)'}, - 'mode': 'markers', + {'marker': {'color': 'rgb(0,0,0)', 'size': 9, 'symbol': 'diamond-tall'}, 'type': 'scatter', 'x': [2], 'xaxis': 'x13', 'y': [6], 'yaxis': 'y13'}, - {'fill': 'tozeroy', - 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, - 'mode': 'line', - 'type': 'scatter', - 'x': [0, 2], - 'xaxis': 'x14', - 'y': [1.2, 1.2], - 'yaxis': 'y14'}, {'type': 'bar', 'visible': False, 'x': [0], 'xaxis': 'x14', 'y': [0], 'yaxis': 'y14'}, - {'fill': 'tozeroy', - 'hoverinfo': 'none', - 'line': {'color': 'rgba(255, 253, 250, 1.0)', 'width': 0}, - 'mode': 'lines', - 'type': 'scatter', - 'x': [-1, 3], - 'xaxis': 'x15', - 'y': [18, 18], - 'yaxis': 'y15'}, {'marker': {'color': ['rgb(181,221,232)', 'rgb(181,221,232)', 'rgb(62,151,169)']}, @@ -3124,7 +2972,7 @@ def test_full_sparkline_fig(self): 'xaxis': 'x15', 'y': [9, 4, 6], 'yaxis': 'y15'}], - 'layout': {'annotations': [{'font': {'size': 15}, + 'layout': {'annotations': [{'font': {'size': 12}, 'showarrow': False, 'text': 'foo', 'x': 0.5, @@ -3132,7 +2980,7 @@ def test_full_sparkline_fig(self): 'xref': 'x1', 'y': 0.5, 'yref': 'y1'}, - {'font': {'size': 15}, + {'font': {'size': 12}, 'showarrow': False, 'text': '5.0', 'x': 0.5, @@ -3140,7 +2988,7 @@ def test_full_sparkline_fig(self): 'xref': 'x4', 'y': 0.5, 'yref': 'y4'}, - {'font': {'size': 15}, + {'font': {'size': 12}, 'showarrow': False, 'text': 'bar', 'x': 0.5, @@ -3148,7 +2996,7 @@ def test_full_sparkline_fig(self): 'xref': 'x6', 'y': 0.5, 'yref': 'y6'}, - {'font': {'size': 15}, + {'font': {'size': 12}, 'showarrow': False, 'text': '4.67', 'x': 0.5, @@ -3156,7 +3004,7 @@ def test_full_sparkline_fig(self): 'xref': 'x9', 'y': 0.5, 'yref': 'y9'}, - {'font': {'size': 15}, + {'font': {'size': 12}, 'showarrow': False, 'text': 'baz', 'x': 0.5, @@ -3164,7 +3012,7 @@ def test_full_sparkline_fig(self): 'xref': 'x11', 'y': 0.5, 'yref': 'y11'}, - {'font': {'size': 15}, + {'font': {'size': 12}, 'showarrow': False, 'text': '6.33', 'x': 0.5, @@ -3172,6 +3020,141 @@ def test_full_sparkline_fig(self): 'xref': 'x14', 'y': 0.5, 'yref': 'y14'}], + 'shapes': [{'fillcolor': 'rgb(247, 247, 242)', + 'layer': 'below', + 'line': {'width': 0}, + 'x0': -0.1, + 'x1': 1.1, + 'xref': 'x1', + 'y0': 0, + 'y1': 1.2, + 'yref': 'y1'}, + {'fillcolor': 'rgb(247, 247, 242)', + 'layer': 'below', + 'line': {'width': 0}, + 'x0': 0, + 'x1': 8.5, + 'xref': 'x2', + 'y0': 0, + 'y1': 1, + 'yref': 'y2'}, + {'fillcolor': 'rgb(247, 247, 242)', + 'layer': 'below', + 'line': {'width': 0}, + 'x0': 0, + 'x1': 3, + 'xref': 'x3', + 'y0': 3.1835034190722737, + 'y1': 6.8164965809277263, + 'yref': 'y3'}, + {'fillcolor': 'rgb(247, 247, 242)', + 'layer': 'below', + 'line': {'width': 0}, + 'x0': 0, + 'x1': 2, + 'xref': 'x4', + 'y0': 0, + 'y1': 1.2, + 'yref': 'y4'}, + {'fillcolor': 'rgb(247, 247, 242)', + 'layer': 'below', + 'line': {'width': 0}, + 'x0': -1, + 'x1': 3, + 'xref': 'x5', + 'y0': 3.1835034190722737, + 'y1': 6.8164965809277263, + 'yref': 'y5'}, + {'fillcolor': 'rgb(255, 253, 250)', + 'layer': 'below', + 'line': {'width': 0}, + 'x0': -0.1, + 'x1': 1.1, + 'xref': 'x6', + 'y0': 0, + 'y1': 1.2, + 'yref': 'y6'}, + {'fillcolor': 'rgb(255, 253, 250)', + 'layer': 'below', + 'line': {'width': 0}, + 'x0': 0, + 'x1': 9.335, + 'xref': 'x7', + 'y0': 0, + 'y1': 1, + 'yref': 'y7'}, + {'fillcolor': 'rgb(255, 253, 250)', + 'layer': 'below', + 'line': {'width': 0}, + 'x0': 0, + 'x1': 3, + 'xref': 'x8', + 'y0': -0.054804667656325634, + 'y1': 9.0548046676563256, + 'yref': 'y8'}, + {'fillcolor': 'rgb(255, 253, 250)', + 'layer': 'below', + 'line': {'width': 0}, + 'x0': 0, + 'x1': 2, + 'xref': 'x9', + 'y0': 0, + 'y1': 1.2, + 'yref': 'y9'}, + {'fillcolor': 'rgb(255, 253, 250)', + 'layer': 'below', + 'line': {'width': 0}, + 'x0': -1, + 'x1': 3, + 'xref': 'x10', + 'y0': -0.054804667656325634, + 'y1': 9.0548046676563256, + 'yref': 'y10'}, + {'fillcolor': 'rgb(247, 247, 242)', + 'layer': 'below', + 'line': {'width': 0}, + 'x0': -0.1, + 'x1': 1.1, + 'xref': 'x11', + 'y0': 0, + 'y1': 1.2, + 'yref': 'y11'}, + {'fillcolor': 'rgb(247, 247, 242)', + 'layer': 'below', + 'line': {'width': 0}, + 'x0': 0, + 'x1': 12.165, + 'xref': 'x12', + 'y0': 0, + 'y1': 1, + 'yref': 'y12'}, + {'fillcolor': 'rgb(247, 247, 242)', + 'layer': 'below', + 'line': {'width': 0}, + 'x0': 0, + 'x1': 3, + 'xref': 'x13', + 'y0': 1.9451953323436744, + 'y1': 11.054804667656326, + 'yref': 'y13'}, + {'fillcolor': 'rgb(247, 247, 242)', + 'layer': 'below', + 'line': {'width': 0}, + 'x0': 0, + 'x1': 2, + 'xref': 'x14', + 'y0': 0, + 'y1': 1.2, + 'yref': 'y14'}, + {'fillcolor': 'rgb(247, 247, 242)', + 'layer': 'below', + 'line': {'width': 0}, + 'x0': -1, + 'x1': 3, + 'xref': 'x15', + 'y0': 1.9451953323436744, + 'y1': 11.054804667656326, + 'yref': 'y15'}], 'showlegend': False, 'title': 'this is a test', 'xaxis1': {'anchor': 'y1', @@ -3197,6 +3180,7 @@ def test_full_sparkline_fig(self): 'xaxis12': {'anchor': 'y12', 'domain': [0.06666666666666667, 0.2], 'fixedrange': True, + 'range': [0, 12.165], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, @@ -3222,6 +3206,7 @@ def test_full_sparkline_fig(self): 'xaxis2': {'anchor': 'y2', 'domain': [0.06666666666666667, 0.2], 'fixedrange': True, + 'range': [0, 8.5], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, @@ -3254,6 +3239,7 @@ def test_full_sparkline_fig(self): 'xaxis7': {'anchor': 'y7', 'domain': [0.06666666666666667, 0.2], 'fixedrange': True, + 'range': [0, 9.335], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, @@ -3280,7 +3266,7 @@ def test_full_sparkline_fig(self): 'yaxis10': {'anchor': 'x10', 'domain': [0.3333333333333333, 0.6666666666666666], 'fixedrange': True, - 'range': [0, 14], + 'range': [-0.054804667656325634, 9.0548046676563256], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, @@ -3301,7 +3287,7 @@ def test_full_sparkline_fig(self): 'yaxis13': {'anchor': 'x13', 'domain': [0.0, 0.3333333333333333], 'fixedrange': True, - 'range': [0, 18], + 'range': [1.9451953323436744, 11.054804667656326], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, @@ -3315,7 +3301,7 @@ def test_full_sparkline_fig(self): 'yaxis15': {'anchor': 'x15', 'domain': [0.0, 0.3333333333333333], 'fixedrange': True, - 'range': [0, 18], + 'range': [1.9451953323436744, 11.054804667656326], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, @@ -3329,7 +3315,7 @@ def test_full_sparkline_fig(self): 'yaxis3': {'anchor': 'x3', 'domain': [0.6666666666666666, 1.0], 'fixedrange': True, - 'range': [0, 12], + 'range': [3.1835034190722737, 6.8164965809277263], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, @@ -3343,7 +3329,7 @@ def test_full_sparkline_fig(self): 'yaxis5': {'anchor': 'x5', 'domain': [0.6666666666666666, 1.0], 'fixedrange': True, - 'range': [0, 12], + 'range': [3.1835034190722737, 6.8164965809277263], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, @@ -3364,7 +3350,7 @@ def test_full_sparkline_fig(self): 'yaxis8': {'anchor': 'x8', 'domain': [0.3333333333333333, 0.6666666666666666], 'fixedrange': True, - 'range': [0, 14], + 'range': [-0.054804667656325634, 9.0548046676563256], 'showgrid': False, 'showticklabels': False, 'zeroline': False}, From efc1c671bd17dc4e9a472e16e660570329834608 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Fri, 15 Dec 2017 12:17:16 -0500 Subject: [PATCH 12/19] add defaults to doc string --- plotly/figure_factory/_sparkline.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plotly/figure_factory/_sparkline.py b/plotly/figure_factory/_sparkline.py index 3ea648ea5ef..323085a899b 100644 --- a/plotly/figure_factory/_sparkline.py +++ b/plotly/figure_factory/_sparkline.py @@ -42,6 +42,7 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, trace_colors=None, :param (list|tuple) chart_types: a sequence of any combination of valid chart types. The valid chart types are 'label', 'bullet', 'line', 'avg' and 'bar' + Default = ('label', 'bullet', 'line', 'avg', 'bar', 'area') :param (list|tuple) trace_colors: a list of colors or a list of lists of two colors. Each row uses two colors: a darker one and a lighter one. Options: @@ -57,7 +58,7 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, trace_colors=None, options Whenever trace_colors has fewer colors than the number of rows of the figure, the colors will repeat from the start of the list - Default = ('rgb(181,221,232)', 'rgb(62,151,169)') + Default = [['rgb(62,151,169)', 'rgb(181,221,232)']] :param (list) column_width: Specify a list that contains numbers where the amount of numbers in the list is equal to `chart_types`. Call `help(plotly.tools.make_subplots)` for more info on this subplot param @@ -67,16 +68,21 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, trace_colors=None, 'left', or 'right'. Default='center'. :param (float) horizontal_spacing: Space between subplot columns. Applied to all columns + Default = 0.0 :param (float) vertical_spacing: Space between subplot rows. Applied to all rows - :param (float) alternate_row_color: set to True to enable the alternate + Default = 0.0 + :param (bool) alternate_row_color: set to True to enable the alternate row coloring of the chart. Uses the trace_colors from param 'row_colors' + Default = True :param (list) row_colors: a list/tuple of colors that are used to alternately color the rows of the chart. If the number of colors in the list is fewer than the number of rows, the active color for the layout will be looped back to the first in the list + Default = ('rgb(247, 247, 242)', 'rgb(255, 253, 250)') :param (float) line_width: sets the width of the lines used in 'area' or filled area line charts + Default = 2 :param (dict) scatter_options: describes attributes for the scatter point in each bullet chart such as label and marker size. Call help(plotly.graph_objs.Scatter) for more information on valid params. From 203576553d87eb6abc842ffa65e7b2e1ff6fabd1 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Mon, 15 Jan 2018 13:51:15 -0500 Subject: [PATCH 13/19] added tidy data functionality --- plotly/figure_factory/_sparkline.py | 775 ++++++++++++++++++++-------- 1 file changed, 556 insertions(+), 219 deletions(-) diff --git a/plotly/figure_factory/_sparkline.py b/plotly/figure_factory/_sparkline.py index 323085a899b..ce9bc2b95ff 100644 --- a/plotly/figure_factory/_sparkline.py +++ b/plotly/figure_factory/_sparkline.py @@ -27,184 +27,294 @@ def rect(xref, yref, x0, x1, y0, y1, color): return shape -def create_sparkline(df, chart_types=VALID_CHART_TYPES, trace_colors=None, - column_width=None, show_titles=False, - text_align='center', horizontal_spacing=0.0, - vertical_spacing=0.0, alternate_row_color=True, - row_colors=('rgb(247, 247, 242)', - 'rgb(255, 253, 250)'), - line_width=2, scatter_options=None, **layout_options): - """ - Returns figure for sparkline. - - :param (pd.DataFrame | list | tuple) df: either a list/tuple of - dictionaries or a pandas DataFrame. - :param (list|tuple) chart_types: a sequence of any combination of valid - chart types. The valid chart types are 'label', 'bullet', 'line', 'avg' - and 'bar' - Default = ('label', 'bullet', 'line', 'avg', 'bar', 'area') - :param (list|tuple) trace_colors: a list of colors or a list of lists of - two colors. Each row uses two colors: a darker one and a lighter one. - Options: - - list of 2 colors: first color is dark color for all traces and - second is light for all traces. - - 1D list of more than 2 colors: the nth color in the list is the - nth dark color for the traces and the associated light color is - just 0.5 times the opacity of the dark color - - lists of lists: each inner list must have exactly 2 colors in it - and the first and second color of the nth inner list represent - the dark and light color for the nth row repsectively - - list of lists and colors: this is a combination of the previous - options - Whenever trace_colors has fewer colors than the number of rows of the - figure, the colors will repeat from the start of the list - Default = [['rgb(62,151,169)', 'rgb(181,221,232)']] - :param (list) column_width: Specify a list that contains numbers where - the amount of numbers in the list is equal to `chart_types`. Call - `help(plotly.tools.make_subplots)` for more info on this subplot param - :param (bool) show_titles: determines if title of chart type is displayed - above their respective column - :param (str) text_align: aligns label and avg cells. Use either 'center', - 'left', or 'right'. Default='center'. - :param (float) horizontal_spacing: Space between subplot columns. - Applied to all columns - Default = 0.0 - :param (float) vertical_spacing: Space between subplot rows. - Applied to all rows - Default = 0.0 - :param (bool) alternate_row_color: set to True to enable the alternate - row coloring of the chart. Uses the trace_colors from param 'row_colors' - Default = True - :param (list) row_colors: a list/tuple of colors that are used to - alternately color the rows of the chart. If the number of colors in the - list is fewer than the number of rows, the active color for the layout - will be looped back to the first in the list - Default = ('rgb(247, 247, 242)', 'rgb(255, 253, 250)') - :param (float) line_width: sets the width of the lines used in 'area' or - filled area line charts - Default = 2 - :param (dict) scatter_options: describes attributes for the scatter point - in each bullet chart such as label and marker size. Call - help(plotly.graph_objs.Scatter) for more information on valid params. - :param layout_options: describes attributes for the layout of the figure - such as title, height and width. Call help(plotly.graph_objs.Layout) - for more information on valid params - """ - # validate dataframe - if not pd: - raise exceptions.ImportError( - "'pandas' must be installed for this figure factory." - ) - - elif not isinstance(df, pd.DataFrame): - raise exceptions.PlotlyError( - 'df must be a pandas DataFrame' - ) +VALID_CHART_TYPE_MESSAGE = ( + 'Invalid chart type. The valid chart types ' + 'are {}'.format(utils.list_of_options(VALID_CHART_TYPES, 'and')) +) - # validate list/tuple of trace_colors - if not trace_colors: - trace_colors = [['rgb(62,151,169)', 'rgb(181,221,232)']] - if not utils.is_sequence(trace_colors): - raise exceptions.PlotlyError( - 'trace_colors must be a list/tuple' - ) - trace_colors_2d = [] - for i, item in enumerate(trace_colors): - plotly.colors.validate_colors(item) - if utils.is_sequence(item): - trace_colors_2d.append(item) - else: - # if hex convert to rgb - if '#' in item: - tuple_item = plotly.colors.hex_to_rgb(item) - rgb_item = plotly.colors.label_rgb(tuple_item) - else: - rgb_item = item - light_c = plotly.colors.find_intermediate_color( - rgb_item, 'rgb(255, 255, 255)', 0.5, colortype='rgb' - ) - trace_colors_2d.append([rgb_item, light_c]) +def _sparkline_tidy(df, fig, chart_types, num_of_chart_types, trace_colors_2d, + column_width, alternate_row_color, row_colors, xanchor, + line_width, align_x, scatter_options, new_marker_color, + narrow_idxs, show_titles, row, column, x): + # create and insert charts + c_idx = 0 + trace_c_idx = 0 + chart_types_keys = [k for d in chart_types for k in d.keys()] + for j, rkey in enumerate(df[row].unique()): + for c, ckey in enumerate(chart_types_keys): + chart = chart_types[c][ckey] + data = list(df[(df[row] == rkey) & (df[column] == ckey)][x]) - num_of_chart_types = len(chart_types) - # narrow columns that are 'label' or 'avg' - narrow_cols = ['label', 'avg'] - narrow_idxs = [] - for i, chart in enumerate(chart_types): - if chart in narrow_cols: - narrow_idxs.append(i) + # update indices + if c_idx >= len(row_colors): + c_idx = 0 + r_color = row_colors[c_idx] - if not column_width: - column_width = [3.0] * num_of_chart_types - for idx in narrow_idxs: - column_width[idx] = 1.0 + if trace_c_idx >= len(trace_colors_2d): + trace_c_idx = 0 + dark_color = trace_colors_2d[trace_c_idx][0] + light_color = trace_colors_2d[trace_c_idx][1] - fig = plotly.tools.make_subplots( - len(df.columns), num_of_chart_types, print_grid=False, - shared_xaxes=False, shared_yaxes=False, - horizontal_spacing=horizontal_spacing, - vertical_spacing=vertical_spacing, column_width=column_width - ) + if len(data) <= 0: + if chart in ['label', 'avg']: + if chart == 'label': + text = rkey + else: + text = 'nan' + fig['layout']['annotations'].append( + dict( + xref='x{}'.format(j * num_of_chart_types + c + 1), + yref='y{}'.format(j * num_of_chart_types + c + 1), + x=align_x, + y=0.5, + xanchor=xanchor, + text=text, + showarrow=False, + font=dict(size=12), + ) + ) + empty_data = go.Bar( + x=[0], + y=[0], + visible=False + ) + if alternate_row_color: + bkg = rect( + 'x{}'.format(j * num_of_chart_types + c + 1), + 'y{}'.format(j * num_of_chart_types + c + 1), + x0=0, x1=1, + y0=-0.1, y1=1.1, + color=( + r_color + ) + ) + fig['layout']['shapes'].append(bkg) + fig.append_trace(empty_data, j + 1, c + 1) + else: + mean = np.mean(data) + rounded_mean = round(mean, 2) + if chart in ['label', 'avg']: + if chart == 'label': + text = rkey + else: + text = '{}'.format(rounded_mean) + fig['layout']['annotations'].append( + dict( + xref='x{}'.format(j * num_of_chart_types + c + 1), + yref='y{}'.format(j * num_of_chart_types + c + 1), + x=align_x, + y=0.5, + xanchor=xanchor, + text=text, + showarrow=False, + font=dict(size=12), + ) + ) + empty_data = go.Bar( + x=[0], + y=[0], + visible=False + ) + if alternate_row_color: + bkg = rect( + 'x{}'.format(j * num_of_chart_types + c + 1), + 'y{}'.format(j * num_of_chart_types + c + 1), + x0=0, x1=1, + y0=-0.1, y1=1.1, + color=( + r_color + ) + ) + fig['layout']['shapes'].append(bkg) + fig.append_trace(empty_data, j + 1, c + 1) - # layout options - fig['layout'].update( - title='', - annotations=[], - shapes=[], - showlegend=False - ) + elif chart == 'bullet': + bullet_range = go.Bar( + x=[rounded_mean], + y=[0.5], + marker=dict( + color=light_color + ), + hoverinfo='x', + orientation='h', + width=0.5 + ) - # update layout - fig['layout'].update(layout_options) + bullet_measure = go.Bar( + x=[data[-1]], + y=[0.5], + marker=dict( + color=dark_color + ), + hoverinfo='x', + orientation='h', + width=0.14, + offset=-0.07 + ) + if not new_marker_color: + scatter_options['marker']['color'] = dark_color + bullet_pt = go.Scatter( + x=[max(data)], + y=[0.5], + hoverinfo='x', + **scatter_options + ) - for key in fig['layout'].keys(): - if 'axis' in key: - fig['layout'][key].update( - showgrid=False, - zeroline=False, - showticklabels=False - ) + xrange_r = max(data) + 0.5 * rounded_mean + if alternate_row_color: + bkg = rect( + 'x{}'.format(j * num_of_chart_types + c + 1), + 'y{}'.format(j * num_of_chart_types + c + 1), + x0=0, x1=xrange_r, + y0=0, y1=1, + color=( + r_color + ) + ) + fig['layout']['shapes'].append(bkg) + fig.append_trace(bullet_range, j + 1, c + 1) + fig.append_trace(bullet_measure, j + 1, c + 1) + fig.append_trace(bullet_pt, j + 1, c + 1) + + fig['layout']['xaxis{}'.format( + j * num_of_chart_types + (c + 1) + )]['range'] = [0, xrange_r] + fig['layout']['yaxis{}'.format( + j * num_of_chart_types + (c + 1) + )]['range'] = [0, 1] + elif chart in ['line', 'area']: + if chart == 'line': + trace_line = go.Scatter( + x=range(len(data)), + y=data, + mode='lines', + marker=dict( + color=dark_color + ) + ) + else: + trace_line = go.Scatter( + x=range(len(data)), + y=data, + mode='lines', + fill='tozeroy', + fillcolor=light_color, + line=dict(width=line_width, color=dark_color) + ) + if not new_marker_color: + scatter_options['marker']['color'] = dark_color + trace_line_pt = go.Scatter( + x=[len(data) - 1], + y=[data[-1]], + **scatter_options + ) - # text alignment - xanchor = text_align - if text_align == 'left': - x = 0 - elif text_align == 'center': - x = 0.5 - elif text_align == 'right': - x = 1 - else: - raise exceptions.PlotlyError( - 'text_align must be left, center or right' - ) + # invisible pt + pt_hidden = go.Scatter( + x=[-1], + y=[0], + hoverinfo='none' + ) - # scatter options - default_scatter = { - 'mode': 'markers', - 'marker': {'size': 9, - 'symbol': 'diamond-tall'} - } + std = np.std(data) + x0 = -0.01 + if np.isnan(std): + yrange_top = 1 + yrange_bottom = 0 + elif std == 0: + extra_space = 0.3 * abs(data[0]) + yrange_top = data[0] + extra_space + yrange_bottom = data[0] - extra_space + else: + yrange_top = max(data) + 2*std + yrange_bottom = min(data) - 2*std + if alternate_row_color: + bkg = rect( + 'x{}'.format(j * num_of_chart_types + c + 1), + 'y{}'.format(j * num_of_chart_types + c + 1), + x0=x0, x1=len(data), + y0=yrange_bottom, y1=yrange_top, + color=( + r_color + ) + ) + fig['layout']['shapes'].append(bkg) + fig.append_trace(trace_line, j + 1, c + 1) + fig.append_trace(trace_line_pt, j + 1, c + 1) + fig.append_trace(pt_hidden, j + 1, c + 1) + + fig['layout']['yaxis{}'.format( + j * num_of_chart_types + (c + 1) + )]['range'] = [yrange_bottom, yrange_top] + fig['layout']['xaxis{}'.format( + j * num_of_chart_types + (c + 1) + )]['range'] = [x0, len(data)] + elif chart == 'bar': + std = np.std(data) + if std == 0: + extra_space = 0.3 * abs(data[0]) + if data[0] < 0: + yrange_top = 0 + yrange_bottom = (data[0] - extra_space) + elif data[0] >= 0: + yrange_top = (data[0] + extra_space) + yrange_bottom = 0 + else: + yrange_top = max(data) + std + yrange_bottom = min(data) - std + + trace_bar = go.Bar( + x=range(len(data)), + y=data, + marker=dict( + color=[light_color for k in + range(len(data) - 1)] + [dark_color] + ) + ) - if not scatter_options: - scatter_options = {} + if alternate_row_color: + bkg = rect( + 'x{}'.format(j * num_of_chart_types + c + 1), + 'y{}'.format(j * num_of_chart_types + c + 1), + x0=-1, x1=len(data), + y0=yrange_bottom, y1=yrange_top, + color=( + r_color + ) + ) + fig['layout']['shapes'].append(bkg) + fig.append_trace(trace_bar, j + 1, c + 1) + + fig['layout']['yaxis{}'.format( + j * num_of_chart_types + (c + 1) + )]['range'] = [yrange_bottom, yrange_top] + for x_y in ['xaxis', 'yaxis']: + fig['layout']['{}{}'.format( + x_y, j * num_of_chart_types + (c + 1) + )]['fixedrange'] = True + + # show titles + if show_titles and j == 0: + label = utils.annotation_dict_for_label( + ckey, c + 1, num_of_chart_types, subplot_spacing=0, + row_col='col', flipped=False, column_width=column_width + ) + fig['layout']['annotations'].append(label) - if scatter_options == {}: - scatter_options.update(default_scatter) - else: - # add default options to scatter_options if they are not present - for k in default_scatter['marker']: - try: - if k not in scatter_options['marker']: - scatter_options['marker'][k] = default_scatter['marker'][k] - except KeyError: - scatter_options['marker'] = {} - scatter_options['marker'][k] = default_scatter['marker'][k] + for idx in narrow_idxs: + for axis in ['xaxis', 'yaxis']: + fig['layout']['{}{}'.format( + axis, c * num_of_chart_types + idx + 1 + )]['range'] = [0, 1] + c_idx += 1 + trace_c_idx += 1 - if 'marker' in scatter_options and 'color' in scatter_options['marker']: - new_marker_color = True - else: - new_marker_color = False +def _sparkline(df, fig, chart_types, num_of_chart_types, trace_colors_2d, + column_width, alternate_row_color, row_colors, xanchor, + line_width, align_x, scatter_options, new_marker_color, + narrow_idxs, show_titles): # create and insert charts c_idx = 0 trace_c_idx = 0 @@ -221,15 +331,20 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, trace_colors=None, trace_c_idx = 0 dark_color = trace_colors_2d[trace_c_idx][0] light_color = trace_colors_2d[trace_c_idx][1] - if chart == 'label': + + if chart == ['label', 'avg']: + if chart == 'label': + text = key + else: + text = '{}'.format(rounded_mean) fig['layout']['annotations'].append( dict( - x=x, + x=align_x, y=0.5, xref='x{}'.format(j * num_of_chart_types + c + 1), yref='y{}'.format(j * num_of_chart_types + c + 1), xanchor=xanchor, - text=key, + text=text, showarrow=False, font=dict(size=12), ) @@ -244,7 +359,7 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, trace_colors=None, 'x{}'.format(j * num_of_chart_types + c + 1), 'y{}'.format(j * num_of_chart_types + c + 1), x0=-0.1, x1=1.1, - y0=0, y1=1.2, + y0=-0.1, y1=1.1, color=( r_color ) @@ -306,6 +421,7 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, trace_colors=None, fig['layout']['yaxis{}'.format( j * num_of_chart_types + (c + 1) )]['range'] = [0, 1] + elif chart in ['line', 'area']: if chart == 'line': trace_line = go.Scatter( @@ -358,37 +474,7 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, trace_colors=None, fig['layout']['yaxis{}'.format( j * num_of_chart_types + (c + 1) )]['range'] = [yrange_bottom, yrange_top] - elif chart == 'avg': - fig['layout']['annotations'].append( - dict( - xref='x{}'.format(j * num_of_chart_types + c + 1), - yref='y{}'.format(j * num_of_chart_types + c + 1), - x=x, - y=0.5, - xanchor=xanchor, - text='{}'.format(rounded_mean), - showarrow=False, - font=dict(size=12), - ) - ) - empty_data = go.Bar( - x=[0], - y=[0], - visible=False - ) - if alternate_row_color: - bkg = rect( - 'x{}'.format(j * num_of_chart_types + c + 1), - 'y{}'.format(j * num_of_chart_types + c + 1), - x0=0, x1=2, - y0=0, y1=1.2, - color=( - r_color - ) - ) - fig['layout']['shapes'].append(bkg) - fig.append_trace(empty_data, j + 1, c + 1) elif chart == 'bar': std = np.std(df[key]) if std == 0: @@ -428,36 +514,287 @@ def create_sparkline(df, chart_types=VALID_CHART_TYPES, trace_colors=None, fig['layout']['yaxis{}'.format( j * num_of_chart_types + (c + 1) )]['range'] = [yrange_bottom, yrange_top] - else: - raise exceptions.PlotlyError( - 'Your chart type must be a list and may only contain any ' - 'combination of the keys {}'.format( - utils.list_of_options( - VALID_CHART_TYPES, 'or') - ) - ) for x_y in ['xaxis', 'yaxis']: fig['layout']['{}{}'.format( x_y, j * num_of_chart_types + (c + 1) )]['fixedrange'] = True + + # show titles + if show_titles and j == 0: + label = utils.annotation_dict_for_label( + chart, c + 1, num_of_chart_types, subplot_spacing=0, + row_col='col', flipped=False, column_width=column_width + ) + fig['layout']['annotations'].append(label) + + for idx in narrow_idxs: + for axis in ['xaxis', 'yaxis']: + fig['layout']['{}{}'.format( + axis, c * num_of_chart_types + idx + 1 + )]['range'] = [0, 1] c_idx += 1 trace_c_idx += 1 - # show titles - if show_titles: - for k, header in enumerate(chart_types): - label = utils.annotation_dict_for_label( - header, k + 1, 5, subplot_spacing=0, row_col='col', - flipped=False, column_width=column_width + +def create_sparkline(df, chart_types=None, row=None, column=None, + x=None, trace_colors=None, column_width=None, + show_titles=False, text_align='center', + horizontal_spacing=0.0, vertical_spacing=0.0, + alternate_row_color=True, + row_colors=('rgb(247, 247, 242)', + 'rgb(255, 253, 250)'), + line_width=2, scatter_options=None, **layout_options): + """ + Returns figure for sparkline. + + :param (pd.DataFrame | list | tuple) df: either a list/tuple of + dictionaries or a pandas DataFrame. + :param (list|tuple) chart_types: a sequence of any combination of valid + chart types. The valid chart types are 'label', 'bullet', 'line', 'avg' + and 'bar' + Default = ('label', 'bullet', 'line', 'avg', 'bar', 'area') + :param (list|tuple) trace_colors: a list of colors or a list of lists of + two colors. Each row uses two colors: a darker one and a lighter one. + Options: + - list of 2 colors: first color is dark color for all traces and + second is light for all traces. + - 1D list of more than 2 colors: the nth color in the list is the + nth dark color for the traces and the associated light color is + just 0.5 times the opacity of the dark color + - lists of lists: each inner list must have exactly 2 colors in it + and the first and second color of the nth inner list represent + the dark and light color for the nth row repsectively + - list of lists and colors: this is a combination of the previous + options + Whenever trace_colors has fewer colors than the number of rows of the + figure, the colors will repeat from the start of the list + Default = [['rgb(62,151,169)', 'rgb(181,221,232)']] + :param (list) column_width: Specify a list that contains numbers where + the amount of numbers in the list is equal to `chart_types`. Call + `help(plotly.tools.make_subplots)` for more info on this subplot param + :param (bool) show_titles: determines if title of chart type is displayed + above their respective column + :param (str) text_align: aligns label and avg cells. Use either 'center', + 'left', or 'right'. Default='center'. + :param (float) horizontal_spacing: Space between subplot columns. + Applied to all columns + Default = 0.0 + :param (float) vertical_spacing: Space between subplot rows. + Applied to all rows + Default = 0.0 + :param (bool) alternate_row_color: set to True to enable the alternate + row coloring of the chart. Uses the trace_colors from param 'row_colors' + Default = True + :param (list) row_colors: a list/tuple of colors that are used to + alternately color the rows of the chart. If the number of colors in the + list is fewer than the number of rows, the active color for the layout + will be looped back to the first in the list + Default = ('rgb(247, 247, 242)', 'rgb(255, 253, 250)') + :param (float) line_width: sets the width of the lines used in 'area' or + filled area line charts + Default = 2 + :param (dict) scatter_options: describes attributes for the scatter point + in each bullet chart such as label and marker size. Call + help(plotly.graph_objs.Scatter) for more information on valid params. + :param layout_options: describes attributes for the layout of the figure + such as title, height and width. Call help(plotly.graph_objs.Layout) + for more information on valid params + """ + # validate dataframe + if not pd: + raise exceptions.ImportError( + "'pandas' must be installed for this figure factory." + ) + + elif not isinstance(df, pd.DataFrame): + raise exceptions.PlotlyError( + 'df must be a pandas DataFrame' + ) + + # validate list/tuple of trace_colors + if not trace_colors: + trace_colors = [['rgb(62,151,169)', 'rgb(181,221,232)']] + if not utils.is_sequence(trace_colors): + raise exceptions.PlotlyError( + 'trace_colors must be a list/tuple' + ) + + trace_colors_2d = [] + for i, item in enumerate(trace_colors): + plotly.colors.validate_colors(item) + if utils.is_sequence(item): + trace_colors_2d.append(item) + else: + # if hex convert to rgb + if '#' in item: + tuple_item = plotly.colors.hex_to_rgb(item) + rgb_item = plotly.colors.label_rgb(tuple_item) + else: + rgb_item = item + light_c = plotly.colors.find_intermediate_color( + rgb_item, 'rgb(255, 255, 255)', 0.5, colortype='rgb' + ) + trace_colors_2d.append([rgb_item, light_c]) + + # validate chart_types and row, column, x + if row or column or x: + if not (row and column and x): + raise exceptions.PlotlyError( + "You must set the params 'row', 'column' and 'x' to valid " + "column names of your dataframe" ) - fig['layout']['annotations'].append(label) - # narrow columns with 'label' or 'avg' chart type - for j in range(len(df.columns)): + # confirm valid row, column, x values + for param in [row, column, x]: + if param not in df: + raise exceptions.PlotlyError( + '{} is not a column name of the dataframe'.format(param) + ) + + if not chart_types: + chart_types = [] + for j, val in enumerate(df[column].unique()): + if j == len(VALID_CHART_TYPES): + j = 0 + chart_types.append({val: VALID_CHART_TYPES[j]}) + j += 1 + + if (not utils.is_sequence(chart_types) and + not all(isinstance(i, dict) for i in chart_types)): + raise exceptions.PlotlyError( + "'chart_types' must be a list/tuple of dictionaries if " + "'row', 'column', and 'x' are being used" + ) + chart_types_keys = [k for d in chart_types for k in d.keys()] + for key in chart_types_keys: + if key not in df[column].unique(): + raise exceptions.PlotlyError( + "All the keys of the dictionaries in 'chart_types' must " + "be values in the chosen column. Since your selected " + "column is '{}', the available keys {}".format( + column, + utils.list_of_options(df[column].unique(), 'and') + ) + ) + + # validate chart types + chart_vals = [k for d in chart_types for k in d.values()] + for item in chart_vals: + if item not in VALID_CHART_TYPES: + raise exceptions.PlotlyError(VALID_CHART_TYPE_MESSAGE) + else: + if not chart_types: + chart_types = VALID_CHART_TYPES + for item in chart_types: + if item not in VALID_CHART_TYPES: + raise exceptions.PlotlyError(VALID_CHART_TYPE_MESSAGE) + + num_of_chart_types = len(chart_types) + # narrow columns that are 'label' or 'avg' + narrow_cols = ['label', 'avg'] + narrow_idxs = [] + if not isinstance(chart_types, dict): + for i, chart in enumerate(chart_types): + if chart in narrow_cols: + narrow_idxs.append(i) + else: + for i, chart in enumerate(chart_types): + if chart in narrow_cols: + narrow_idxs.append(i) + + if not column_width: + column_width = [3.0] * num_of_chart_types for idx in narrow_idxs: - for axis in ['xaxis', 'yaxis']: - fig['layout']['{}{}'.format( - axis, j * num_of_chart_types + idx + 1 - )]['range'] = [0, 1] + column_width[idx] = 1.0 + + # text alignment + xanchor = text_align + if text_align == 'left': + align_x = 0 + elif text_align == 'center': + align_x = 0.5 + elif text_align == 'right': + align_x = 1 + else: + raise exceptions.PlotlyError( + 'text_align must be left, center or right' + ) + + # scatter options + default_scatter = { + 'mode': 'markers', + 'marker': {'size': 8, + 'symbol': 'diamond-tall'} + } + + if not scatter_options: + scatter_options = {} + + if scatter_options == {}: + scatter_options.update(default_scatter) + else: + # add default options to scatter_options if they are not present + for k in default_scatter['marker']: + try: + if k not in scatter_options['marker']: + scatter_options['marker'][k] = default_scatter['marker'][k] + except KeyError: + scatter_options['marker'] = {} + scatter_options['marker'][k] = default_scatter['marker'][k] + + if 'marker' in scatter_options and 'color' in scatter_options['marker']: + new_marker_color = True + else: + new_marker_color = False + + # create fig + if row and column and x: + fig = plotly.tools.make_subplots( + len(df[row].unique()), num_of_chart_types, print_grid=False, + shared_xaxes=False, shared_yaxes=False, + horizontal_spacing=horizontal_spacing, + vertical_spacing=vertical_spacing, column_width=column_width + ) + else: + fig = plotly.tools.make_subplots( + len(df.columns), num_of_chart_types, print_grid=False, + shared_xaxes=False, shared_yaxes=False, + horizontal_spacing=horizontal_spacing, + vertical_spacing=vertical_spacing, column_width=column_width + ) + + # layout options + fig['layout'].update( + title='', + annotations=[], + shapes=[], + showlegend=False + ) + + # update layout + fig['layout'].update(layout_options) + + for key in fig['layout'].keys(): + if 'axis' in key: + fig['layout'][key].update( + showgrid=False, + zeroline=False, + showticklabels=False + ) + + if row and column and x: + _sparkline_tidy( + df, fig, chart_types, num_of_chart_types, trace_colors_2d, + column_width, alternate_row_color, row_colors, xanchor, + line_width, align_x, scatter_options, new_marker_color, + narrow_idxs, show_titles, row, column, x + ) + else: + _sparkline( + df, fig, chart_types, num_of_chart_types, trace_colors_2d, + column_width, alternate_row_color, row_colors, xanchor, + line_width, align_x, scatter_options, new_marker_color, + narrow_idxs, show_titles + ) return fig From 4d44d8aede6f234d1e67dc456c587876f42a2c61 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Mon, 9 Apr 2018 14:23:39 -0400 Subject: [PATCH 14/19] starting on theme=sparkliens --- plotly/figure_factory/_facet_grid.py | 175 +++++++++++++++------------ 1 file changed, 100 insertions(+), 75 deletions(-) diff --git a/plotly/figure_factory/_facet_grid.py b/plotly/figure_factory/_facet_grid.py index 6d5fe33eedf..e31b59bcee6 100644 --- a/plotly/figure_factory/_facet_grid.py +++ b/plotly/figure_factory/_facet_grid.py @@ -25,7 +25,8 @@ THRES_FOR_FLIPPED_FACET_TITLES = 10 GRID_WIDTH = 1 -VALID_TRACE_TYPES = ['scatter', 'scattergl', 'histogram', 'bar', 'box'] +VALID_TRACE_TYPES = ['scatter', 'scattergl', 'histogram', 'bar', 'box'] + ['line', 'bullet', 'label', 'avg', 'area'] + CUSTOM_LABEL_ERROR = ( "If you are using a dictionary for custom labels for the facet row/col, " @@ -611,7 +612,11 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, facet_row_labels=None, facet_col_labels=None, height=None, width=None, trace_type='scatter', scales='fixed', dtick_x=None, dtick_y=None, - show_boxes=True, ggplot2=False, binsize=1, **kwargs): + show_boxes=True, ggplot2=False, binsize=1, + row_colors=('rgb(247, 247, 242)', + 'rgb(255, 253, 250)'), + theme='facet', + **kwargs): """ Returns figure for facet grid. @@ -657,6 +662,9 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, http://ggplot2.tidyverse.org/reference/facet_grid.html for reference. Default = False :param (int) binsize: groups all data into bins of a given length. + :param (tuple|list) row_colors: + :param (str) theme: determines the layout style of the plot. The options + are 'facet' (default) and 'sparklines'. :param (dict) kwargs: a dictionary of scatterplot arguments. Examples 1: One Way Faceting @@ -801,9 +809,10 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, try: df[key] except KeyError: + # TODO: change error message in tests raise exceptions.PlotlyError( "x, y, facet_row, facet_col and color_name must be keys " - "in your dataframe." + "in your dataframe if they are not set to None." ) # autoscale histogram bars if trace_type not in ['scatter', 'scattergl']: @@ -820,10 +829,13 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, "'trace_type' must be in {}".format(VALID_TRACE_TYPES) ) - if trace_type == 'histogram': - SUBPLOT_SPACING = 0.06 + if theme == 'sparklines': + SUBPLOT_SPACING = 0.0 else: - SUBPLOT_SPACING = 0.015 + if trace_type == 'histogram': + SUBPLOT_SPACING = 0.06 + else: + SUBPLOT_SPACING = 0.015 # seperate kwargs for marker and else if 'marker' in kwargs: @@ -977,17 +989,23 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, kwargs_trace, kwargs_marker ) + # style the layout depending on theme if not height: height = max(600, 100 * num_of_rows) if not width: width = max(600, 100 * num_of_cols) - fig['layout'].update(height=height, width=width, title='', - paper_bgcolor='rgb(251, 251, 251)') - if ggplot2: - fig['layout'].update(plot_bgcolor=PLOT_BGCOLOR, - paper_bgcolor='rgb(255, 255, 255)', - hovermode='closest') + if theme == 'sparklines': + fig['layout'].update(height=height, width=width, title='') + + else: + fig['layout'].update(height=height, width=width, title='', + paper_bgcolor='rgb(251, 251, 251)') + + if ggplot2: + fig['layout'].update(plot_bgcolor=PLOT_BGCOLOR, + paper_bgcolor='rgb(255, 255, 255)', + hovermode='closest') # axis titles x_title_annot = _axis_title_annotation(x, 'x') @@ -1048,68 +1066,75 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, fixed_axes = [] # fixed ranges - for x_y in fixed_axes: - min_ranges = [] - max_ranges = [] - for trace in fig['data']: - if trace[x_y] is not None and len(trace[x_y]) > 0: - min_ranges.append(min(trace[x_y])) - max_ranges.append(max(trace[x_y])) - while None in min_ranges: - min_ranges.remove(None) - while None in max_ranges: - max_ranges.remove(None) - - min_range = min(min_ranges) - max_range = max(max_ranges) - - range_are_numbers = (isinstance(min_range, Number) and - isinstance(max_range, Number)) - - if range_are_numbers: - min_range = math.floor(min_range) - max_range = math.ceil(max_range) - - # extend widen frame by 5% on each side - min_range -= 0.05 * (max_range - min_range) - max_range += 0.05 * (max_range - min_range) - - if x_y == 'x': - if dtick_x: - dtick = dtick_x - else: - dtick = math.floor( - (max_range - min_range) / MAX_TICKS_PER_AXIS - ) - elif x_y == 'y': - if dtick_y: - dtick = dtick_y - else: - dtick = math.floor( - (max_range - min_range) / MAX_TICKS_PER_AXIS - ) - else: - dtick = 1 - - for axis_title in axis_labels[x_y]: - fig['layout'][axis_title]['dtick'] = dtick - fig['layout'][axis_title]['ticklen'] = 0 - fig['layout'][axis_title]['zeroline'] = False - if ggplot2: - fig['layout'][axis_title]['tickwidth'] = 1 - fig['layout'][axis_title]['ticklen'] = 4 - fig['layout'][axis_title]['gridwidth'] = GRID_WIDTH - - fig['layout'][axis_title]['gridcolor'] = GRID_COLOR - fig['layout'][axis_title]['gridwidth'] = 2 - fig['layout'][axis_title]['tickfont'] = { - 'color': TICK_COLOR, 'size': 10 - } - - # insert ranges into fig - if x_y in fixed_axes: - for key in fig['layout']: - if '{}axis'.format(x_y) in key and range_are_numbers: - fig['layout'][key]['range'] = [min_range, max_range] + if theme != 'sparklines': + for x_y in fixed_axes: + min_ranges = [] + max_ranges = [] + for trace in fig['data']: + if trace[x_y] is not None and len(trace[x_y]) > 0: + min_ranges.append(min(trace[x_y])) + max_ranges.append(max(trace[x_y])) + while None in min_ranges: + min_ranges.remove(None) + while None in max_ranges: + max_ranges.remove(None) + + min_range = min(min_ranges) + max_range = max(max_ranges) + + range_are_numbers = (isinstance(min_range, Number) and + isinstance(max_range, Number)) + + if range_are_numbers: + min_range = math.floor(min_range) + max_range = math.ceil(max_range) + + # extend widen frame by 5% on each side + min_range -= 0.05 * (max_range - min_range) + max_range += 0.05 * (max_range - min_range) + + if x_y == 'x': + if dtick_x: + dtick = dtick_x + else: + dtick = math.floor( + (max_range - min_range) / MAX_TICKS_PER_AXIS + ) + elif x_y == 'y': + if dtick_y: + dtick = dtick_y + else: + dtick = math.floor( + (max_range - min_range) / MAX_TICKS_PER_AXIS + ) + else: + dtick = 1 + + for axis_title in axis_labels[x_y]: + fig['layout'][axis_title]['dtick'] = dtick + fig['layout'][axis_title]['ticklen'] = 0 + fig['layout'][axis_title]['zeroline'] = False + if ggplot2: + fig['layout'][axis_title]['tickwidth'] = 1 + fig['layout'][axis_title]['ticklen'] = 4 + fig['layout'][axis_title]['gridwidth'] = GRID_WIDTH + + fig['layout'][axis_title]['gridcolor'] = GRID_COLOR + fig['layout'][axis_title]['gridwidth'] = 2 + fig['layout'][axis_title]['tickfont'] = { + 'color': TICK_COLOR, 'size': 10 + } + + # insert ranges into fig + if x_y in fixed_axes: + for key in fig['layout']: + if '{}axis'.format(x_y) in key and range_are_numbers: + fig['layout'][key]['range'] = [min_range, max_range] + else: + for x_y in fixed_axes: + for axis_title in axis_labels[x_y]: + fig['layout'][axis_title]['showgrid'] = False + fig['layout'][axis_title]['showticklabels'] = False + fig['layout'][axis_title]['zeroline'] = False return fig From 5b3008648d3164c956e45a086b6aaa5b8270e963 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Mon, 9 Apr 2018 14:26:40 -0400 Subject: [PATCH 15/19] column_width param --- plotly/figure_factory/_facet_grid.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/plotly/figure_factory/_facet_grid.py b/plotly/figure_factory/_facet_grid.py index e31b59bcee6..4d7643fd36b 100644 --- a/plotly/figure_factory/_facet_grid.py +++ b/plotly/figure_factory/_facet_grid.py @@ -191,12 +191,13 @@ def _facet_grid_color_categorical(df, x, y, facet_row, facet_col, color_name, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, marker_color, - kwargs_trace, kwargs_marker): + kwargs_trace, kwargs_marker, column_width): fig = make_subplots(rows=num_of_rows, cols=num_of_cols, shared_xaxes=True, shared_yaxes=True, horizontal_spacing=SUBPLOT_SPACING, - vertical_spacing=SUBPLOT_SPACING, print_grid=False) + vertical_spacing=SUBPLOT_SPACING, print_grid=False, + column_width=column_width) annotations = [] if not facet_row and not facet_col: @@ -344,12 +345,13 @@ def _facet_grid_color_numerical(df, x, y, facet_row, facet_col, color_name, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, marker_color, kwargs_trace, - kwargs_marker): + kwargs_marker, column_width): fig = make_subplots(rows=num_of_rows, cols=num_of_cols, shared_xaxes=True, shared_yaxes=True, horizontal_spacing=SUBPLOT_SPACING, - vertical_spacing=SUBPLOT_SPACING, print_grid=False) + vertical_spacing=SUBPLOT_SPACING, print_grid=False, + column_width=column_width) annotations = [] if not facet_row and not facet_col: @@ -484,12 +486,14 @@ def _facet_grid_color_numerical(df, x, y, facet_row, facet_col, color_name, def _facet_grid(df, x, y, facet_row, facet_col, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, - SUBPLOT_SPACING, marker_color, kwargs_trace, kwargs_marker): + SUBPLOT_SPACING, marker_color, kwargs_trace, kwargs_marker, + column_width): fig = make_subplots(rows=num_of_rows, cols=num_of_cols, shared_xaxes=True, shared_yaxes=True, horizontal_spacing=SUBPLOT_SPACING, - vertical_spacing=SUBPLOT_SPACING, print_grid=False) + vertical_spacing=SUBPLOT_SPACING, print_grid=False, + column_width=column_width) annotations = [] if not facet_row and not facet_col: trace = dict( @@ -616,6 +620,7 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, row_colors=('rgb(247, 247, 242)', 'rgb(255, 253, 250)'), theme='facet', + column_width=None, **kwargs): """ Returns figure for facet grid. @@ -665,6 +670,9 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, :param (tuple|list) row_colors: :param (str) theme: determines the layout style of the plot. The options are 'facet' (default) and 'sparklines'. + :param (list) column_width: Specify a list that contains numbers where + the amount of numbers in the list is equal to `chart_types`. Call + `help(plotly.tools.make_subplots)` for more info on this subplot param :param (dict) kwargs: a dictionary of scatterplot arguments. Examples 1: One Way Faceting From 8a21ff76eeb9622ee367274c2ccedd341d619a04 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Thu, 12 Apr 2018 10:32:52 -0400 Subject: [PATCH 16/19] alternate colors working --- plotly/figure_factory/_facet_grid.py | 182 +++++++++++++++++++++------ 1 file changed, 142 insertions(+), 40 deletions(-) diff --git a/plotly/figure_factory/_facet_grid.py b/plotly/figure_factory/_facet_grid.py index 4d7643fd36b..d1019b27511 100644 --- a/plotly/figure_factory/_facet_grid.py +++ b/plotly/figure_factory/_facet_grid.py @@ -8,6 +8,7 @@ import math import copy from numbers import Number +import numpy as np pd = optional_imports.get_module('pandas') @@ -35,6 +36,21 @@ ) +def rect(xref, yref, x0, x1, y0, y1, color): + shape = { + 'layer': 'below', + 'xref': xref, + 'yref': yref, + 'x0': x0, + 'x1': x1, + 'y0': y0, + 'y1': y1, + 'fillcolor': color, + 'line': {'width': 0} + } + return shape + + def _is_flipped(num): if num >= THRES_FOR_FLIPPED_FACET_TITLES: flipped = True @@ -487,10 +503,11 @@ def _facet_grid(df, x, y, facet_row, facet_col, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, marker_color, kwargs_trace, kwargs_marker, - column_width): + row_colors, alternate_row_color, theme, column_width): + shared_xaxes = shared_yaxes = False fig = make_subplots(rows=num_of_rows, cols=num_of_cols, - shared_xaxes=True, shared_yaxes=True, + shared_xaxes=shared_xaxes, shared_yaxes=shared_yaxes, horizontal_spacing=SUBPLOT_SPACING, vertical_spacing=SUBPLOT_SPACING, print_grid=False, column_width=column_width) @@ -548,13 +565,14 @@ def _facet_grid(df, x, y, facet_row, facet_col, num_of_rows, ) annotations.append( - _annotation_dict( + utils.annotation_dict_for_label( label, num_of_rows - j if facet_row else j + 1, num_of_rows if facet_row else num_of_cols, SUBPLOT_SPACING, 'row' if facet_row else 'col', - flipped_rows + flipped_rows, + column_width=column_width ) ) @@ -565,44 +583,64 @@ def _facet_grid(df, x, y, facet_row, facet_col, num_of_rows, row_values = df[facet_row].unique() col_values = df[facet_col].unique() + for row_count, x_val in enumerate(row_values): for col_count, y_val in enumerate(col_values): try: group = tuple_to_facet_group[(x_val, y_val)] except KeyError: group = pd.DataFrame([[None, None]], columns=[x, y]) - trace = dict( - type=trace_type, - marker=dict( - color=marker_color, - line=kwargs_marker['line'], - ), - **kwargs_trace - ) + + # make the trace + # 'bullet', 'label', 'avg', 'area' + mode = None + if trace_type == 'bullet': + # do something + + elif trace_type in ['scatter', 'scattergl', 'histogram', 'bar', 'box', 'line']: + trace = dict( + type=trace_type, + marker=dict( + color=marker_color, + line=kwargs_marker['line'], + ), + **kwargs_trace + ) + if trace_type == 'line': + trace['mode'] = 'lines+markers' + trace['type'] = 'scatter' + + elif trace_type in ['scatter', 'scattergl']: + trace['mode'] = 'markers' + if x: trace['x'] = group[x] if y: trace['y'] = group[y] - trace = _make_trace_for_scatter( - trace, trace_type, marker_color, **kwargs_marker - ) fig.append_trace(trace, row_count + 1, col_count + 1) + if row_count == 0: label = _return_label(col_values[col_count], facet_col_labels, facet_col) annotations.append( - _annotation_dict(label, col_count + 1, num_of_cols, SUBPLOT_SPACING, - row_col='col', flipped=flipped_cols) + utils.annotation_dict_for_label( + label, col_count + 1, num_of_cols, SUBPLOT_SPACING, + row_col='col', flipped=flipped_cols, + column_width=column_width ) + ) label = _return_label(row_values[row_count], facet_row_labels, facet_row) + annotations.append( - _annotation_dict(label, num_of_rows - row_count, num_of_rows, SUBPLOT_SPACING, - row_col='row', flipped=flipped_rows) + utils.annotation_dict_for_label( + label, num_of_rows - row_count, num_of_rows, + SUBPLOT_SPACING, row_col='row', flipped=flipped_rows + ) ) # add annotations @@ -619,6 +657,7 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, show_boxes=True, ggplot2=False, binsize=1, row_colors=('rgb(247, 247, 242)', 'rgb(255, 253, 250)'), + alternate_row_color=True, theme='facet', column_width=None, **kwargs): @@ -929,7 +968,8 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, df, x, y, facet_row, facet_col, color_name, colormap, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, - SUBPLOT_SPACING, marker_color, kwargs_trace, kwargs_marker + SUBPLOT_SPACING, marker_color, kwargs_trace, kwargs_marker, + column_width ) elif isinstance(df[color_name].iloc[0], Number): @@ -949,7 +989,7 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, marker_color, - kwargs_trace, kwargs_marker + kwargs_trace, kwargs_marker, column_width ) elif isinstance(colormap, list): @@ -961,7 +1001,7 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, colorscale_list, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, - marker_color, kwargs_trace, kwargs_marker + marker_color, kwargs_trace, kwargs_marker, column_width ) elif isinstance(colormap, str): if colormap in colors.PLOTLY_SCALES.keys(): @@ -977,7 +1017,7 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, colorscale_list, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, - marker_color, kwargs_trace, kwargs_marker + marker_color, kwargs_trace, kwargs_marker, column_width ) else: colorscale_list = colors.PLOTLY_SCALES['Reds'] @@ -986,7 +1026,7 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, colorscale_list, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, - marker_color, kwargs_trace, kwargs_marker + marker_color, kwargs_trace, kwargs_marker, column_width ) else: @@ -994,7 +1034,7 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, df, x, y, facet_row, facet_col, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, marker_color, - kwargs_trace, kwargs_marker + kwargs_trace, kwargs_marker, row_colors, alternate_row_color, theme, column_width ) # style the layout depending on theme @@ -1064,17 +1104,22 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, for axis_name in axis_labels[x_y]: fig['layout'][axis_name]['type'] = 'category' - if scales == 'fixed': + if theme == 'facet': + if scales == 'fixed': + fixed_axes = ['x', 'y'] + elif scales == 'free_x': + fixed_axes = ['y'] + elif scales == 'free_y': + fixed_axes = ['x'] + elif scales == 'free': + fixed_axes = [] + else: fixed_axes = ['x', 'y'] - elif scales == 'free_x': - fixed_axes = ['y'] - elif scales == 'free_y': - fixed_axes = ['x'] - elif scales == 'free': - fixed_axes = [] # fixed ranges - if theme != 'sparklines': + + # all xaxis + if theme == 'facet': for x_y in fixed_axes: min_ranges = [] max_ranges = [] @@ -1097,7 +1142,7 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, min_range = math.floor(min_range) max_range = math.ceil(max_range) - # extend widen frame by 5% on each side + # widen frame by 5% on each side min_range -= 0.05 * (max_range - min_range) max_range += 0.05 * (max_range - min_range) @@ -1133,16 +1178,73 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, 'color': TICK_COLOR, 'size': 10 } - # insert ranges into fig - if x_y in fixed_axes: - for key in fig['layout']: - if '{}axis'.format(x_y) in key and range_are_numbers: - fig['layout'][key]['range'] = [min_range, max_range] + for key in fig['layout']: + if '{}axis'.format(x_y) in key and range_are_numbers: + fig['layout'][key]['range'] = [min_range, max_range] + + # adjust range for individual subplot + # and add bkgd panel colors else: - for x_y in fixed_axes: + # layout styling + for x_y in ['x', 'y']: for axis_title in axis_labels[x_y]: fig['layout'][axis_title]['showgrid'] = False fig['layout'][axis_title]['showticklabels'] = False fig['layout'][axis_title]['zeroline'] = False + + # set ranges + c_idx = 0 + for i, trace in enumerate(fig['data']): + min_x = min(trace['x']) + max_x = max(trace['x']) + + min_y = min(trace['y']) + max_y = max(trace['y']) + + range_are_numbers = (isinstance(min_y, Number) and + isinstance(max_y, Number)) + + if range_are_numbers: + min_y = math.floor(min_y) + max_y = math.ceil(max_y) + + # widen frame on each side + std_x = np.std(trace['x']) + std_y = np.std(trace['y']) + if min_x == max_x: + xrange_bottom = min_x - 0.3 * abs(list(trace['x'])[0]) + xrange_top = max_x + 0.3 * abs(list(trace['x'])[0]) + else: + xrange_bottom = min_x - 2 * std_x + xrange_top = max_x + 2 * std_x + + if min_y == max_y: + yrange_bottom = min_y - 0.5 + yrange_top = max_y + 0.5 + else: + yrange_bottom = min_y - 2 * std_y + yrange_top = max_y + 2 * std_y + + fig['layout']['xaxis{}'.format(i + 1)]['range'] = [xrange_bottom, xrange_top] + fig['layout']['yaxis{}'.format(i + 1)]['range'] = [yrange_bottom, yrange_top] + + # add shapes to bkgd + if alternate_row_color: + if c_idx >= len(row_colors): + c_idx = 0 + r_color = row_colors[c_idx] + bkg = rect( + 'x{}'.format(i + 1), + 'y{}'.format(i + 1), + x0=xrange_bottom, x1=xrange_top, + y0=yrange_bottom, y1=yrange_top, + color=( + r_color + ) + ) + fig['layout']['shapes'].append(bkg) + + if (i + 1) % num_of_cols == 0: + c_idx += 1 return fig From 8080beca3930de8441c6725a250306d03877e48c Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Mon, 23 Apr 2018 14:17:48 -0400 Subject: [PATCH 17/19] function to handle trace creation in _facet_grid --- plotly/figure_factory/_facet_grid.py | 424 ++++++++++++++++++++++----- 1 file changed, 343 insertions(+), 81 deletions(-) diff --git a/plotly/figure_factory/_facet_grid.py b/plotly/figure_factory/_facet_grid.py index d1019b27511..ee9969a0d28 100644 --- a/plotly/figure_factory/_facet_grid.py +++ b/plotly/figure_factory/_facet_grid.py @@ -5,10 +5,14 @@ from plotly.graph_objs import graph_objs from plotly.tools import make_subplots +import plotly +import plotly.graph_objs as go + import math import copy from numbers import Number import numpy as np +import re pd = optional_imports.get_module('pandas') @@ -26,8 +30,9 @@ THRES_FOR_FLIPPED_FACET_TITLES = 10 GRID_WIDTH = 1 -VALID_TRACE_TYPES = ['scatter', 'scattergl', 'histogram', 'bar', 'box'] + ['line', 'bullet', 'label', 'avg', 'area'] - +X_AND_Y_TRACE_TYPES = ['scatter', 'scattergl', 'line', 'area'] +X_OR_Y_TRACE_TYPES = ['histogram', 'bar', 'box', 'bullet', 'label', 'avg'] +VALID_TRACE_TYPES = X_AND_Y_TRACE_TYPES + X_OR_Y_TRACE_TYPES CUSTOM_LABEL_ERROR = ( "If you are using a dictionary for custom labels for the facet row/col, " @@ -202,12 +207,147 @@ def _make_trace_for_scatter(trace, trace_type, color, **kwargs_marker): return trace +def _return_traces_list_for_subplot_cell(trace_type, group, x, y, theme, + trace_dimension, + light_color, dark_color, + marker_color, kwargs_marker, + kwargs_trace): + + mean = np.mean(group[x]) + rounded_mean = round(mean, 2) + mode = None + traces_for_cell = [] + if trace_type == 'bullet': + bullet_range = go.Bar( + x=[rounded_mean], + y=[0.5], + marker=dict( + color='rgb(230,60,140)', + line=kwargs_marker['line'], + ), + hoverinfo='x', + orientation='h', + width=0.5, + **kwargs_trace + ) + + bullet_measure = go.Bar( + x=[list(group[x])[-1]], + y=[0.5], + marker=dict( + color=marker_color, + line=kwargs_marker['line'], + ), + hoverinfo='x', + orientation='h', + width=0.14, + offset=-0.07, + **kwargs_trace + ) + + bullet_pt = go.Scatter( + x=[max(group[x])], + y=[0.5], + hoverinfo='x', + line=kwargs_marker['line'], + **kwargs_trace + ) + + traces_for_cell.append(bullet_range) + traces_for_cell.append(bullet_measure) + traces_for_cell.append(bullet_pt) + + elif trace_type in ['avg']: + # deal with label + pass + + elif trace_type in ['scatter', 'scattergl', 'line', + 'histogram', 'bar', 'box']: + trace = dict( + type=trace_type, + marker=dict( + color=dark_color, + line=kwargs_marker['line'], + ), + **kwargs_trace + ) + + last_pt = dict( + type=trace_type, + marker=dict( + color=light_color, + ), + **kwargs_trace + ) + + if trace_type == 'line': + trace['mode'] = 'lines' + trace['type'] = 'scatter' + last_pt['mode'] = 'markers' + last_pt['type'] = 'scatter' + + elif trace_type in ['scatter', 'scattergl']: + trace['mode'] = 'markers' + last_pt['mode'] = 'markers' + + if theme == 'sparklines': + if trace_dimension == 'x': + trace['y'] = list(group[y]) if len(group[y]) <= 1 else list(group[y]) + last_pt['y'] = list(group[y])[-1:] + + trace['x'] = range(len(group[y])) + last_pt['x'] = range(len(group[y]))[-1:] + elif trace_dimension == 'y': + trace['x'] = list(group[x]) if len(group[x]) <= 1 else list(group[x]) + last_pt['x'] = list(group[x])[-1:] + + trace['y'] = range(len(group[y])) + last_pt['y'] = range(len(group[y]))[-1:] + elif trace_dimension == 'x+y': + if x: + trace['x'] = list(group[x]) if len(group[x]) <= 1 else list(group[x])[:-1] + last_pt['x'] = list(group[x])[-1:] + if y: + trace['y'] = list(group[y]) if len(group[y]) <= 1 else list(group[y])[:-1] + last_pt['y'] = list(group[y])[-1:] + else: + if x: + trace['x'] = list(group[x]) if len(group[x]) <= 1 else list(group[x]) + last_pt['x'] = list(group[x])[-1:] + if y: + trace['y'] = list(group[y]) if len(group[y]) <= 1 else list(group[y])[:-1] + last_pt['y'] = list(group[y])[-1:] + + traces_for_cell.append(trace) + traces_for_cell.append(last_pt) + + elif trace_type == 'area': + trace = dict( + x=range(len(group[y])), + type='scatter', + mode='lines', + fill='tozeroy', + fillcolor=light_color, + line=dict( + color=dark_color + ) + ) + + if y: + trace['y'] = list(group[y]) if len(group[y]) <= 1 else list(group[y])[:-1] + + traces_for_cell.append(trace) + + return traces_for_cell + + def _facet_grid_color_categorical(df, x, y, facet_row, facet_col, color_name, colormap, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, marker_color, - kwargs_trace, kwargs_marker, column_width): + kwargs_trace, kwargs_marker, column_width, + trace_dimension): fig = make_subplots(rows=num_of_rows, cols=num_of_cols, shared_xaxes=True, shared_yaxes=True, @@ -361,7 +501,8 @@ def _facet_grid_color_numerical(df, x, y, facet_row, facet_col, color_name, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, marker_color, kwargs_trace, - kwargs_marker, column_width): + kwargs_marker, column_width, + trace_dimension): fig = make_subplots(rows=num_of_rows, cols=num_of_cols, shared_xaxes=True, shared_yaxes=True, @@ -489,7 +630,8 @@ def _facet_grid_color_numerical(df, x, y, facet_row, facet_col, color_name, facet_row_labels, facet_row) annotations.append( _annotation_dict(row_values[row_count], - num_of_rows - row_count, num_of_rows, SUBPLOT_SPACING, + num_of_rows - row_count, num_of_rows, + SUBPLOT_SPACING, row_col='row', flipped=flipped_rows) ) @@ -503,7 +645,8 @@ def _facet_grid(df, x, y, facet_row, facet_col, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, marker_color, kwargs_trace, kwargs_marker, - row_colors, alternate_row_color, theme, column_width): + row_colors, alternate_row_color, theme, column_width, + trace_dimension, trace_colors_2d): shared_xaxes = shared_yaxes = False fig = make_subplots(rows=num_of_rows, cols=num_of_cols, @@ -526,10 +669,10 @@ def _facet_grid(df, x, y, facet_row, facet_col, num_of_rows, trace['x'] = df[x] if y: trace['y'] = df[y] - trace = _make_trace_for_scatter( - trace, trace_type, marker_color, **kwargs_marker - ) + if trace_type in ['scatter', 'scattergl']: + trace['mode'] = 'markers' + trace['marker'] = dict(color=marker_color, **kwargs_marker) fig.append_trace(trace, 1, 1) elif (facet_row and not facet_col) or (not facet_row and facet_col): @@ -554,9 +697,11 @@ def _facet_grid(df, x, y, facet_row, facet_col, num_of_rows, trace, trace_type, marker_color, **kwargs_marker ) - fig.append_trace(trace, - j + 1 if facet_row else 1, - 1 if facet_row else j + 1) + fig.append_trace( + trace, + j + 1 if facet_row else 1, + 1 if facet_row else j + 1 + ) label = _return_label( group[0], @@ -584,6 +729,7 @@ def _facet_grid(df, x, y, facet_row, facet_col, num_of_rows, row_values = df[facet_row].unique() col_values = df[facet_col].unique() + trace_c_idx = 0 for row_count, x_val in enumerate(row_values): for col_count, y_val in enumerate(col_values): try: @@ -591,34 +737,21 @@ def _facet_grid(df, x, y, facet_row, facet_col, num_of_rows, except KeyError: group = pd.DataFrame([[None, None]], columns=[x, y]) - # make the trace - # 'bullet', 'label', 'avg', 'area' - mode = None - if trace_type == 'bullet': - # do something - - elif trace_type in ['scatter', 'scattergl', 'histogram', 'bar', 'box', 'line']: - trace = dict( - type=trace_type, - marker=dict( - color=marker_color, - line=kwargs_marker['line'], - ), - **kwargs_trace - ) - if trace_type == 'line': - trace['mode'] = 'lines+markers' - trace['type'] = 'scatter' - - elif trace_type in ['scatter', 'scattergl']: - trace['mode'] = 'markers' + # set light and dark colors + if trace_c_idx >= len(trace_colors_2d): + trace_c_idx = 0 + light_color = trace_colors_2d[trace_c_idx][1] + dark_color = trace_colors_2d[trace_c_idx][0] - if x: - trace['x'] = group[x] - if y: - trace['y'] = group[y] + traces_for_cell = _return_traces_list_for_subplot_cell( + trace_type, group, x, y, theme, trace_dimension, + light_color, dark_color, marker_color, kwargs_marker, + kwargs_trace + ) - fig.append_trace(trace, row_count + 1, col_count + 1) + # insert traces in subplot cell + for trace in traces_for_cell: + fig.append_trace(trace, row_count + 1, col_count + 1) if row_count == 0: label = _return_label(col_values[col_count], @@ -642,6 +775,7 @@ def _facet_grid(df, x, y, facet_row, facet_col, num_of_rows, SUBPLOT_SPACING, row_col='row', flipped=flipped_rows ) ) + trace_c_idx += 1 # add annotations fig['layout']['annotations'] = annotations @@ -658,8 +792,9 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, row_colors=('rgb(247, 247, 242)', 'rgb(255, 253, 250)'), alternate_row_color=True, - theme='facet', - column_width=None, + theme='facet', column_width=None, trace_dimension=None, + trace_colors=None, x_margin_factor=0.2, + y_margin_factor=0.2, **kwargs): """ Returns figure for facet grid. @@ -712,6 +847,40 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, :param (list) column_width: Specify a list that contains numbers where the amount of numbers in the list is equal to `chart_types`. Call `help(plotly.tools.make_subplots)` for more info on this subplot param + :param (str) trace_dimension: the trace data for plotting the various + charts. Some charts can only work properly with one variable such as + bullet or histogram, while scatter and area charts can take 'x' and + 'y' data. The valid options are 'x', 'y' and 'x+y'. If trace_dimension + is not set, it will automatically set to: + - 'x' if x=None and y=None OR x='foo' and y='bar' + - 'x' or 'y' if ONLY one of 'x' or 'y' is set + :param (list|tuple) trace_colors: a list of colors or a list of lists of + two colors. Each row in a sparkline chart uses two colors: a darker + one and a lighter one. + Options: + - list of 2 colors: first color is dark color for all traces and + second is light for all traces. + - 1D list of more than 2 colors: the nth color in the list is the + nth dark color for the traces and the associated light color is + just 0.5 times the opacity of the dark color + - lists of lists: each inner list must have exactly 2 colors in it + and the first and second color of the nth inner list represent + the dark and light color for the nth row repsectively + - list of lists and colors: this is a combination of the previous + options + Whenever trace_colors has fewer colors than the number of rows of the + figure, the colors will repeat from the start of the list + Default = [['rgb(62,151,169)', 'rgb(181,221,232)']] + :param (float) x_margin_factor: proportional to how much margin space + along the x axis there is from the data points to the subplot cell + border. The x_margin_factor is multiplied by the standard deviation of + the x-values of the data to yield the actual margin. + Default = 0.2 + :param (float) y_margin_factor: proportional to how much margin space + along the y axis there is from the data points to the subplot cell + border. The y_margin_factor is multiplied by the standard deviation of + the y-values of the data to yield the actual margin. + Default = 0.2 :param (dict) kwargs: a dictionary of scatterplot arguments. Examples 1: One Way Faceting @@ -844,13 +1013,6 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, # make sure all columns are of homogenous datatype utils.validate_dataframe(df) - if trace_type in ['scatter', 'scattergl']: - if not x or not y: - raise exceptions.PlotlyError( - "You need to input 'x' and 'y' if you are you are using a " - "trace_type of 'scatter' or 'scattergl'." - ) - for key in [x, y, facet_row, facet_col, color_name]: if key is not None: try: @@ -876,6 +1038,12 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, "'trace_type' must be in {}".format(VALID_TRACE_TYPES) ) + # theme + if theme not in ['facet', 'sparklines']: + raise exceptions.PlotlyError( + "theme must be 'facet' or 'sparklines'" + ) + if theme == 'sparklines': SUBPLOT_SPACING = 0.0 else: @@ -916,6 +1084,49 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, else: marker_color = 'rgb(0, 0, 0)' + # set trace dimension + if trace_dimension is None: + if x is None and y is None: + trace_dimension = 'x' + elif x is not None and y is not None: + trace_dimension = 'x+y' + elif x and not y: + trace_dimension = 'x' + elif not x and y: + trace_dimension = 'y' + + if trace_dimension not in ['x', 'y', 'x+y']: + raise exceptions.PlotlyError( + "trace_dimension must be either 'x', 'y' or 'x+y'" + ) + + + # validate list/tuple of trace_colors + if not trace_colors: + trace_colors = [['rgb(62,151,169)', 'rgb(181,221,232)']] + if not utils.is_sequence(trace_colors): + raise exceptions.PlotlyError( + 'trace_colors must be a list/tuple' + ) + + trace_colors_2d = [] + for i, item in enumerate(trace_colors): + plotly.colors.validate_colors(item) + if utils.is_sequence(item): + trace_colors_2d.append(item) + else: + # if hex convert to rgb + if '#' in item: + tuple_item = plotly.colors.hex_to_rgb(item) + rgb_item = plotly.colors.label_rgb(tuple_item) + else: + rgb_item = item + light_c = plotly.colors.find_intermediate_color( + rgb_item, 'rgb(255, 255, 255)', 0.5, colortype='rgb' + ) + trace_colors_2d.append([rgb_item, light_c]) + + num_of_rows = 1 num_of_cols = 1 flipped_rows = False @@ -940,6 +1151,10 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, raise exceptions.PlotlyError( CUSTOM_LABEL_ERROR.format(unique_keys) ) + + if column_width is None: + column_width = [1 for _ in range(num_of_cols)] + show_legend = False if color_name: if isinstance(df[color_name].iloc[0], str) or color_is_cat: @@ -969,7 +1184,7 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, marker_color, kwargs_trace, kwargs_marker, - column_width + column_width, trace_dimension ) elif isinstance(df[color_name].iloc[0], Number): @@ -989,7 +1204,8 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, marker_color, - kwargs_trace, kwargs_marker, column_width + kwargs_trace, kwargs_marker, column_width, + trace_dimension ) elif isinstance(colormap, list): @@ -1001,7 +1217,8 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, colorscale_list, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, - marker_color, kwargs_trace, kwargs_marker, column_width + marker_color, kwargs_trace, kwargs_marker, column_width, + trace_dimension ) elif isinstance(colormap, str): if colormap in colors.PLOTLY_SCALES.keys(): @@ -1017,7 +1234,8 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, colorscale_list, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, - marker_color, kwargs_trace, kwargs_marker, column_width + marker_color, kwargs_trace, kwargs_marker, column_width, + trace_dimension ) else: colorscale_list = colors.PLOTLY_SCALES['Reds'] @@ -1026,7 +1244,8 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, colorscale_list, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, - marker_color, kwargs_trace, kwargs_marker, column_width + marker_color, kwargs_trace, kwargs_marker, column_width, + trace_dimension ) else: @@ -1034,7 +1253,8 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, df, x, y, facet_row, facet_col, num_of_rows, num_of_cols, facet_row_labels, facet_col_labels, trace_type, flipped_rows, flipped_cols, show_boxes, SUBPLOT_SPACING, marker_color, - kwargs_trace, kwargs_marker, row_colors, alternate_row_color, theme, column_width + kwargs_trace, kwargs_marker, row_colors, alternate_row_color, + theme, column_width, trace_dimension, trace_colors_2d ) # style the layout depending on theme @@ -1117,8 +1337,6 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, fixed_axes = ['x', 'y'] # fixed ranges - - # all xaxis if theme == 'facet': for x_y in fixed_axes: min_ranges = [] @@ -1183,7 +1401,7 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, fig['layout'][key]['range'] = [min_range, max_range] # adjust range for individual subplot - # and add bkgd panel colors + # add bkgd panel colors else: # layout styling for x_y in ['x', 'y']: @@ -1191,51 +1409,96 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, fig['layout'][axis_title]['showgrid'] = False fig['layout'][axis_title]['showticklabels'] = False fig['layout'][axis_title]['zeroline'] = False - + # set ranges c_idx = 0 - for i, trace in enumerate(fig['data']): - min_x = min(trace['x']) - max_x = max(trace['x']) - - min_y = min(trace['y']) - max_y = max(trace['y']) + # extract numbers from axis labels (eg. 'xaxis7' -> '7') + axis_numbers = [] + for xaxis in axis_labels['x']: + num_from_axis = re.findall('[^xaxis]+', xaxis)[0] + axis_numbers.append(int(num_from_axis)) + axis_numbers = sorted(axis_numbers) + + for num in axis_numbers: + # collect all traces with same axes + traces_with_same_axes = [] + for trace in fig['data']: + if trace['xaxis'][1:] == str(num): + traces_with_same_axes.append(trace) + + min_x = min( + [min(trace['x']) for trace in traces_with_same_axes] + ) + max_x = max( + [max(trace['x']) for trace in traces_with_same_axes] + ) + min_y = min( + [min(trace['y']) for trace in traces_with_same_axes] + ) + max_y = max( + [max(trace['y']) for trace in traces_with_same_axes] + ) range_are_numbers = (isinstance(min_y, Number) and isinstance(max_y, Number)) + # TODO: set x, y ranges when string data if range_are_numbers: min_y = math.floor(min_y) max_y = math.ceil(max_y) - # widen frame on each side - std_x = np.std(trace['x']) - std_y = np.std(trace['y']) - if min_x == max_x: - xrange_bottom = min_x - 0.3 * abs(list(trace['x'])[0]) - xrange_top = max_x + 0.3 * abs(list(trace['x'])[0]) + all_x_data = [] + all_y_data = [] + for l in traces_with_same_axes: + for x in l['x']: + all_x_data.append(x) + for y in l['y']: + all_y_data.append(y) + + # handle x margin for cells + if any(item is None or item != item for item in all_x_data): + xrange_bottom = -1 + xrange_top = 1 + elif min_x == max_x: + xrange_bottom = min_x - 0.5 + xrange_top = max_x + 0.5 else: - xrange_bottom = min_x - 2 * std_x - xrange_top = max_x + 2 * std_x - - if min_y == max_y: + std_x = np.std(all_x_data) + xrange_bottom = min_x - x_margin_factor * std_x + xrange_top = max_x + x_margin_factor * std_x + + # handle y margins for cells + if any(item is None or item != item for item in all_y_data): + yrange_bottom = -1 + yrange_top = 1 + elif min_y == max_y: yrange_bottom = min_y - 0.5 yrange_top = max_y + 0.5 else: - yrange_bottom = min_y - 2 * std_y - yrange_top = max_y + 2 * std_y + std_y = np.std(all_y_data) + yrange_bottom = min_y - y_margin_factor * std_y + yrange_top = max_y + y_margin_factor * std_y + + else: + xrange_bottom = -2 + xrange_top = 2 + yrange_bottom = -2 + yrange_top = 2 - fig['layout']['xaxis{}'.format(i + 1)]['range'] = [xrange_bottom, xrange_top] - fig['layout']['yaxis{}'.format(i + 1)]['range'] = [yrange_bottom, yrange_top] + some_xaxis = 'xaxis{}'.format(str(num)) + some_yaxis = 'yaxis{}'.format(str(num)) + fig['layout'][some_xaxis]['range'] = [xrange_bottom, xrange_top] + fig['layout'][some_yaxis]['range'] = [yrange_bottom, yrange_top] # add shapes to bkgd if alternate_row_color: if c_idx >= len(row_colors): c_idx = 0 r_color = row_colors[c_idx] + bkg = rect( - 'x{}'.format(i + 1), - 'y{}'.format(i + 1), + 'x{}'.format(num), + 'y{}'.format(num), x0=xrange_bottom, x1=xrange_top, y0=yrange_bottom, y1=yrange_top, color=( @@ -1243,8 +1506,7 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, ) ) fig['layout']['shapes'].append(bkg) - - if (i + 1) % num_of_cols == 0: + if int(num) % num_of_cols == 0: c_idx += 1 return fig From 77add691b051bf4d03f0d8463063154df82100b0 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Thu, 3 May 2018 13:44:57 -0400 Subject: [PATCH 18/19] working on ranges for string data --- plotly/figure_factory/_facet_grid.py | 183 +++++++++++++++++---------- 1 file changed, 115 insertions(+), 68 deletions(-) diff --git a/plotly/figure_factory/_facet_grid.py b/plotly/figure_factory/_facet_grid.py index ee9969a0d28..7d3e4011e74 100644 --- a/plotly/figure_factory/_facet_grid.py +++ b/plotly/figure_factory/_facet_grid.py @@ -31,15 +31,22 @@ GRID_WIDTH = 1 X_AND_Y_TRACE_TYPES = ['scatter', 'scattergl', 'line', 'area'] -X_OR_Y_TRACE_TYPES = ['histogram', 'bar', 'box', 'bullet', 'label', 'avg'] +X_OR_Y_TRACE_TYPES = ['histogram', 'bar', 'box', 'bullet', 'text'] VALID_TRACE_TYPES = X_AND_Y_TRACE_TYPES + X_OR_Y_TRACE_TYPES CUSTOM_LABEL_ERROR = ( - "If you are using a dictionary for custom labels for the facet row/col, " - "make sure each key in that column of the dataframe is in your facet " - "labels. The keys you need are {}" + 'If you are using a dictionary for custom labels for the facet row/col, ' + 'make sure each key in that column of the dataframe is in your facet ' + 'labels. The keys you need are {}' ) +BULLET_USING_STRING_DATA_MSG = ( + 'Whoops. You are attempting to create a bullet chart out of an array of ' + 'data with at least one string in it.\n\nBullet charts in the facet grid ' + 'present the mean, standard deviation and maximum value of a 1D dataset. ' + 'Since all of these quantities are quantitiative, they can only be ' + 'generated from numerical data.' +) def rect(xref, yref, x0, x1, y0, y1, color): shape = { @@ -213,13 +220,34 @@ def _return_traces_list_for_subplot_cell(trace_type, group, x, y, theme, marker_color, kwargs_marker, kwargs_trace): - mean = np.mean(group[x]) - rounded_mean = round(mean, 2) mode = None traces_for_cell = [] if trace_type == 'bullet': + if 'x' in trace_dimension: + # check if data contains strings + if any(isinstance(e, str) for e in group[x]): + raise exceptions.PlotlyError( + BULLET_USING_STRING_DATA_MSG + ) + std = np.std(group[x]) + rounded_mean = round(np.mean(group[x]), 2) + max_value = max(group[x]) + else: + # check if data contains strings + if any(isinstance(e, str) for e in group[y]): + raise exceptions.PlotlyError( + BULLET_USING_STRING_DATA_MSG + ) + std = np.std(group[y]) + rounded_mean = round(np.mean(group[y]), 2) + max_value = max(group[y]) + + bullet_range_x = std + bullet_measure_x = rounded_mean + bullet_pt_x = max_value + bullet_range = go.Bar( - x=[rounded_mean], + x=[bullet_range_x], y=[0.5], marker=dict( color='rgb(230,60,140)', @@ -232,7 +260,7 @@ def _return_traces_list_for_subplot_cell(trace_type, group, x, y, theme, ) bullet_measure = go.Bar( - x=[list(group[x])[-1]], + x=[bullet_measure_x], y=[0.5], marker=dict( color=marker_color, @@ -246,7 +274,7 @@ def _return_traces_list_for_subplot_cell(trace_type, group, x, y, theme, ) bullet_pt = go.Scatter( - x=[max(group[x])], + x=[bullet_pt_x], y=[0.5], hoverinfo='x', line=kwargs_marker['line'], @@ -257,8 +285,7 @@ def _return_traces_list_for_subplot_cell(trace_type, group, x, y, theme, traces_for_cell.append(bullet_measure) traces_for_cell.append(bullet_pt) - elif trace_type in ['avg']: - # deal with label + elif trace_type in ['text']: # grab from a column pass elif trace_type in ['scatter', 'scattergl', 'line', @@ -280,48 +307,51 @@ def _return_traces_list_for_subplot_cell(trace_type, group, x, y, theme, **kwargs_trace ) - if trace_type == 'line': + if trace_type in ['scatter', 'scattergl']: + trace['mode'] = 'markers' + last_pt['mode'] = 'markers' + + elif trace_type == 'line': trace['mode'] = 'lines' trace['type'] = 'scatter' last_pt['mode'] = 'markers' last_pt['type'] = 'scatter' - elif trace_type in ['scatter', 'scattergl']: - trace['mode'] = 'markers' - last_pt['mode'] = 'markers' + if trace_dimension == 'x': + # add x + trace['x'] = list(group[x]) if len(group[x]) <= 1 else list(group[x])[:-1] + last_pt['x'] = list(group[x])[-1:] - if theme == 'sparklines': - if trace_dimension == 'x': - trace['y'] = list(group[y]) if len(group[y]) <= 1 else list(group[y]) - last_pt['y'] = list(group[y])[-1:] + #trace['y'] = [None] + #last_pt['y'] = [None] + elif trace_dimension == 'y': + #trace['x'] = [None] + #last_pt['x'] = [None] - trace['x'] = range(len(group[y])) - last_pt['x'] = range(len(group[y]))[-1:] - elif trace_dimension == 'y': - trace['x'] = list(group[x]) if len(group[x]) <= 1 else list(group[x]) + # add y + trace['y'] = list(group[y]) if len(group[y]) <= 1 else list(group[y])[:-1] + last_pt['y'] = list(group[y])[-1:] + else: # 'x+y' + if trace_type in ['scatter', 'scattergl', 'line', 'histogram']: + # add both x and y + trace['x'] = list(group[x]) if len(group[x]) <= 1 else list(group[x])[:-1] last_pt['x'] = list(group[x])[-1:] - trace['y'] = range(len(group[y])) - last_pt['y'] = range(len(group[y]))[-1:] - elif trace_dimension == 'x+y': - if x: - trace['x'] = list(group[x]) if len(group[x]) <= 1 else list(group[x])[:-1] - last_pt['x'] = list(group[x])[-1:] - if y: - trace['y'] = list(group[y]) if len(group[y]) <= 1 else list(group[y])[:-1] - last_pt['y'] = list(group[y])[-1:] - else: - if x: - trace['x'] = list(group[x]) if len(group[x]) <= 1 else list(group[x]) + #trace['y'] = list(group[y]) if len(group[y]) <= 1 else list(group[y])[:-1] + #last_pt['y'] = list(group[y])[-1:] + + elif trace_type in ['box', 'bar']: + # add only x + trace['x'] = list(group[x]) if len(group[x]) <= 1 else list(group[x])[:-1] last_pt['x'] = list(group[x])[-1:] - if y: - trace['y'] = list(group[y]) if len(group[y]) <= 1 else list(group[y])[:-1] - last_pt['y'] = list(group[y])[-1:] + + #trace['y'] = [None] + #last_pt['y'] = [None] traces_for_cell.append(trace) traces_for_cell.append(last_pt) - elif trace_type == 'area': + elif trace_type in ['area']: trace = dict( x=range(len(group[y])), type='scatter', @@ -335,7 +365,6 @@ def _return_traces_list_for_subplot_cell(trace_type, group, x, y, theme, if y: trace['y'] = list(group[y]) if len(group[y]) <= 1 else list(group[y])[:-1] - traces_for_cell.append(trace) return traces_for_cell @@ -647,7 +676,6 @@ def _facet_grid(df, x, y, facet_row, facet_col, num_of_rows, SUBPLOT_SPACING, marker_color, kwargs_trace, kwargs_marker, row_colors, alternate_row_color, theme, column_width, trace_dimension, trace_colors_2d): - shared_xaxes = shared_yaxes = False fig = make_subplots(rows=num_of_rows, cols=num_of_cols, shared_xaxes=shared_xaxes, shared_yaxes=shared_yaxes, @@ -717,7 +745,6 @@ def _facet_grid(df, x, y, facet_row, facet_col, num_of_rows, SUBPLOT_SPACING, 'row' if facet_row else 'col', flipped_rows, - column_width=column_width ) ) @@ -793,8 +820,8 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, 'rgb(255, 253, 250)'), alternate_row_color=True, theme='facet', column_width=None, trace_dimension=None, - trace_colors=None, x_margin_factor=0.2, - y_margin_factor=0.2, + trace_colors=None, x_margin_factor=0.4, + y_margin_factor=0.4, chart_types=None, **kwargs): """ Returns figure for facet grid. @@ -881,6 +908,8 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, border. The y_margin_factor is multiplied by the standard deviation of the y-values of the data to yield the actual margin. Default = 0.2 + :param (list|tuple) chart_types: a sequence (list/tuple/etc) of valid + chart names that each column will produce in order from left to right. :param (dict) kwargs: a dictionary of scatterplot arguments. Examples 1: One Way Faceting @@ -1084,23 +1113,16 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, else: marker_color = 'rgb(0, 0, 0)' - # set trace dimension + # set trace dimension if None if trace_dimension is None: - if x is None and y is None: - trace_dimension = 'x' - elif x is not None and y is not None: - trace_dimension = 'x+y' - elif x and not y: - trace_dimension = 'x' - elif not x and y: - trace_dimension = 'y' + trace_dimension = 'x' + # validate trace dimension if trace_dimension not in ['x', 'y', 'x+y']: raise exceptions.PlotlyError( "trace_dimension must be either 'x', 'y' or 'x+y'" ) - # validate list/tuple of trace_colors if not trace_colors: trace_colors = [['rgb(62,151,169)', 'rgb(181,221,232)']] @@ -1155,6 +1177,22 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, if column_width is None: column_width = [1 for _ in range(num_of_cols)] + # validate chart_types + # TODO: integrate this with trace_type eventually + # and keep backwards compatibility + if chart_types is None: + chart_types = ['scatter' for _ in range(num_of_cols)] + else: + # TODO: use sequence checker, not just list + if not isinstance(chart_types, list): + raise exceptions.PlotlyError( + 'chart_types must be a list' + ) + if len(chart_types) != num_of_cols: + raise exceptions.PlotlyError( + 'number of strings in chart_types must be equal to the ' + 'number of columns' + ) show_legend = False if color_name: if isinstance(df[color_name].iloc[0], str) or color_is_cat: @@ -1425,25 +1463,34 @@ def create_facet_grid(df, x=None, y=None, facet_row=None, facet_col=None, for trace in fig['data']: if trace['xaxis'][1:] == str(num): traces_with_same_axes.append(trace) + if trace['x'] is not None: + min_x = min( + [min(trace['x']) for trace in traces_with_same_axes] + ) + max_x = max( + [max(trace['x']) for trace in traces_with_same_axes] + ) + else: + min_x = [None] + max_x = [None] + if trace['y'] is not None: + min_y = min( + [min(trace['y']) for trace in traces_with_same_axes] + ) + max_y = max( + [max(trace['y']) for trace in traces_with_same_axes] + ) + else: + min_y = [None] + max_y = [None] - min_x = min( - [min(trace['x']) for trace in traces_with_same_axes] + range_are_numbers = all( + isinstance(n, Number) for n in [min_x, max_x, min_y, max_y] ) - max_x = max( - [max(trace['x']) for trace in traces_with_same_axes] - ) - min_y = min( - [min(trace['y']) for trace in traces_with_same_axes] - ) - max_y = max( - [max(trace['y']) for trace in traces_with_same_axes] - ) - - range_are_numbers = (isinstance(min_y, Number) and - isinstance(max_y, Number)) # TODO: set x, y ranges when string data if range_are_numbers: + print('range are numbers') min_y = math.floor(min_y) max_y = math.ceil(max_y) From 8d665acc999e3793915e6c8002761303176ee9f6 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Fri, 17 Aug 2018 17:09:17 -0400 Subject: [PATCH 19/19] rewrite shapes and annotations sections for new tuple format --- plotly/figure_factory/_sparkline.py | 55 ++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/plotly/figure_factory/_sparkline.py b/plotly/figure_factory/_sparkline.py index ce9bc2b95ff..e980bee5ba4 100644 --- a/plotly/figure_factory/_sparkline.py +++ b/plotly/figure_factory/_sparkline.py @@ -37,6 +37,8 @@ def _sparkline_tidy(df, fig, chart_types, num_of_chart_types, trace_colors_2d, column_width, alternate_row_color, row_colors, xanchor, line_width, align_x, scatter_options, new_marker_color, narrow_idxs, show_titles, row, column, x): + shapes = [] + annotations = [] # create and insert charts c_idx = 0 trace_c_idx = 0 @@ -62,7 +64,7 @@ def _sparkline_tidy(df, fig, chart_types, num_of_chart_types, trace_colors_2d, text = rkey else: text = 'nan' - fig['layout']['annotations'].append( + annotations.append( dict( xref='x{}'.format(j * num_of_chart_types + c + 1), yref='y{}'.format(j * num_of_chart_types + c + 1), @@ -89,7 +91,8 @@ def _sparkline_tidy(df, fig, chart_types, num_of_chart_types, trace_colors_2d, r_color ) ) - fig['layout']['shapes'].append(bkg) + # fig['layout']['shapes'].append(bkg) + shapes.append(bkg) fig.append_trace(empty_data, j + 1, c + 1) else: mean = np.mean(data) @@ -99,7 +102,7 @@ def _sparkline_tidy(df, fig, chart_types, num_of_chart_types, trace_colors_2d, text = rkey else: text = '{}'.format(rounded_mean) - fig['layout']['annotations'].append( + annotations.append( dict( xref='x{}'.format(j * num_of_chart_types + c + 1), yref='y{}'.format(j * num_of_chart_types + c + 1), @@ -126,7 +129,8 @@ def _sparkline_tidy(df, fig, chart_types, num_of_chart_types, trace_colors_2d, r_color ) ) - fig['layout']['shapes'].append(bkg) + # fig['layout']['shapes'].append(bkg) + shapes.append(bkg) fig.append_trace(empty_data, j + 1, c + 1) elif chart == 'bullet': @@ -172,7 +176,8 @@ def _sparkline_tidy(df, fig, chart_types, num_of_chart_types, trace_colors_2d, r_color ) ) - fig['layout']['shapes'].append(bkg) + # fig['layout']['shapes'].append(bkg) + shapes.append(bkg) fig.append_trace(bullet_range, j + 1, c + 1) fig.append_trace(bullet_measure, j + 1, c + 1) fig.append_trace(bullet_pt, j + 1, c + 1) @@ -239,7 +244,8 @@ def _sparkline_tidy(df, fig, chart_types, num_of_chart_types, trace_colors_2d, r_color ) ) - fig['layout']['shapes'].append(bkg) + # fig['layout']['shapes'].append(bkg) + shapes.append(bkg) fig.append_trace(trace_line, j + 1, c + 1) fig.append_trace(trace_line_pt, j + 1, c + 1) fig.append_trace(pt_hidden, j + 1, c + 1) @@ -283,7 +289,8 @@ def _sparkline_tidy(df, fig, chart_types, num_of_chart_types, trace_colors_2d, r_color ) ) - fig['layout']['shapes'].append(bkg) + # fig['layout']['shapes'].append(bkg) + shapes.append(bkg) fig.append_trace(trace_bar, j + 1, c + 1) fig['layout']['yaxis{}'.format( @@ -300,7 +307,7 @@ def _sparkline_tidy(df, fig, chart_types, num_of_chart_types, trace_colors_2d, ckey, c + 1, num_of_chart_types, subplot_spacing=0, row_col='col', flipped=False, column_width=column_width ) - fig['layout']['annotations'].append(label) + annotations.append(label) for idx in narrow_idxs: for axis in ['xaxis', 'yaxis']: @@ -310,11 +317,17 @@ def _sparkline_tidy(df, fig, chart_types, num_of_chart_types, trace_colors_2d, c_idx += 1 trace_c_idx += 1 + # add shapes and annotations as tuples + fig['layout']['shapes'] = shapes + fig['layout']['annotations'] = annotations + def _sparkline(df, fig, chart_types, num_of_chart_types, trace_colors_2d, column_width, alternate_row_color, row_colors, xanchor, line_width, align_x, scatter_options, new_marker_color, narrow_idxs, show_titles): + shapes = [] + annotations = [] # create and insert charts c_idx = 0 trace_c_idx = 0 @@ -337,7 +350,8 @@ def _sparkline(df, fig, chart_types, num_of_chart_types, trace_colors_2d, text = key else: text = '{}'.format(rounded_mean) - fig['layout']['annotations'].append( + # fig['layout']['annotations'].append( + annotations.append( dict( x=align_x, y=0.5, @@ -364,7 +378,8 @@ def _sparkline(df, fig, chart_types, num_of_chart_types, trace_colors_2d, r_color ) ) - fig['layout']['shapes'].append(bkg) + # fig['layout']['shapes'].append(bkg) + shapes.append(bkg) fig.append_trace(empty_data, j + 1, c + 1) elif chart == 'bullet': @@ -410,7 +425,8 @@ def _sparkline(df, fig, chart_types, num_of_chart_types, trace_colors_2d, r_color ) ) - fig['layout']['shapes'].append(bkg) + # fig['layout']['shapes'].append(bkg) + shapes.append(bkg) fig.append_trace(bullet_range, j + 1, c + 1) fig.append_trace(bullet_measure, j + 1, c + 1) fig.append_trace(bullet_pt, j + 1, c + 1) @@ -467,7 +483,8 @@ def _sparkline(df, fig, chart_types, num_of_chart_types, trace_colors_2d, r_color ) ) - fig['layout']['shapes'].append(bkg) + # fig['layout']['shapes'].append(bkg) + shapes.append(bkg) fig.append_trace(trace_line, j + 1, c + 1) fig.append_trace(trace_line_pt, j + 1, c + 1) @@ -508,7 +525,8 @@ def _sparkline(df, fig, chart_types, num_of_chart_types, trace_colors_2d, r_color ) ) - fig['layout']['shapes'].append(bkg) + # fig['layout']['shapes'].append(bkg) + shapes.append(bkg) fig.append_trace(trace_bar, j + 1, c + 1) fig['layout']['yaxis{}'.format( @@ -525,7 +543,8 @@ def _sparkline(df, fig, chart_types, num_of_chart_types, trace_colors_2d, chart, c + 1, num_of_chart_types, subplot_spacing=0, row_col='col', flipped=False, column_width=column_width ) - fig['layout']['annotations'].append(label) + # fig['layout']['annotations'].append(label) + annotations.append(label) for idx in narrow_idxs: for axis in ['xaxis', 'yaxis']: @@ -535,6 +554,8 @@ def _sparkline(df, fig, chart_types, num_of_chart_types, trace_colors_2d, c_idx += 1 trace_c_idx += 1 + fig['layout']['shapes'] = shapes + fig['layout']['annotations'] = annotations def create_sparkline(df, chart_types=None, row=None, column=None, x=None, trace_colors=None, column_width=None, @@ -766,15 +787,15 @@ def create_sparkline(df, chart_types=None, row=None, column=None, # layout options fig['layout'].update( title='', - annotations=[], - shapes=[], + # annotations=[], + # shapes=[], showlegend=False ) # update layout fig['layout'].update(layout_options) - for key in fig['layout'].keys(): + for key in fig['layout'].to_plotly_json().keys(): if 'axis' in key: fig['layout'][key].update( showgrid=False,