From 0eeae617319c2034356ce5781f6f1107ce75fec4 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Mon, 16 May 2016 17:10:45 -0400 Subject: [PATCH 01/27] First PR for Gantt Charts - Adding Tests Very Soon --- plotly/tools.py | 423 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 418 insertions(+), 5 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index 0a109d08c37..dbe5eaabd77 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1450,6 +1450,416 @@ class FigureFactory(object): more information and examples of a specific chart type. """ + @staticmethod + def _validate_gantt(df): + """ + Validates the inputted dataframe or list + """ + # check if pandas dataframe + if isinstance(df, pd.core.frame.DataFrame): + # extract data into list of dictionaries + if ('Task' not in df): + raise exceptions.PlotlyError("Your dataframe must have the " + "columns 'Task', 'Start' and " + "'Finish', with 'Complete' as " + "an optional column.") + if ('Start' not in df): + raise exceptions.PlotlyError("Your dataframe must have the " + "columns 'Task', 'Start' and " + "'Finish', with 'Complete' as " + "an optional column.") + if ('Finish' not in df): + raise exceptions.PlotlyError("Your dataframe must have the " + "columns 'Task', 'Start' and " + "'Finish', with 'Complete' as " + "an optional column.") + if 'Complete' in df: + for percentage in df['Complete']: + if (percentage < 0.0) or (percentage > 100.0): + raise exceptions.PlotlyError("The values in the " + "'Complete' column must " + "be between 0 and 100.") + + num_of_rows = len(df.index) + chart = [] + for index in range(num_of_rows): + task_dict = {} + task_dict['Task'] = df.ix[index]['Task'] + task_dict['Start'] = df.ix[index]['Start'] + task_dict['Finish'] = df.ix[index]['Finish'] + if 'Complete' in df: + task_dict['Complete'] = df.ix[index]['Complete'] + chart.append(task_dict) + return chart + + if not isinstance(df, list): + raise exceptions.PlotlyError("You must input either a dataframe " + "or a list of dictionaries.") + + if len(df) <= 0: + raise exceptions.PlotlyError("Your list is empty. It must contain " + "at least one dictionary.") + + if not isinstance(df[0], dict): + raise exceptions.PlotlyError("Your list must only " + "include dictionaries.") + + if 'Complete' in df[0]: + for dictionary in df: + if 'Complete' not in dictionary: + raise exceptions.PlotlyError("If you are using 'Complete' " + "as a dictionary key, make " + "sure each dictionary has " + "this key with an assigned " + "value between 0 and 100.") + return df + + @staticmethod + def _gantt(chart, colors, use_colorscale, title, + bar_width, showgrid_x, showgrid_y, + height, width, tasks=None, + task_names=None, data=None): + """ + Refer to FigureFactory.create_gantt() for docstring + """ + if tasks is None: + tasks = [] + if task_names is None: + task_names = [] + if data is None: + data = [] + + for index in range(len(chart)): + task = dict(x0=chart[index]['Start'], + x1=chart[index]['Finish'], + name=chart[index]['Task']) + tasks.append(task) + + shape_template = { + 'type': 'rect', + 'xref': 'x', + 'yref': 'y', + 'opacity': 1, + 'line': { + 'width': 0, + }, + 'yref': 'y', + } + + color_index = 0 + for index in range(len(tasks)): + tn = tasks[index]['name'] + task_names.append(tn) + del tasks[index]['name'] + tasks[index].update(shape_template) + tasks[index]['y0'] = index - bar_width + tasks[index]['y1'] = index + bar_width + + # check if colors need to be looped + if color_index >= len(colors): + color_index = 0 + tasks[index]['fillcolor'] = colors[color_index] + # Add a line for hover text and autorange + data.append( + dict( + x=[tasks[index]['x0'], tasks[index]['x1']], + y=[index, index], + name='', + marker={'color': 'white'} + ) + ) + color_index += 1 + + layout = dict( + title=title, + showlegend=False, + height=height, + width=width, + shapes=[], + hovermode='closest', + yaxis=dict( + showgrid=showgrid_y, + ticktext=task_names, + tickvals=range(len(tasks)), + range=[-1, len(tasks) + 1], + autorange=False, + zeroline=False, + ), + xaxis=dict( + showgrid=showgrid_x, + zeroline=False, + rangeselector=dict( + buttons=list([ + dict(count=7, + label='1w', + step='day', + stepmode='backward'), + dict(count=1, + label='1m', + step='month', + stepmode='backward'), + dict(count=6, + label='6m', + step='month', + stepmode='backward'), + dict(count=1, + label='YTD', + step='year', + stepmode='todate'), + dict(count=1, + label='1y', + step='year', + stepmode='backward'), + dict(step='all') + ]) + ), + type='date' + ) + ) + layout['shapes'] = tasks + + fig = dict(data=data, layout=layout) + return fig + + @staticmethod + def _gantt_colorscale(chart, colors, use_colorscale, title, + bar_width, showgrid_x, showgrid_y, + height, width, tasks=None, + task_names=None, data=None): + """ + Refer to FigureFactory.create_gantt() for docstring + """ + if tasks is None: + tasks = [] + if task_names is None: + task_names = [] + if data is None: + data = [] + + for index in range(len(chart)): + task = dict(x0=chart[index]['Start'], + x1=chart[index]['Finish'], + name=chart[index]['Task']) + tasks.append(task) + + shape_template = { + 'type': 'rect', + 'xref': 'x', + 'yref': 'y', + 'opacity': 1, + 'line': { + 'width': 0, + }, + 'yref': 'y', + } + + for index in range(len(tasks)): + tn = tasks[index]['name'] + task_names.append(tn) + del tasks[index]['name'] + tasks[index].update(shape_template) + tasks[index]['y0'] = index - bar_width + tasks[index]['y1'] = index + bar_width + + # compute the color for task based on 'Completed' column + colors = FigureFactory._unlabel_rgb(colors) + lowcolor = colors[0] + highcolor = colors[1] + + intermed = (chart[index]['Complete'])/100.0 + intermed_color = FigureFactory._find_intermediate_color(lowcolor, + highcolor, + intermed) + intermed_color = FigureFactory._label_rgb(intermed_color) + tasks[index]['fillcolor'] = intermed_color + # relabel colors with 'rgb' + colors = FigureFactory._label_rgb(colors) + + # add a line for hover text and autorange + data.append( + dict( + x=[tasks[index]['x0'], tasks[index]['x1']], + y=[index, index], + name='', + marker={'color': 'white'} + ) + ) + + layout = dict( + title=title, + showlegend=False, + height=height, + width=width, + shapes=[], + hovermode='closest', + yaxis=dict( + showgrid=showgrid_y, + ticktext=task_names, + tickvals=range(len(tasks)), + range=[-1, len(tasks) + 1], + autorange=False, + zeroline=False, + ), + xaxis=dict( + showgrid=showgrid_x, + zeroline=False, + rangeselector=dict( + buttons=list([ + dict(count=7, + label='1w', + step='day', + stepmode='backward'), + dict(count=1, + label='1m', + step='month', + stepmode='backward'), + dict(count=6, + label='6m', + step='month', + stepmode='backward'), + dict(count=1, + label='YTD', + step='year', + stepmode='todate'), + dict(count=1, + label='1y', + step='year', + stepmode='backward'), + dict(step='all') + ]) + ), + type='date' + ) + ) + layout['shapes'] = tasks + + fig = dict(data=data, layout=layout) + return fig + + @staticmethod + def create_gantt(df, colors=None, use_colorscale=False, + reverse_colors=False, title='Gantt Chart', + bar_width=0.2, showgrid_x=False, showgrid_y=False, + height=600, width=900, tasks=None, + task_names=None, data=None): + """ + Returns figure for a gantt chart + + :param (array|list) df: input data for gantt chart. Must be either a + a dataframe or a list. If dataframe, the columns must include + 'Task', 'Start' and 'Finish'; 'Complete' is optional and is used + to colorscale the bars. If a list, it must contain dictionaries + with the same required column headers, with 'Complete' optional + in the same way as the dataframe + :param (list) colors: a list of 'rgb(a, b, c)' colors where a, b and c + are between 0 and 255. Can also be a Plotly colorscale but this is + will result in only a 2-color cycle. If number of colors is less + than the total number of tasks, colors will cycle + :param (bool) use_colorscale: enables colorscale with 'Complete' as + the index + :param (bool) reverse_colors: reverses the order of selected colors + :param (str) title: the title of the chart + :param (float) bar_width: the width of the horizontal bars in the plot + :param (bool) showgrid_x: show/hide the x-axis grid + :param (bool) showgrid_y: show/hide the y-axis grid + :param (float) height: the height of the chart + :param (float) width: the width of the chart + + """ + plotly_scales = {'Greys': ['rgb(0,0,0)', 'rgb(255,255,255)'], + 'YlGnBu': ['rgb(8,29,88)', 'rgb(255,255,217)'], + 'Greens': ['rgb(0,68,27)', 'rgb(247,252,245)'], + 'YlOrRd': ['rgb(128,0,38)', 'rgb(255,255,204)'], + 'Bluered': ['rgb(0,0,255)', 'rgb(255,0,0)'], + 'RdBu': ['rgb(5,10,172)', 'rgb(178,10,28)'], + 'Reds': ['rgb(220,220,220)', 'rgb(178,10,28)'], + 'Blues': ['rgb(5,10,172)', 'rgb(220,220,220)'], + 'Picnic': ['rgb(0,0,255)', 'rgb(255,0,0)'], + 'Rainbow': ['rgb(150,0,90)', 'rgb(255,0,0)'], + 'Portland': ['rgb(12,51,131)', 'rgb(217,30,30)'], + 'Jet': ['rgb(0,0,131)', 'rgb(128,0,0)'], + 'Hot': ['rgb(0,0,0)', 'rgb(255,255,255)'], + 'Blackbody': ['rgb(0,0,0)', 'rgb(160,200,255)'], + 'Earth': ['rgb(0,0,130)', 'rgb(255,255,255)'], + 'Electric': ['rgb(0,0,0)', 'rgb(255,250,220)'], + 'Viridis': ['rgb(68,1,84)', 'rgb(253,231,37)']} + + # validate gantt input data + chart = FigureFactory._validate_gantt(df) + + if colors is None: + colors = DEFAULT_PLOTLY_COLORS + + # validate color choice + if isinstance(colors, str): + if colors not in plotly_scales: + scale_keys = list(plotly_scales.keys()) + raise exceptions.PlotlyError("You must pick a valid " + "plotly colorscale " + "name from " + "{}".format(scale_keys)) + + colors = [plotly_scales[colors][0], + plotly_scales[colors][1]] + + else: + if not isinstance(colors, list): + raise exceptions.PlotlyError("If 'colors' is a list then " + "its items must be tripets " + "of the form a,b,c or " + "'rgbx,y,z' where a,b,c are " + "between 0 and 1 inclusive " + "and x,y,z are between 0 " + "and 255 inclusive.") + if 'rgb' in colors[0]: + colors = FigureFactory._unlabel_rgb(colors) + for color in colors: + for index in range(3): + if color[index] > 255.0: + raise exceptions.PlotlyError("Whoops! The " + "elements in your " + "rgb colors " + "tuples cannot " + "exceed 255.0.") + colors = FigureFactory._label_rgb(colors) + + if isinstance(colors[0], tuple): + for color in colors: + for index in range(3): + if color[index] > 1.0: + raise exceptions.PlotlyError("Whoops! The " + "elements in your " + "rgb colors " + "tuples cannot " + "exceed 1.0.") + colors = FigureFactory._convert_to_RGB_255(colors) + colors = FigureFactory._label_rgb(colors) + + # reverse colors if 'reverse_colors' is True + if reverse_colors is True: + colors.reverse() + + if use_colorscale is False: + fig = FigureFactory._gantt(chart, colors, use_colorscale, title, + bar_width, showgrid_x, showgrid_y, + height, width, tasks=None, + task_names=None, data=None) + return fig + + else: + if 'Complete' not in chart[0]: + raise exceptions.PlotlyError("In order to use colorscale " + "there must be a 'complete' " + "column in the chart.") + + fig = FigureFactory._gantt_colorscale(chart, colors, + use_colorscale, title, + bar_width, showgrid_x, + showgrid_y, height, + width, tasks=None, + task_names=None, data=None) + return fig + @staticmethod def _find_intermediate_color(lowcolor, highcolor, intermed): """ @@ -2881,12 +3291,15 @@ def _label_rgb(colors): same list with each tuple replaced by a string 'rgb(a, b, c)' """ - colors_label = [] - for color in colors: - color_label = 'rgb{}'.format(color) - colors_label.append(color_label) + if isinstance(colors, tuple): + return 'rgb{}'.format(colors) + else: + colors_label = [] + for color in colors: + color_label = 'rgb{}'.format(color) + colors_label.append(color_label) - return colors_label + return colors_label @staticmethod def _unlabel_rgb(colors): From fe67a2bc87aa3e6d208bdfcc4723678511af84d6 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Tue, 17 May 2016 12:35:12 -0400 Subject: [PATCH 02/27] Added Doc Strings and Tests --- .../test_tools/test_figure_factory.py | 200 ++++++++++++++++++ .../test_optional/test_figure_factory.py | 18 ++ plotly/tools.py | 98 ++++++--- 3 files changed, 287 insertions(+), 29 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index c79bde4eb2a..5322a94f684 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -1126,6 +1126,206 @@ def test_table_with_index(self): self.assertEqual(index_table, exp_index_table) +class TestGantt(TestCase): + + def test_df_list(self): + + # validate df when it is a list + + df1 = 42 + self.assertRaisesRegexp(PlotlyError, + "You must input either a dataframe or a list " + "of dictionaries.", + tls.FigureFactory.create_gantt, df1) + + df2 = [] + self.assertRaisesRegexp(PlotlyError, + "Your list is empty. It must contain " + "at least one dictionary.", + tls.FigureFactory.create_gantt, df2) + + df3 = [42] + self.assertRaisesRegexp(PlotlyError, + "Your list must only include dictionaries.", + tls.FigureFactory.create_gantt, df3) + + df4 = [{'apple': 2}] + self.assertRaises(PlotlyError, tls.FigureFactory.create_gantt, df4) + + df5 = [{'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30'}, + {'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30', + 'Complete': 25}] + self.assertRaisesRegexp(PlotlyError, + "If you are using 'Complete' as a dictionary " + "key, make sure each dictionary has this key " + "with an assigned value between 0 and 100.", + tls.FigureFactory.create_gantt, df5) + + df6 = [{'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30', + 'Complete': 55}, + {'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30', + 'Complete': 'string'}] + self.assertRaisesRegexp(PlotlyError, + "The values in the 'Complete' column must " + "be between 0 and 100.", + tls.FigureFactory.create_gantt, df6) + + def test_valid_colors(self): + + # check: if color choices are valid + + df = [{'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30', + 'Complete': 55}, + {'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30', + 'Complete': 65}] + + self.assertRaises(PlotlyError, tls.FigureFactory.create_gantt, + df, colors='Weird') + + pattern1 = ( + "If 'colors' is a list then its items must be tripets of the " + "form a,b,c or 'rgbx,y,z' where a,b,c are between 0 and 1 " + "inclusive and x,y,z are between 0 and 255 inclusive." + ) + + self.assertRaisesRegexp(PlotlyError, pattern1, + tls.FigureFactory.create_gantt, df, colors=25) + + pattern2 = ( + "Whoops! The elements in your rgb colors tuples " + "cannot exceed 255.0." + ) + + self.assertRaisesRegexp(PlotlyError, pattern2, + tls.FigureFactory.create_gantt, df, + colors=['rgb(1, 2, 3)', 'rgb(300, 2, 3)']) + + pattern3 = ( + "Whoops! The elements in your rgb colors tuples " + "cannot exceed 1.0." + ) + + self.assertRaisesRegexp(PlotlyError, pattern3, + tls.FigureFactory.create_gantt, df, + colors=[(0.1, 0.2, 0.3), (0.6, 0.8, 1.2)]) + + def test_use_colorscale(self): + + # checks: 'Complete' in inputted array or list + + df = [{'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30'}, + {'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30'}] + + pattern = ( + "In order to use colorscale there must be a " + "'Complete' column in the chart." + ) + self.assertRaisesRegexp(PlotlyError, pattern, + tls.FigureFactory.create_gantt, df, + use_colorscale=True) + + def test_gantt_all_args(self): + + # check if gantt chart matches with expected output + + df = [dict(Task="Run", + Start='2010-01-01', + Finish='2011-02-02', + Complete=0), + dict(Task="Fast", + Start='2011-01-01', + Finish='2012-06-05', + Complete=25)] + + test_gantt_chart = tls.FigureFactory.create_gantt( + df, colors='Blues', use_colorscale=True, reverse_colors=True, + title='Title', bar_width=0.5, showgrid_x=True, showgrid_y=True, + height=500, width=500 + ) + + exp_gantt_chart = { + 'data': [{'marker': {'color': 'white'}, + 'name': '', + 'x': ['2010-01-01', '2011-02-02'], + 'y': [0, 0]}, + {'marker': {'color': 'white'}, + 'name': '', + 'x': ['2011-01-01', '2012-06-05'], + 'y': [1, 1]}], + 'layout': {'height': 500, + 'hovermode': 'closest', + 'shapes': [{'fillcolor': 'rgb(220.0, 220.0, 220.0)', + 'line': {'width': 0}, + 'opacity': 1, + 'type': 'rect', + 'x0': '2010-01-01', + 'x1': '2011-02-02', + 'xref': 'x', + 'y0': -0.5, + 'y1': 0.5, + 'yref': 'y'}, + {'fillcolor': 'rgb(166.25, 167.5, 208.0)', + 'line': {'width': 0}, + 'opacity': 1, + 'type': 'rect', + 'x0': '2011-01-01', + 'x1': '2012-06-05', + 'xref': 'x', + 'y0': 0.5, + 'y1': 1.5, + 'yref': 'y'}], + 'showlegend': False, + 'title': 'Title', + 'width': 500, + 'xaxis': { + 'rangeselector': { + 'buttons': [{'count': 7, 'label': '1w', + 'step': 'day', + 'stepmode': 'backward'}, + {'count': 1, 'label': '1m', + 'step': 'month', + 'stepmode': 'backward'}, + {'count': 6, 'label': '6m', + 'step': 'month', + 'stepmode': 'backward'}, + {'count': 1, 'label': 'YTD', + 'step': 'year', + 'stepmode': 'todate'}, + {'count': 1, 'label': '1y', + 'step': 'year', + 'stepmode': 'backward'}, + {'step': 'all'}] + }, + 'showgrid': True, + 'type': 'date', + 'zeroline': False}, + 'yaxis': {'autorange': False, + 'range': [-1, 3], + 'showgrid': True, + 'ticktext': ['Run', 'Fast'], + 'tickvals': [0, 1], + 'zeroline': False}} + } + + self.assertEqual(test_gantt_chart, exp_gantt_chart) + + # class TestDistplot(TestCase): # def test_scipy_import_error(self): diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index f39c5c8f04e..833c4b1bafc 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -1012,3 +1012,21 @@ def test_scatter_plot_matrix_kwargs(self): self.assert_dict_equal(test_scatter_plot_matrix['layout'], exp_scatter_plot_matrix['layout']) + + +class TestGantt(TestCase): + + def test_df_dataframe(self): + + # validate df when it is a dataframe + + df1 = pd.DataFrame([[2, 'Apple']], columns=['Numbers', 'Fruit']) + self.assertRaises(PlotlyError, tls.FigureFactory.create_gantt, df1) + + df2 = pd.DataFrame([['Job A', '2009-01-01', '2009-02-30', 25], + ['Job B', '2009-01-01', '2009-02-30', '25']], + columns=['Task', 'Start', 'Finish', 'Complete']) + self.assertRaisesRegexp(PlotlyError, + "The values in the 'Complete' column must " + "be between 0 and 100.", + tls.FigureFactory.create_gantt, df2) diff --git a/plotly/tools.py b/plotly/tools.py index dbe5eaabd77..1b6bd7dd5e3 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1455,27 +1455,21 @@ def _validate_gantt(df): """ Validates the inputted dataframe or list """ - # check if pandas dataframe + from numbers import Number + REQ_GANTT_KEYS = ['Task', 'Start', 'Finish'] + if isinstance(df, pd.core.frame.DataFrame): - # extract data into list of dictionaries - if ('Task' not in df): - raise exceptions.PlotlyError("Your dataframe must have the " - "columns 'Task', 'Start' and " - "'Finish', with 'Complete' as " - "an optional column.") - if ('Start' not in df): - raise exceptions.PlotlyError("Your dataframe must have the " - "columns 'Task', 'Start' and " - "'Finish', with 'Complete' as " - "an optional column.") - if ('Finish' not in df): - raise exceptions.PlotlyError("Your dataframe must have the " - "columns 'Task', 'Start' and " - "'Finish', with 'Complete' as " - "an optional column.") + # validate if df is a dataframe + for key in REQ_GANTT_KEYS: + if key not in df: + raise exceptions.PlotlyError("The columns in your data" + "frame must be one " + "of".format(REQ_GANTT_KEYS)) + # check if 'Complete' column has 'nan' values + # or fall outside the [0, 100] interval if 'Complete' in df: for percentage in df['Complete']: - if (percentage < 0.0) or (percentage > 100.0): + if not isinstance(percentage, Number): raise exceptions.PlotlyError("The values in the " "'Complete' column must " "be between 0 and 100.") @@ -1495,23 +1489,32 @@ def _validate_gantt(df): if not isinstance(df, list): raise exceptions.PlotlyError("You must input either a dataframe " "or a list of dictionaries.") - + # validate if df is a list if len(df) <= 0: raise exceptions.PlotlyError("Your list is empty. It must contain " "at least one dictionary.") - if not isinstance(df[0], dict): raise exceptions.PlotlyError("Your list must only " "include dictionaries.") - - if 'Complete' in df[0]: + for key in REQ_GANTT_KEYS: for dictionary in df: - if 'Complete' not in dictionary: - raise exceptions.PlotlyError("If you are using 'Complete' " - "as a dictionary key, make " - "sure each dictionary has " - "this key with an assigned " - "value between 0 and 100.") + if key not in dictionary: + raise exceptions.PlotlyError("The columns in your data" + "frame must be one " + "of".format(REQ_GANTT_KEYS)) + if any('Complete' in dictionary for dictionary in df): + if not all('Complete' in dictionary for dictionary in df): + raise exceptions.PlotlyError("If you are using 'Complete' " + "as a dictionary key, make " + "sure each dictionary has " + "this key with an assigned " + "value between 0 and 100.") + if 'Complete' in df[0]: + for dictioanry in df: + if not isinstance(dictionary['Complete'], Number): + raise exceptions.PlotlyError("The values in the " + "'Complete' column must " + "be between 0 and 100.") return df @staticmethod @@ -1765,6 +1768,43 @@ def create_gantt(df, colors=None, use_colorscale=False, :param (float) height: the height of the chart :param (float) width: the width of the chart + Example 1: Simple Gantt Chart + ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + + # Make data for chart + df = [dict(Task="Job A", Start='2009-01-01', Finish='2009-02-30'), + dict(Task="Job B", Start='2009-03-05', Finish='2009-04-15'), + dict(Task="Job C", Start='2009-02-20', Finish='2009-05-30')] + + # Create a figure + fig = FF.create_gantt(df) + + # Plot the data + py.iplot(fig, filename='Gantt Chart', world_readable=True) + ``` + + Example 2: Colormap by 'Complete' Variable + ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + + # Make data for chart + df = [dict(Task="Job A", Start='2009-01-01', + Finish='2009-02-30', Complete=10), + dict(Task="Job B", Start='2009-03-05', + Finish='2009-04-15', Complete=60), + dict(Task="Job C", Start='2009-02-20', + Finish='2009-05-30', Complete=95)] + + # Create a figure with Plotly colorscale + fig = FF.create_gantt(df, use_colorscale=True, colors='Blues', + bar_width=0.5, showgrid_x=True, showgrid_y=True) + + # Plot the data + py.iplot(fig, filename='Colormap Gantt Chart', world_readable=True) + ``` """ plotly_scales = {'Greys': ['rgb(0,0,0)', 'rgb(255,255,255)'], 'YlGnBu': ['rgb(8,29,88)', 'rgb(255,255,217)'], @@ -1849,7 +1889,7 @@ def create_gantt(df, colors=None, use_colorscale=False, else: if 'Complete' not in chart[0]: raise exceptions.PlotlyError("In order to use colorscale " - "there must be a 'complete' " + "there must be a 'Complete' " "column in the chart.") fig = FigureFactory._gantt_colorscale(chart, colors, From e22830781d15ca91aea17a8cee65dd81f91c911f Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Tue, 17 May 2016 13:47:56 -0400 Subject: [PATCH 03/27] made range(len(tasks)) into a list for Python 3 --- .../test_tools/test_figure_factory.py | 200 ----------------- .../test_optional/test_figure_factory.py | 206 +++++++++++++++++- plotly/tools.py | 4 +- 3 files changed, 207 insertions(+), 203 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index 5322a94f684..c79bde4eb2a 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -1126,206 +1126,6 @@ def test_table_with_index(self): self.assertEqual(index_table, exp_index_table) -class TestGantt(TestCase): - - def test_df_list(self): - - # validate df when it is a list - - df1 = 42 - self.assertRaisesRegexp(PlotlyError, - "You must input either a dataframe or a list " - "of dictionaries.", - tls.FigureFactory.create_gantt, df1) - - df2 = [] - self.assertRaisesRegexp(PlotlyError, - "Your list is empty. It must contain " - "at least one dictionary.", - tls.FigureFactory.create_gantt, df2) - - df3 = [42] - self.assertRaisesRegexp(PlotlyError, - "Your list must only include dictionaries.", - tls.FigureFactory.create_gantt, df3) - - df4 = [{'apple': 2}] - self.assertRaises(PlotlyError, tls.FigureFactory.create_gantt, df4) - - df5 = [{'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30'}, - {'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30', - 'Complete': 25}] - self.assertRaisesRegexp(PlotlyError, - "If you are using 'Complete' as a dictionary " - "key, make sure each dictionary has this key " - "with an assigned value between 0 and 100.", - tls.FigureFactory.create_gantt, df5) - - df6 = [{'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30', - 'Complete': 55}, - {'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30', - 'Complete': 'string'}] - self.assertRaisesRegexp(PlotlyError, - "The values in the 'Complete' column must " - "be between 0 and 100.", - tls.FigureFactory.create_gantt, df6) - - def test_valid_colors(self): - - # check: if color choices are valid - - df = [{'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30', - 'Complete': 55}, - {'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30', - 'Complete': 65}] - - self.assertRaises(PlotlyError, tls.FigureFactory.create_gantt, - df, colors='Weird') - - pattern1 = ( - "If 'colors' is a list then its items must be tripets of the " - "form a,b,c or 'rgbx,y,z' where a,b,c are between 0 and 1 " - "inclusive and x,y,z are between 0 and 255 inclusive." - ) - - self.assertRaisesRegexp(PlotlyError, pattern1, - tls.FigureFactory.create_gantt, df, colors=25) - - pattern2 = ( - "Whoops! The elements in your rgb colors tuples " - "cannot exceed 255.0." - ) - - self.assertRaisesRegexp(PlotlyError, pattern2, - tls.FigureFactory.create_gantt, df, - colors=['rgb(1, 2, 3)', 'rgb(300, 2, 3)']) - - pattern3 = ( - "Whoops! The elements in your rgb colors tuples " - "cannot exceed 1.0." - ) - - self.assertRaisesRegexp(PlotlyError, pattern3, - tls.FigureFactory.create_gantt, df, - colors=[(0.1, 0.2, 0.3), (0.6, 0.8, 1.2)]) - - def test_use_colorscale(self): - - # checks: 'Complete' in inputted array or list - - df = [{'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30'}, - {'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30'}] - - pattern = ( - "In order to use colorscale there must be a " - "'Complete' column in the chart." - ) - self.assertRaisesRegexp(PlotlyError, pattern, - tls.FigureFactory.create_gantt, df, - use_colorscale=True) - - def test_gantt_all_args(self): - - # check if gantt chart matches with expected output - - df = [dict(Task="Run", - Start='2010-01-01', - Finish='2011-02-02', - Complete=0), - dict(Task="Fast", - Start='2011-01-01', - Finish='2012-06-05', - Complete=25)] - - test_gantt_chart = tls.FigureFactory.create_gantt( - df, colors='Blues', use_colorscale=True, reverse_colors=True, - title='Title', bar_width=0.5, showgrid_x=True, showgrid_y=True, - height=500, width=500 - ) - - exp_gantt_chart = { - 'data': [{'marker': {'color': 'white'}, - 'name': '', - 'x': ['2010-01-01', '2011-02-02'], - 'y': [0, 0]}, - {'marker': {'color': 'white'}, - 'name': '', - 'x': ['2011-01-01', '2012-06-05'], - 'y': [1, 1]}], - 'layout': {'height': 500, - 'hovermode': 'closest', - 'shapes': [{'fillcolor': 'rgb(220.0, 220.0, 220.0)', - 'line': {'width': 0}, - 'opacity': 1, - 'type': 'rect', - 'x0': '2010-01-01', - 'x1': '2011-02-02', - 'xref': 'x', - 'y0': -0.5, - 'y1': 0.5, - 'yref': 'y'}, - {'fillcolor': 'rgb(166.25, 167.5, 208.0)', - 'line': {'width': 0}, - 'opacity': 1, - 'type': 'rect', - 'x0': '2011-01-01', - 'x1': '2012-06-05', - 'xref': 'x', - 'y0': 0.5, - 'y1': 1.5, - 'yref': 'y'}], - 'showlegend': False, - 'title': 'Title', - 'width': 500, - 'xaxis': { - 'rangeselector': { - 'buttons': [{'count': 7, 'label': '1w', - 'step': 'day', - 'stepmode': 'backward'}, - {'count': 1, 'label': '1m', - 'step': 'month', - 'stepmode': 'backward'}, - {'count': 6, 'label': '6m', - 'step': 'month', - 'stepmode': 'backward'}, - {'count': 1, 'label': 'YTD', - 'step': 'year', - 'stepmode': 'todate'}, - {'count': 1, 'label': '1y', - 'step': 'year', - 'stepmode': 'backward'}, - {'step': 'all'}] - }, - 'showgrid': True, - 'type': 'date', - 'zeroline': False}, - 'yaxis': {'autorange': False, - 'range': [-1, 3], - 'showgrid': True, - 'ticktext': ['Run', 'Fast'], - 'tickvals': [0, 1], - 'zeroline': False}} - } - - self.assertEqual(test_gantt_chart, exp_gantt_chart) - - # class TestDistplot(TestCase): # def test_scipy_import_error(self): diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index 833c4b1bafc..83c49eb5450 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -1014,7 +1014,7 @@ def test_scatter_plot_matrix_kwargs(self): exp_scatter_plot_matrix['layout']) -class TestGantt(TestCase): +class TestGantt(NumpyTestUtilsMixin, TestCase): def test_df_dataframe(self): @@ -1030,3 +1030,207 @@ def test_df_dataframe(self): "The values in the 'Complete' column must " "be between 0 and 100.", tls.FigureFactory.create_gantt, df2) + + def test_df_list(self): + + # validate df when it is a list + + df1 = 42 + self.assertRaisesRegexp(PlotlyError, + "You must input either a dataframe or a list " + "of dictionaries.", + tls.FigureFactory.create_gantt, df1) + + df2 = [] + self.assertRaisesRegexp(PlotlyError, + "Your list is empty. It must contain " + "at least one dictionary.", + tls.FigureFactory.create_gantt, df2) + + df3 = [42] + self.assertRaisesRegexp(PlotlyError, + "Your list must only include dictionaries.", + tls.FigureFactory.create_gantt, df3) + + df4 = [{'apple': 2}] + self.assertRaises(PlotlyError, tls.FigureFactory.create_gantt, df4) + + df5 = [{'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30'}, + {'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30', + 'Complete': 25}] + self.assertRaisesRegexp(PlotlyError, + "If you are using 'Complete' as a dictionary " + "key, make sure each dictionary has this key " + "with an assigned value between 0 and 100.", + tls.FigureFactory.create_gantt, df5) + + df6 = [{'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30', + 'Complete': 55}, + {'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30', + 'Complete': 'string'}] + self.assertRaisesRegexp(PlotlyError, + "The values in the 'Complete' column must " + "be between 0 and 100.", + tls.FigureFactory.create_gantt, df6) + + def test_valid_colors(self): + + # check: if color choices are valid + + df = [{'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30', + 'Complete': 55}, + {'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30', + 'Complete': 65}] + + self.assertRaises(PlotlyError, tls.FigureFactory.create_gantt, + df, colors='Weird') + + pattern1 = ( + "If 'colors' is a list then its items must be tripets of the " + "form a,b,c or 'rgbx,y,z' where a,b,c are between 0 and 1 " + "inclusive and x,y,z are between 0 and 255 inclusive." + ) + + self.assertRaisesRegexp(PlotlyError, pattern1, + tls.FigureFactory.create_gantt, df, colors=25) + + pattern2 = ( + "Whoops! The elements in your rgb colors tuples " + "cannot exceed 255.0." + ) + + self.assertRaisesRegexp(PlotlyError, pattern2, + tls.FigureFactory.create_gantt, df, + colors=['rgb(1, 2, 3)', 'rgb(300, 2, 3)']) + + pattern3 = ( + "Whoops! The elements in your rgb colors tuples " + "cannot exceed 1.0." + ) + + self.assertRaisesRegexp(PlotlyError, pattern3, + tls.FigureFactory.create_gantt, df, + colors=[(0.1, 0.2, 0.3), (0.6, 0.8, 1.2)]) + + def test_use_colorscale(self): + + # checks: 'Complete' in inputted array or list + + df = [{'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30'}, + {'Task': 'A Job', + 'Start': '2009-01-01', + 'Finish': '2009-02-30'}] + + pattern = ( + "In order to use colorscale there must be a " + "'Complete' column in the chart." + ) + self.assertRaisesRegexp(PlotlyError, pattern, + tls.FigureFactory.create_gantt, df, + use_colorscale=True) + + def test_gantt_all_args(self): + + # check if gantt chart matches with expected output + + df = [dict(Task="Run", + Start='2010-01-01', + Finish='2011-02-02', + Complete=0), + dict(Task="Fast", + Start='2011-01-01', + Finish='2012-06-05', + Complete=25)] + + test_gantt_chart = tls.FigureFactory.create_gantt( + df, colors='Blues', use_colorscale=True, reverse_colors=True, + title='Title', bar_width=0.5, showgrid_x=True, showgrid_y=True, + height=500, width=500 + ) + + exp_gantt_chart = { + 'data': [{'marker': {'color': 'white'}, + 'name': '', + 'x': ['2010-01-01', '2011-02-02'], + 'y': [0, 0]}, + {'marker': {'color': 'white'}, + 'name': '', + 'x': ['2011-01-01', '2012-06-05'], + 'y': [1, 1]}], + 'layout': {'height': 500, + 'hovermode': 'closest', + 'shapes': [{'fillcolor': 'rgb(220.0, 220.0, 220.0)', + 'line': {'width': 0}, + 'opacity': 1, + 'type': 'rect', + 'x0': '2010-01-01', + 'x1': '2011-02-02', + 'xref': 'x', + 'y0': -0.5, + 'y1': 0.5, + 'yref': 'y'}, + {'fillcolor': 'rgb(166.25, 167.5, 208.0)', + 'line': {'width': 0}, + 'opacity': 1, + 'type': 'rect', + 'x0': '2011-01-01', + 'x1': '2012-06-05', + 'xref': 'x', + 'y0': 0.5, + 'y1': 1.5, + 'yref': 'y'}], + 'showlegend': False, + 'title': 'Title', + 'width': 500, + 'xaxis': { + 'rangeselector': { + 'buttons': [{'count': 7, 'label': '1w', + 'step': 'day', + 'stepmode': 'backward'}, + {'count': 1, 'label': '1m', + 'step': 'month', + 'stepmode': 'backward'}, + {'count': 6, 'label': '6m', + 'step': 'month', + 'stepmode': 'backward'}, + {'count': 1, 'label': 'YTD', + 'step': 'year', + 'stepmode': 'todate'}, + {'count': 1, 'label': '1y', + 'step': 'year', + 'stepmode': 'backward'}, + {'step': 'all'}] + }, + 'showgrid': True, + 'type': 'date', + 'zeroline': False}, + 'yaxis': {'autorange': False, + 'range': [-1, 3], + 'showgrid': True, + 'ticktext': ['Run', 'Fast'], + 'tickvals': [0, 1], + 'zeroline': False}} + } + + self.assert_dict_equal(test_gantt_chart['data'][0], + exp_gantt_chart['data'][0]) + + self.assert_dict_equal(test_gantt_chart['data'][1], + exp_gantt_chart['data'][1]) + + self.assert_dict_equal(test_gantt_chart['layout'], + exp_gantt_chart['layout']) diff --git a/plotly/tools.py b/plotly/tools.py index 1b6bd7dd5e3..422d419dae3 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1583,7 +1583,7 @@ def _gantt(chart, colors, use_colorscale, title, yaxis=dict( showgrid=showgrid_y, ticktext=task_names, - tickvals=range(len(tasks)), + tickvals=list(range(len(tasks))), range=[-1, len(tasks) + 1], autorange=False, zeroline=False, @@ -1698,7 +1698,7 @@ def _gantt_colorscale(chart, colors, use_colorscale, title, yaxis=dict( showgrid=showgrid_y, ticktext=task_names, - tickvals=range(len(tasks)), + tickvals=list(range(len(tasks))), range=[-1, len(tasks) + 1], autorange=False, zeroline=False, From e7a1596f4c90fbec7eba73b5609ebf2fad2b0736 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Tue, 17 May 2016 15:26:02 -0400 Subject: [PATCH 04/27] Added colorbar visibility on the side of chart --- plotly/tools.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/plotly/tools.py b/plotly/tools.py index 422d419dae3..c7952bfea41 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1688,6 +1688,20 @@ def _gantt_colorscale(chart, colors, use_colorscale, title, ) ) + # generate dummy data for colorscale visibility + data.append( + dict( + x=[tasks[index]['x0'], tasks[index]['x0']], + y=[index, index], + name='', + marker={'color': 'white', + 'colorscale': [[0, colors[0]], [1, colors[1]]], + 'showscale': True, + 'cmax': 100, + 'cmin': 0} + ) + ) + layout = dict( title=title, showlegend=False, From 3035dbaabae730386d89650c615f46d923a53d23 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Tue, 31 May 2016 10:49:27 -0400 Subject: [PATCH 05/27] Made Gantt Changes --- plotly/tools.py | 164 ++++++++++++++++++++++++++---------------------- 1 file changed, 90 insertions(+), 74 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index c7952bfea41..8f33bbca890 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1451,7 +1451,7 @@ class FigureFactory(object): """ @staticmethod - def _validate_gantt(df): + def _validate_gantt(df, index_col): """ Validates the inputted dataframe or list """ @@ -1463,8 +1463,8 @@ def _validate_gantt(df): for key in REQ_GANTT_KEYS: if key not in df: raise exceptions.PlotlyError("The columns in your data" - "frame must be one " - "of".format(REQ_GANTT_KEYS)) + "frame must include the " + "keys".format(REQ_GANTT_KEYS)) # check if 'Complete' column has 'nan' values # or fall outside the [0, 100] interval if 'Complete' in df: @@ -1478,11 +1478,14 @@ def _validate_gantt(df): chart = [] for index in range(num_of_rows): task_dict = {} - task_dict['Task'] = df.ix[index]['Task'] - task_dict['Start'] = df.ix[index]['Start'] - task_dict['Finish'] = df.ix[index]['Finish'] - if 'Complete' in df: - task_dict['Complete'] = df.ix[index]['Complete'] + for key in df: + task_dict[key] = df.ix[index][key] + + #task_dict['Task'] = df.ix[index]['Task'] + #task_dict['Start'] = df.ix[index]['Start'] + #task_dict['Finish'] = df.ix[index]['Finish'] + #if 'Complete' in df: + # task_dict['Complete'] = df.ix[index]['Complete'] chart.append(task_dict) return chart @@ -1502,24 +1505,18 @@ def _validate_gantt(df): raise exceptions.PlotlyError("The columns in your data" "frame must be one " "of".format(REQ_GANTT_KEYS)) - if any('Complete' in dictionary for dictionary in df): - if not all('Complete' in dictionary for dictionary in df): - raise exceptions.PlotlyError("If you are using 'Complete' " - "as a dictionary key, make " - "sure each dictionary has " - "this key with an assigned " - "value between 0 and 100.") - if 'Complete' in df[0]: - for dictioanry in df: - if not isinstance(dictionary['Complete'], Number): - raise exceptions.PlotlyError("The values in the " - "'Complete' column must " - "be between 0 and 100.") + #if index_col in df[0]: + # for dictioanry in df: + # if not isinstance(dictionary[index_col], Number): + # raise exceptions.PlotlyError("The values in the " + # "'Complete' column must " + # "be between 0 and 100.") + + # validate index column is all strings or all numbers return df @staticmethod - def _gantt(chart, colors, use_colorscale, title, - bar_width, showgrid_x, showgrid_y, + def _gantt(chart, colors, title, bar_width, showgrid_x, showgrid_y, height, width, tasks=None, task_names=None, data=None): """ @@ -1625,13 +1622,13 @@ def _gantt(chart, colors, use_colorscale, title, return fig @staticmethod - def _gantt_colorscale(chart, colors, use_colorscale, title, - bar_width, showgrid_x, showgrid_y, - height, width, tasks=None, - task_names=None, data=None): + def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, + bar_width, showgrid_x, showgrid_y, height, + width, tasks=None, task_names=None, data=None): """ Refer to FigureFactory.create_gantt() for docstring """ + from numbers import Number if tasks is None: tasks = [] if task_names is None: @@ -1664,44 +1661,61 @@ def _gantt_colorscale(chart, colors, use_colorscale, title, tasks[index]['y0'] = index - bar_width tasks[index]['y1'] = index + bar_width - # compute the color for task based on 'Completed' column - colors = FigureFactory._unlabel_rgb(colors) - lowcolor = colors[0] - highcolor = colors[1] - - intermed = (chart[index]['Complete'])/100.0 - intermed_color = FigureFactory._find_intermediate_color(lowcolor, - highcolor, - intermed) - intermed_color = FigureFactory._label_rgb(intermed_color) - tasks[index]['fillcolor'] = intermed_color - # relabel colors with 'rgb' - colors = FigureFactory._label_rgb(colors) - - # add a line for hover text and autorange + # compute the color for task based on indexing column + if isinstance(chart[index][index_col], Number): + colors = FigureFactory._unlabel_rgb(colors) + lowcolor = colors[0] + highcolor = colors[1] + + intermed = (chart[index][index_col])/100.0 + intermed_color = FigureFactory._find_intermediate_color(lowcolor, + highcolor, + intermed) + intermed_color = FigureFactory._label_rgb(intermed_color) + tasks[index]['fillcolor'] = intermed_color + # relabel colors with 'rgb' + colors = FigureFactory._label_rgb(colors) + + # add a line for hover text and autorange + data.append( + dict( + x=[tasks[index]['x0'], tasks[index]['x1']], + y=[index, index], + name='', + marker={'color': 'white'} + ) + ) + if isinstance(chart[index][index_col], str): + color = colors[0] + tasks[index]['fillcolor'] = color + # relabel colors with 'rgb' + colors = FigureFactory._label_rgb(colors) + + # add a line for hover text and autorange + data.append( + dict( + x=[tasks[index]['x0'], tasks[index]['x1']], + y=[index, index], + name='', + marker={'color': 'white'} + ) + ) + + if show_colorbar is True: + # generate dummy data for colorscale visibility data.append( dict( - x=[tasks[index]['x0'], tasks[index]['x1']], + x=[tasks[index]['x0'], tasks[index]['x0']], y=[index, index], name='', - marker={'color': 'white'} + marker={'color': 'white', + 'colorscale': [[0, colors[0]], [1, colors[1]]], + 'showscale': True, + 'cmax': 100, + 'cmin': 0} ) ) - # generate dummy data for colorscale visibility - data.append( - dict( - x=[tasks[index]['x0'], tasks[index]['x0']], - y=[index, index], - name='', - marker={'color': 'white', - 'colorscale': [[0, colors[0]], [1, colors[1]]], - 'showscale': True, - 'cmax': 100, - 'cmin': 0} - ) - ) - layout = dict( title=title, showlegend=False, @@ -1754,7 +1768,7 @@ def _gantt_colorscale(chart, colors, use_colorscale, title, return fig @staticmethod - def create_gantt(df, colors=None, use_colorscale=False, + def create_gantt(df, colors=None, index_col=None, show_colorbar=False, reverse_colors=False, title='Gantt Chart', bar_width=0.2, showgrid_x=False, showgrid_y=False, height=600, width=900, tasks=None, @@ -1772,8 +1786,6 @@ def create_gantt(df, colors=None, use_colorscale=False, are between 0 and 255. Can also be a Plotly colorscale but this is will result in only a 2-color cycle. If number of colors is less than the total number of tasks, colors will cycle - :param (bool) use_colorscale: enables colorscale with 'Complete' as - the index :param (bool) reverse_colors: reverses the order of selected colors :param (str) title: the title of the chart :param (float) bar_width: the width of the horizontal bars in the plot @@ -1813,7 +1825,7 @@ def create_gantt(df, colors=None, use_colorscale=False, Finish='2009-05-30', Complete=95)] # Create a figure with Plotly colorscale - fig = FF.create_gantt(df, use_colorscale=True, colors='Blues', + fig = FF.create_gantt(df, colors='Blues', bar_width=0.5, showgrid_x=True, showgrid_y=True) # Plot the data @@ -1839,7 +1851,7 @@ def create_gantt(df, colors=None, use_colorscale=False, 'Viridis': ['rgb(68,1,84)', 'rgb(253,231,37)']} # validate gantt input data - chart = FigureFactory._validate_gantt(df) + chart = FigureFactory._validate_gantt(df, index_col) if colors is None: colors = DEFAULT_PLOTLY_COLORS @@ -1859,12 +1871,13 @@ def create_gantt(df, colors=None, use_colorscale=False, else: if not isinstance(colors, list): raise exceptions.PlotlyError("If 'colors' is a list then " - "its items must be tripets " - "of the form a,b,c or " + "its items must be either " + "tuples of the forms " "'rgbx,y,z' where a,b,c are " "between 0 and 1 inclusive " "and x,y,z are between 0 " - "and 255 inclusive.") + "and 255 inclusive, or a hex " + "color string.") if 'rgb' in colors[0]: colors = FigureFactory._unlabel_rgb(colors) for color in colors: @@ -1877,6 +1890,8 @@ def create_gantt(df, colors=None, use_colorscale=False, "exceed 255.0.") colors = FigureFactory._label_rgb(colors) + #if isinstance(colors[0], hex) + if isinstance(colors[0], tuple): for color in colors: for index in range(3): @@ -1889,25 +1904,26 @@ def create_gantt(df, colors=None, use_colorscale=False, colors = FigureFactory._convert_to_RGB_255(colors) colors = FigureFactory._label_rgb(colors) - # reverse colors if 'reverse_colors' is True if reverse_colors is True: colors.reverse() - if use_colorscale is False: - fig = FigureFactory._gantt(chart, colors, use_colorscale, title, + if not index_col: + fig = FigureFactory._gantt(chart, colors, title, bar_width, showgrid_x, showgrid_y, height, width, tasks=None, task_names=None, data=None) return fig else: - if 'Complete' not in chart[0]: - raise exceptions.PlotlyError("In order to use colorscale " - "there must be a 'Complete' " - "column in the chart.") + if index_col not in chart[0]: + raise exceptions.PlotlyError("In order to use a colormap on " + "categorical or sequential data " + "the indexing column must be in " + "the chart.") fig = FigureFactory._gantt_colorscale(chart, colors, - use_colorscale, title, + title, index_col, + show_colorbar, bar_width, showgrid_x, showgrid_y, height, width, tasks=None, From 675ba7d2839c5f664fc2b9cea1d73dc332c4c859 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Tue, 31 May 2016 11:23:03 -0400 Subject: [PATCH 06/27] More stuff --- plotly/tools.py | 138 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/plotly/tools.py b/plotly/tools.py index 8f33bbca890..fc7315f2ee5 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1853,6 +1853,143 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, # validate gantt input data chart = FigureFactory._validate_gantt(df, index_col) + # Validate colors + if colors is None: + colors = DEFAULT_PLOTLY_COLORS + + if isinstance(colors, str): + if colors in plotly_scales: + colors = plotly_scales[colors] + + elif 'rgb' in colors: + colors = FigureFactory._unlabel_rgb(colors) + for value in colors: + if value > 255.0: + raise exceptions.PlotlyError("Whoops! The " + "elements in your " + "rgb colors " + "tuples cannot " + "exceed 255.0.") + colors = FigureFactory._label_rgb(colors) + + # put colors in list + colors_list = [] + colors_list.append(colors) + colors = colors_list + + elif '#' in colors: + colors = FigureFactory._hex_to_rgb(colors) + colors = FigureFactory._label_rgb(colors) + + # put colors in list + colors_list = [] + colors_list.append(colors) + colors = colors_list + + else: + scale_keys = list(plotly_scales.keys()) + raise exceptions.PlotlyError("If you input a string " + "for 'colors', it must " + "either be a Plotly " + "colorscale, an 'rgb' " + "color or a hex color." + "Valid plotly colorscale " + "names are {}".format(scale_keys)) + elif isinstance(colors, tuple): + for value in colors: + if value > 1.0: + raise exceptions.PlotlyError("Whoops! The " + "elements in " + "your colors " + "tuples cannot " + "exceed 1.0.") + + colors_list = [] + colors_list.append(colors) + colors = colors_list + + colors = FigureFactory._convert_to_RGB_255(colors) + colors = FigureFactory._label_rgb(colors) + + elif isinstance(colors, list): + new_colormap = [] + for color in colors: + if 'rgb' in color: + color = FigureFactory._unlabel_rgb(color) + + for value in color: + if value > 255.0: + raise exceptions.PlotlyError("Whoops! The " + "elements in your " + "rgb colors " + "tuples cannot " + "exceed 255.0.") + + color = FigureFactory._label_rgb(color) + new_colormap.append(color) + elif '#' in color: + color = FigureFactory._hex_to_rgb(color) + color = FigureFactory._label_rgb(color) + new_colormap.append(color) + elif isinstance(color, tuple): + for value in color: + if value > 1.0: + raise exceptions.PlotlyError("Whoops! The " + "elements in " + "your colors " + "tuples cannot " + "exceed 1.0.") + color = FigureFactory._convert_to_RGB_255(color) + color = FigureFactory._label_rgb(color) + new_colormap.append(color) + colors = new_colormap + + elif isinstance(colors, dict): + for name in colors: + if 'rgb' in colors[name]: + color = FigureFactory._unlabel_rgb(colors[name]) + for value in color: + if value > 255.0: + raise exceptions.PlotlyError("Whoops! The " + "elements in your " + "rgb colors " + "tuples cannot " + "exceed 255.0.") + + elif '#' in colors[name]: + color = FigureFactory._hex_to_rgb(colors[name]) + color = FigureFactory._label_rgb(color) + colors[name] = color + + elif isinstance(colors[name], tuple): + for value in colors[name]: + if value > 1.0: + raise exceptions.PlotlyError("Whoops! The " + "elements in " + "your colors " + "tuples cannot " + "exceed 1.0.") + color = FigureFactory._convert_to_RGB_255(colors[name]) + color = FigureFactory._label_rgb(color) + colors[name] = color + + else: + raise exceptions.PlotlyError("You must input a valid colors. " + "Valid types include a plotly scale, " + "rgb, hex or tuple color, a list of " + "any color types, or a dictionary " + "with index names each assigned " + "to a color.") + + + + + + + + + + """ if colors is None: colors = DEFAULT_PLOTLY_COLORS @@ -1903,6 +2040,7 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, "exceed 1.0.") colors = FigureFactory._convert_to_RGB_255(colors) colors = FigureFactory._label_rgb(colors) + """ if reverse_colors is True: colors.reverse() From 29dbb4467c42ca0403472ffb0d465de3e1e1ec75 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Tue, 31 May 2016 11:28:44 -0400 Subject: [PATCH 07/27] updated unlabel_rgb --- plotly/tools.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index fc7315f2ee5..4f711897be6 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -2093,20 +2093,29 @@ def _unconvert_from_RGB_255(colors): """ Return a tuple where each element gets divided by 255 - Takes a list of color tuples where each element is between 0 and 255 - and returns the same list where each tuple element is normalized to be - between 0 and 1 + Takes a (list of) color tuple(s) where each element is between 0 and + 255. Returns the same tuples where each tuple element is normalized to + a value between 0 and 1 """ - un_rgb_colors = [] - for color in colors: - un_rgb_color = (color[0]/(255.0), - color[1]/(255.0), - color[2]/(255.0)) + if isinstance(colors, tuple): + + un_rgb_color = (colors[0]/(255.0), + colors[1]/(255.0), + colors[2]/(255.0)) + + return un_rgb_color + + if isinstance(colors, list): + un_rgb_colors = [] + for color in colors: + un_rgb_color = (color[0]/(255.0), + color[1]/(255.0), + color[2]/(255.0)) - un_rgb_colors.append(un_rgb_color) + un_rgb_colors.append(un_rgb_color) - return un_rgb_colors + return un_rgb_colors @staticmethod def _map_z2color(zval, colormap, vmin, vmax): From 667110646ad85f62899ef444a29c71ca03f80bef Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Tue, 31 May 2016 15:52:08 -0400 Subject: [PATCH 08/27] gantt//update old parsing functions//tests --- .../test_tools/test_figure_factory.py | 108 ++++++++ plotly/tools.py | 258 ++++++++---------- 2 files changed, 226 insertions(+), 140 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index c79bde4eb2a..62f8825b76c 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -1126,6 +1126,114 @@ def test_table_with_index(self): self.assertEqual(index_table, exp_index_table) +class TestGantt(TestCase): + + def test_validate_gantt(self): + + # checks if gantt is of valid form + df = [dict(Task="Job A")] + + self.assertRaises(PlotlyError, tls.FigureFactory.create_gantt, + df) + + df = [dict(Task='Job A', + Start='2009-02-01', + Finish='2009-08-30', + Complete='a')] + + pattern2 = ("In order to use an indexing column and assign colors to " + "the values of the index, you must choose an actual " + "column name in the dataframe or key if a list of " + "dictionaries is being used.") + + self.assertRaisesRegexp(PlotlyError, pattern2, + tls.FigureFactory.create_gantt, + df, index_col='foo') + + df = 'foo' + + pattern3 = ("You must input either a dataframe or a list of " + "dictionaries.") + + self.assertRaisesRegexp(PlotlyError, pattern3, + tls.FigureFactory.create_gantt, df) + + df = [] + + pattern4 = ("Your list is empty. It must contain at least one " + "dictionary.") + + self.assertRaisesRegexp(PlotlyError, pattern4, + tls.FigureFactory.create_gantt, df) + + df = ['foo'] + + pattern5 = ("Your list must only include dictionaries.") + + self.assertRaisesRegexp(PlotlyError, pattern5, + tls.FigureFactory.create_gantt, df) + + def test_gantt_index(self): + + df = [dict(Task='Job A', + Start='2009-02-01', + Finish='2009-08-30', + Complete=50)] + + pattern = ("In order to use an indexing column and assign colors to " + "the values of the index, you must choose an actual " + "column name in the dataframe or key if a list of " + "dictionaries is being used.") + + self.assertRaisesRegexp(PlotlyError, pattern, + tls.FigureFactory.create_gantt, + df, index_col='foo') + + df = [dict(Task='Job A', Start='2009-02-01', + Finish='2009-08-30', Complete='a'), + dict(Task='Job A', Start='2009-02-01', + Finish='2009-08-30', Complete=50)] + + pattern2 = ("Error in indexing column. Make sure all entries of each " + "column are all numbers or all strings.") + + self.assertRaisesRegexp(PlotlyError, pattern2, + tls.FigureFactory.create_gantt, + df, index_col='Complete') + + def test_gantt_validate_colors(self): + + df = [dict(Task='Job A', Start='2009-02-01', + Finish='2009-08-30', Complete=75), + dict(Task='Job B', Start='2009-02-01', + Finish='2009-08-30', Complete=50)] + + pattern = ("Whoops! The elements in your rgb colors tuples cannot " + "exceed 255.0.") + + self.assertRaisesRegexp(PlotlyError, pattern, + tls.FigureFactory.create_gantt, df, + index_col='Complete', colors='rgb(300,1,1)') + + self.assertRaises(PlotlyError, tls.FigureFactory.create_gantt, + df, index_col='Complete', colors='foo') + + pattern2 = ("Whoops! The elements in your colors tuples cannot " + "exceed 1.0.") + + self.assertRaisesRegexp(PlotlyError, pattern2, + tls.FigureFactory.create_gantt, df, + index_col='Complete', colors=(2, 1, 1)) + + pattern3 = ("You must input a valid colors. Valid types include a " + "plotly scale, rgb, hex or tuple color, a list of any " + "color types, or a dictionary with index names each " + "assigned to a color.") + + self.assertRaisesRegexp(PlotlyError, pattern3, + tls.FigureFactory.create_gantt, df, + index_col='Complete', colors=5) + # class TestDistplot(TestCase): # def test_scipy_import_error(self): diff --git a/plotly/tools.py b/plotly/tools.py index 4f711897be6..3b64b612ee5 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -28,6 +28,8 @@ 'rgb(227, 119, 194)', 'rgb(127, 127, 127)', 'rgb(188, 189, 34)', 'rgb(23, 190, 207)'] +REQ_GANTT_KEYS = ['Task', 'Start', 'Finish'] + # Warning format def warning_on_one_line(message, category, filename, lineno, @@ -1451,13 +1453,10 @@ class FigureFactory(object): """ @staticmethod - def _validate_gantt(df, index_col): + def _validate_gantt(df): """ Validates the inputted dataframe or list """ - from numbers import Number - REQ_GANTT_KEYS = ['Task', 'Start', 'Finish'] - if isinstance(df, pd.core.frame.DataFrame): # validate if df is a dataframe for key in REQ_GANTT_KEYS: @@ -1465,14 +1464,6 @@ def _validate_gantt(df, index_col): raise exceptions.PlotlyError("The columns in your data" "frame must include the " "keys".format(REQ_GANTT_KEYS)) - # check if 'Complete' column has 'nan' values - # or fall outside the [0, 100] interval - if 'Complete' in df: - for percentage in df['Complete']: - if not isinstance(percentage, Number): - raise exceptions.PlotlyError("The values in the " - "'Complete' column must " - "be between 0 and 100.") num_of_rows = len(df.index) chart = [] @@ -1480,19 +1471,15 @@ def _validate_gantt(df, index_col): task_dict = {} for key in df: task_dict[key] = df.ix[index][key] - - #task_dict['Task'] = df.ix[index]['Task'] - #task_dict['Start'] = df.ix[index]['Start'] - #task_dict['Finish'] = df.ix[index]['Finish'] - #if 'Complete' in df: - # task_dict['Complete'] = df.ix[index]['Complete'] chart.append(task_dict) + return chart + # validate if df is a list if not isinstance(df, list): raise exceptions.PlotlyError("You must input either a dataframe " "or a list of dictionaries.") - # validate if df is a list + # validate if df is empty if len(df) <= 0: raise exceptions.PlotlyError("Your list is empty. It must contain " "at least one dictionary.") @@ -1505,14 +1492,6 @@ def _validate_gantt(df, index_col): raise exceptions.PlotlyError("The columns in your data" "frame must be one " "of".format(REQ_GANTT_KEYS)) - #if index_col in df[0]: - # for dictioanry in df: - # if not isinstance(dictionary[index_col], Number): - # raise exceptions.PlotlyError("The values in the " - # "'Complete' column must " - # "be between 0 and 100.") - - # validate index column is all strings or all numbers return df @staticmethod @@ -1653,16 +1632,16 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, 'yref': 'y', } - for index in range(len(tasks)): - tn = tasks[index]['name'] - task_names.append(tn) - del tasks[index]['name'] - tasks[index].update(shape_template) - tasks[index]['y0'] = index - bar_width - tasks[index]['y1'] = index + bar_width + # compute the color for task based on indexing column + if isinstance(chart[0][index_col], Number): + for index in range(len(tasks)): + tn = tasks[index]['name'] + task_names.append(tn) + del tasks[index]['name'] + tasks[index].update(shape_template) + tasks[index]['y0'] = index - bar_width + tasks[index]['y1'] = index + bar_width - # compute the color for task based on indexing column - if isinstance(chart[index][index_col], Number): colors = FigureFactory._unlabel_rgb(colors) lowcolor = colors[0] highcolor = colors[1] @@ -1685,11 +1664,35 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, marker={'color': 'white'} ) ) - if isinstance(chart[index][index_col], str): - color = colors[0] - tasks[index]['fillcolor'] = color - # relabel colors with 'rgb' - colors = FigureFactory._label_rgb(colors) + if isinstance(chart[0][index_col], str): + index_vals = [] + for row in range(len(tasks)): + if chart[row][index_col] not in index_vals: + index_vals.append(chart[row][index_col]) + + index_vals.sort() + + # make a dictionary assignment to each index value + index_vals_dict = {} + # define color index + c_index = 0 + for key in index_vals: + if c_index > len(colors) - 1: + c_index = 0 + index_vals_dict[key] = colors[c_index] + c_index += 1 + + for index in range(len(tasks)): + tn = tasks[index]['name'] + task_names.append(tn) + del tasks[index]['name'] + tasks[index].update(shape_template) + tasks[index]['y0'] = index - bar_width + tasks[index]['y1'] = index + bar_width + + tasks[index]['fillcolor'] = index_vals_dict[ + chart[index][index_col] + ] # add a line for hover text and autorange data.append( @@ -1851,7 +1854,22 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, 'Viridis': ['rgb(68,1,84)', 'rgb(253,231,37)']} # validate gantt input data - chart = FigureFactory._validate_gantt(df, index_col) + chart = FigureFactory._validate_gantt(df) + + if index_col: + if index_col not in chart[0]: + raise exceptions.PlotlyError("In order to use an indexing " + "column and assign colors to " + "the values of the index, you " + "must choose an actual column " + "name in the dataframe or key " + "if a list of dictionaries is " + "being used.") + # validate gantt index column + index_list = [] + for dictionary in chart: + index_list.append(dictionary[index_col]) + FigureFactory._validate_index(index_list) # Validate colors if colors is None: @@ -1981,67 +1999,6 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, "with index names each assigned " "to a color.") - - - - - - - - - """ - if colors is None: - colors = DEFAULT_PLOTLY_COLORS - - # validate color choice - if isinstance(colors, str): - if colors not in plotly_scales: - scale_keys = list(plotly_scales.keys()) - raise exceptions.PlotlyError("You must pick a valid " - "plotly colorscale " - "name from " - "{}".format(scale_keys)) - - colors = [plotly_scales[colors][0], - plotly_scales[colors][1]] - - else: - if not isinstance(colors, list): - raise exceptions.PlotlyError("If 'colors' is a list then " - "its items must be either " - "tuples of the forms " - "'rgbx,y,z' where a,b,c are " - "between 0 and 1 inclusive " - "and x,y,z are between 0 " - "and 255 inclusive, or a hex " - "color string.") - if 'rgb' in colors[0]: - colors = FigureFactory._unlabel_rgb(colors) - for color in colors: - for index in range(3): - if color[index] > 255.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in your " - "rgb colors " - "tuples cannot " - "exceed 255.0.") - colors = FigureFactory._label_rgb(colors) - - #if isinstance(colors[0], hex) - - if isinstance(colors[0], tuple): - for color in colors: - for index in range(3): - if color[index] > 1.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in your " - "rgb colors " - "tuples cannot " - "exceed 1.0.") - colors = FigureFactory._convert_to_RGB_255(colors) - colors = FigureFactory._label_rgb(colors) - """ - if reverse_colors is True: colors.reverse() @@ -2053,12 +2010,6 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, return fig else: - if index_col not in chart[0]: - raise exceptions.PlotlyError("In order to use a colormap on " - "categorical or sequential data " - "the indexing column must be in " - "the chart.") - fig = FigureFactory._gantt_colorscale(chart, colors, title, index_col, show_colorbar, @@ -2937,7 +2888,6 @@ def _scatterplot_theme(dataframe, headers, diag, size, height, width, c_indx += 1 trace_list.append(unique_index_vals) legend_param += 1 - #return trace_list trace_index = 0 indices = range(1, dim + 1) @@ -3461,26 +3411,31 @@ def _endpts_to_intervals(endpts): @staticmethod def _convert_to_RGB_255(colors): """ - Return a list of tuples where each element gets multiplied by 255 + Return a (list of) tuple(s) where each element is multiplied by 255 - Takes a list of color tuples where each element is between 0 and 1 - and returns the same list where each tuple element is normalized to be - between 0 and 255 + Takes a tuple or a list of tuples where each element of each tuple is + between 0 and 1. Returns the same tuple(s) where each tuple element is + multiplied by 255 """ - colors_255 = [] - for color in colors: - rgb_color = (color[0]*255.0, color[1]*255.0, color[2]*255.0) - colors_255.append(rgb_color) - return colors_255 + if isinstance(colors, tuple): + return (colors[0]*255.0, colors[1]*255.0, colors[2]*255.0) + + else: + colors_255 = [] + for color in colors: + rgb_color = (color[0]*255.0, color[1]*255.0, color[2]*255.0) + colors_255.append(rgb_color) + return colors_255 @staticmethod def _n_colors(lowcolor, highcolor, n_colors): """ - Splits a low and high color into a list of #n_colors colors + Splits a low and high color into a list of n_colors colors in it Accepts two color tuples and returns a list of n_colors colors which form the intermediate colors between lowcolor and highcolor + from linearly interpolating through RGB space """ diff_0 = float(highcolor[0] - lowcolor[0]) @@ -3502,18 +3457,21 @@ def _n_colors(lowcolor, highcolor, n_colors): @staticmethod def _label_rgb(colors): """ - Takes colors (a, b, c) and returns tuples 'rgb(a, b, c)' + Takes tuple(s) (a, b, c) and returns rgb color(s) 'rgb(a, b, c)' - Takes a list of two color tuples of the form (a, b, c) and returns the - same list with each tuple replaced by a string 'rgb(a, b, c)' + Takes either a list or a single color tuple of the form (a, b, c) and + returns the same color(s) with each tuple replaced by a string + 'rgb(a, b, c)' """ if isinstance(colors, tuple): - return 'rgb{}'.format(colors) + return ('rgb(%s, %s, %s)' % (colors[0], colors[1], colors[2])) else: colors_label = [] for color in colors: - color_label = 'rgb{}'.format(color) + color_label = ('rgb(%s, %s, %s)' % (color[0], + color[1], + color[2])) colors_label.append(color_label) return colors_label @@ -3521,24 +3479,21 @@ def _label_rgb(colors): @staticmethod def _unlabel_rgb(colors): """ - Takes rgb colors 'rgb(a, b, c)' and returns the tuples (a, b, c) + Takes rgb color(s) 'rgb(a, b, c)' and returns tuple(s) (a, b, c) - This function takes a list of two 'rgb(a, b, c)' color strings and - returns a list of the color tuples in tuple form without the 'rgb' - label. In particular, the output is a list of two tuples of the form - (a, b, c) + This function takes either an 'rgb(a, b, c)' color or a list of + such colors and returns the color tuples in tuple(s) (a, b, c) """ - unlabelled_colors = [] - for character in colors: + if isinstance(colors, str): str_vals = '' - for index in range(len(character)): + for index in range(len(colors)): try: - float(character[index]) - str_vals = str_vals + character[index] + float(colors[index]) + str_vals = str_vals + colors[index] except ValueError: - if (character[index] == ',') or (character[index] == '.'): - str_vals = str_vals + character[index] + if (colors[index] == ',') or (colors[index] == '.'): + str_vals = str_vals + colors[index] str_vals = str_vals + ',' numbers = [] @@ -3549,10 +3504,33 @@ def _unlabel_rgb(colors): else: numbers.append(float(str_num)) str_num = '' - unlabelled_tuple = (numbers[0], numbers[1], numbers[2]) - unlabelled_colors.append(unlabelled_tuple) + return (numbers[0], numbers[1], numbers[2]) + + if isinstance(colors, list): + unlabelled_colors = [] + for color in colors: + str_vals = '' + for index in range(len(color)): + try: + float(color[index]) + str_vals = str_vals + color[index] + except ValueError: + if (color[index] == ',') or (color[index] == '.'): + str_vals = str_vals + color[index] + + str_vals = str_vals + ',' + numbers = [] + str_num = '' + for char in str_vals: + if char != ',': + str_num = str_num + char + else: + numbers.append(float(str_num)) + str_num = '' + unlabelled_tuple = (numbers[0], numbers[1], numbers[2]) + unlabelled_colors.append(unlabelled_tuple) - return unlabelled_colors + return unlabelled_colors @staticmethod def create_scatterplotmatrix(df, dataframe=None, headers=None, From 7ac7d9bc39816041bf165ee456864858f212709a Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Tue, 31 May 2016 15:56:45 -0400 Subject: [PATCH 09/27] Updated schema --- plotly/graph_reference/default-schema.json | 217 ++++++++++++++++++++- 1 file changed, 212 insertions(+), 5 deletions(-) diff --git a/plotly/graph_reference/default-schema.json b/plotly/graph_reference/default-schema.json index b3a0640193b..27cdf82abf6 100644 --- a/plotly/graph_reference/default-schema.json +++ b/plotly/graph_reference/default-schema.json @@ -712,7 +712,8 @@ "mollweide", "hammer", "transverse mercator", - "albers usa" + "albers usa", + "winkel tripel" ] } }, @@ -838,6 +839,114 @@ false ] }, + "images": { + "items": { + "image": { + "layer": { + "description": "Specifies whether images are drawn below or above traces. When `xref` and `yref` are both set to `paper`, image is drawn below the entire plot area.", + "dflt": "above", + "role": "info", + "valType": "enumerated", + "values": [ + "below", + "above" + ] + }, + "opacity": { + "description": "Sets the opacity of the image.", + "dflt": 1, + "max": 1, + "min": 0, + "role": "info", + "valType": "number" + }, + "role": "object", + "sizex": { + "description": "Sets the image container size horizontally. The image will be sized based on the `position` value. When `xref` is set to `paper`, units are sized relative to the plot width.", + "dflt": 0, + "role": "info", + "valType": "number" + }, + "sizey": { + "description": "Sets the image container size vertically. The image will be sized based on the `position` value. When `yref` is set to `paper`, units are sized relative to the plot height.", + "dflt": 0, + "role": "info", + "valType": "number" + }, + "sizing": { + "description": "Specifies which dimension of the image to constrain.", + "dflt": "contain", + "role": "info", + "valType": "enumerated", + "values": [ + "fill", + "contain", + "stretch" + ] + }, + "source": { + "description": "Specifies the URL of the image to be used. The URL must be accessible from the domain where the plot code is run, and can be either relative or absolute.", + "role": "info", + "valType": "string" + }, + "x": { + "description": "Sets the image's x position. When `xref` is set to `paper`, units are sized relative to the plot height. See `xref` for more info", + "dflt": 0, + "role": "info", + "valType": "number" + }, + "xanchor": { + "description": "Sets the anchor for the x position", + "dflt": "left", + "role": "info", + "valType": "enumerated", + "values": [ + "left", + "center", + "right" + ] + }, + "xref": { + "description": "Sets the images's x coordinate axis. If set to a x axis id (e.g. *x* or *x2*), the `x` position refers to an x data coordinate If set to *paper*, the `x` position refers to the distance from the left of plot in normalized coordinates where *0* (*1*) corresponds to the left (right).", + "dflt": "paper", + "role": "info", + "valType": "enumerated", + "values": [ + "paper", + "/^x([2-9]|[1-9][0-9]+)?$/" + ] + }, + "y": { + "description": "Sets the image's y position. When `yref` is set to `paper`, units are sized relative to the plot height. See `yref` for more info", + "dflt": 0, + "role": "info", + "valType": "number" + }, + "yanchor": { + "description": "Sets the anchor for the y position.", + "dflt": "top", + "role": "info", + "valType": "enumerated", + "values": [ + "top", + "middle", + "bottom" + ] + }, + "yref": { + "description": "Sets the images's y coordinate axis. If set to a y axis id (e.g. *y* or *y2*), the `y` position refers to a y data coordinate. If set to *paper*, the `y` position refers to the distance from the bottom of the plot in normalized coordinates where *0* (*1*) corresponds to the bottom (top).", + "dflt": "paper", + "role": "info", + "valType": "enumerated", + "values": [ + "paper", + "/^y([2-9]|[1-9][0-9]+)?$/" + ] + } + } + }, + "role": "object" + }, "legend": { "bgcolor": { "description": "Sets the legend background color.", @@ -877,6 +986,16 @@ "valType": "number" } }, + "orientation": { + "description": "Sets the orientation of the legend.", + "dflt": "v", + "role": "info", + "valType": "enumerated", + "values": [ + "v", + "h" + ] + }, "role": "object", "tracegroupgap": { "description": "Sets the amount of vertical space (in px) between legend groups.", @@ -6018,14 +6137,15 @@ "valType": "number" }, "barmode": { - "description": "Determines how bars at the same location coordinate are displayed on the graph. With *stack*, the bars are stacked on top of one another With *group*, the bars are plotted next to one another centered around the shared location. With *overlay*, the bars are plotted over one another, you might need to an *opacity* to see multiple bars.", + "description": "Determines how bars at the same location coordinate are displayed on the graph. With *stack*, the bars are stacked on top of one another With *relative*, the bars are stacked on top of one another, with negative values below the axis, positive values above With *group*, the bars are plotted next to one another centered around the shared location. With *overlay*, the bars are plotted over one another, you might need to an *opacity* to see multiple bars.", "dflt": "group", "role": "info", "valType": "enumerated", "values": [ "stack", "group", - "overlay" + "overlay", + "relative" ] }, "barnorm": { @@ -9298,14 +9418,15 @@ "valType": "number" }, "barmode": { - "description": "Determines how bars at the same location coordinate are displayed on the graph. With *stack*, the bars are stacked on top of one another With *group*, the bars are plotted next to one another centered around the shared location. With *overlay*, the bars are plotted over one another, you might need to an *opacity* to see multiple bars.", + "description": "Determines how bars at the same location coordinate are displayed on the graph. With *stack*, the bars are stacked on top of one another With *relative*, the bars are stacked on top of one another, with negative values below the axis, positive values above With *group*, the bars are plotted next to one another centered around the shared location. With *overlay*, the bars are plotted over one another, you might need to an *opacity* to see multiple bars.", "dflt": "group", "role": "info", "valType": "enumerated", "values": [ "stack", "group", - "overlay" + "overlay", + "relative" ] }, "barnorm": { @@ -11109,6 +11230,7 @@ }, "lighting": { "ambient": { + "description": "Ambient light increases overall color visibility but can wash out the image.", "dflt": 0.8, "max": 1, "min": 0, @@ -11116,13 +11238,23 @@ "valType": "number" }, "diffuse": { + "description": "Represents the extent that incident rays are reflected in a range of angles.", "dflt": 0.8, "max": 1, "min": 0, "role": "style", "valType": "number" }, + "facenormalsepsilon": { + "description": "Epsilon for face normals calculation avoids math issues arising from degenerate geometry.", + "dflt": 1e-06, + "max": 1, + "min": 0, + "role": "style", + "valType": "number" + }, "fresnel": { + "description": "Represents the reflectance as a dependency of the viewing angle; e.g. paper is reflective when viewing it from the edge of the paper (almost 90 degrees), causing shine.", "dflt": 0.2, "max": 5, "min": 0, @@ -11131,6 +11263,7 @@ }, "role": "object", "roughness": { + "description": "Alters specular reflection; the rougher the surface, the wider and less contrasty the shine.", "dflt": 0.5, "max": 1, "min": 0, @@ -11138,11 +11271,47 @@ "valType": "number" }, "specular": { + "description": "Represents the level that incident rays are reflected in a single direction, causing shine.", "dflt": 0.05, "max": 2, "min": 0, "role": "style", "valType": "number" + }, + "vertexnormalsepsilon": { + "description": "Epsilon for vertex normals calculation avoids math issues arising from degenerate geometry.", + "dflt": 1e-12, + "max": 1, + "min": 0, + "role": "style", + "valType": "number" + } + }, + "lightposition": { + "role": "object", + "x": { + "description": "Numeric vector, representing the X coordinate for each vertex.", + "dflt": 100000, + "max": 100000, + "min": -100000, + "role": "style", + "valType": "number" + }, + "y": { + "description": "Numeric vector, representing the Y coordinate for each vertex.", + "dflt": 100000, + "max": 100000, + "min": -100000, + "role": "style", + "valType": "number" + }, + "z": { + "description": "Numeric vector, representing the Z coordinate for each vertex.", + "dflt": 0, + "max": 100000, + "min": -100000, + "role": "style", + "valType": "number" } }, "name": { @@ -15156,6 +15325,12 @@ }, "scattergl": { "attributes": { + "connectgaps": { + "description": "Determines whether or not gaps (i.e. {nan} or missing values) in the provided data arrays are connected.", + "dflt": false, + "role": "info", + "valType": "boolean" + }, "dx": { "description": "Sets the x coordinate step. See `x0` for more info.", "dflt": 1, @@ -17810,6 +17985,7 @@ }, "lighting": { "ambient": { + "description": "Ambient light increases overall color visibility but can wash out the image.", "dflt": 0.8, "max": 1, "min": 0, @@ -17817,6 +17993,7 @@ "valType": "number" }, "diffuse": { + "description": "Represents the extent that incident rays are reflected in a range of angles.", "dflt": 0.8, "max": 1, "min": 0, @@ -17824,6 +18001,7 @@ "valType": "number" }, "fresnel": { + "description": "Represents the reflectance as a dependency of the viewing angle; e.g. paper is reflective when viewing it from the edge of the paper (almost 90 degrees), causing shine.", "dflt": 0.2, "max": 5, "min": 0, @@ -17832,6 +18010,7 @@ }, "role": "object", "roughness": { + "description": "Alters specular reflection; the rougher the surface, the wider and less contrasty the shine.", "dflt": 0.5, "max": 1, "min": 0, @@ -17839,6 +18018,7 @@ "valType": "number" }, "specular": { + "description": "Represents the level that incident rays are reflected in a single direction, causing shine.", "dflt": 0.05, "max": 2, "min": 0, @@ -17846,6 +18026,33 @@ "valType": "number" } }, + "lightposition": { + "role": "object", + "x": { + "description": "Numeric vector, representing the X coordinate for each vertex.", + "dflt": 10, + "max": 100000, + "min": -100000, + "role": "style", + "valType": "number" + }, + "y": { + "description": "Numeric vector, representing the Y coordinate for each vertex.", + "dflt": 100000, + "max": 100000, + "min": -100000, + "role": "style", + "valType": "number" + }, + "z": { + "description": "Numeric vector, representing the Z coordinate for each vertex.", + "dflt": 0, + "max": 100000, + "min": -100000, + "role": "style", + "valType": "number" + } + }, "name": { "description": "Sets the trace name. The trace name appear as the legend item and on hover.", "role": "info", From f165b7319ccd1f802fb39ebc7df4a920ed3ad495 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Wed, 1 Jun 2016 16:29:01 -0400 Subject: [PATCH 10/27] Updated tests --- .../tests/test_core/test_tools/test_figure_factory.py | 6 ------ plotly/tools.py | 11 +++-------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index 62f8825b76c..a9f1f915b43 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -1130,12 +1130,6 @@ class TestGantt(TestCase): def test_validate_gantt(self): - # checks if gantt is of valid form - df = [dict(Task="Job A")] - - self.assertRaises(PlotlyError, tls.FigureFactory.create_gantt, - df) - df = [dict(Task='Job A', Start='2009-02-01', Finish='2009-08-30', diff --git a/plotly/tools.py b/plotly/tools.py index 3b64b612ee5..24c38b157d4 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1457,8 +1457,8 @@ def _validate_gantt(df): """ Validates the inputted dataframe or list """ - if isinstance(df, pd.core.frame.DataFrame): - # validate if df is a dataframe + if _pandas_imported and isinstance(df, pd.core.frame.DataFrame): + # validate that df has all the required keys for key in REQ_GANTT_KEYS: if key not in df: raise exceptions.PlotlyError("The columns in your data" @@ -1479,6 +1479,7 @@ def _validate_gantt(df): if not isinstance(df, list): raise exceptions.PlotlyError("You must input either a dataframe " "or a list of dictionaries.") + # validate if df is empty if len(df) <= 0: raise exceptions.PlotlyError("Your list is empty. It must contain " @@ -1486,12 +1487,6 @@ def _validate_gantt(df): if not isinstance(df[0], dict): raise exceptions.PlotlyError("Your list must only " "include dictionaries.") - for key in REQ_GANTT_KEYS: - for dictionary in df: - if key not in dictionary: - raise exceptions.PlotlyError("The columns in your data" - "frame must be one " - "of".format(REQ_GANTT_KEYS)) return df @staticmethod From 00f81628a5beb7e78b9c1626dedc0b958eeb61cf Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Thu, 2 Jun 2016 10:23:15 -0400 Subject: [PATCH 11/27] Rewrote tests in core and optional --- .../test_tools/test_figure_factory.py | 96 ++++++++ .../test_optional/test_figure_factory.py | 215 +----------------- 2 files changed, 97 insertions(+), 214 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index a9f1f915b43..ffe9685a622 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -1228,6 +1228,102 @@ def test_gantt_validate_colors(self): tls.FigureFactory.create_gantt, df, index_col='Complete', colors=5) + def test_gantt_all_args(self): + + # check if gantt chart matches with expected output + + df = [dict(Task="Run", + Start='2010-01-01', + Finish='2011-02-02', + Complete=0), + dict(Task="Fast", + Start='2011-01-01', + Finish='2012-06-05', + Complete=25)] + + test_gantt_chart = tls.FigureFactory.create_gantt( + df, colors='Blues', index_col='Complete', reverse_colors=True, + title='Title', bar_width=0.5, showgrid_x=True, showgrid_y=True, + height=500, width=500 + ) + + exp_gantt_chart = { + 'data': [{'marker': {'color': 'white'}, + 'name': '', + 'x': ['2010-01-01', '2011-02-02'], + 'y': [0, 0]}, + {'marker': {'color': 'white'}, + 'name': '', + 'x': ['2011-01-01', '2012-06-05'], + 'y': [1, 1]}], + 'layout': {'height': 500, + 'hovermode': 'closest', + 'shapes': [{'fillcolor': 'rgb(220.0, 220.0, 220.0)', + 'line': {'width': 0}, + 'opacity': 1, + 'type': 'rect', + 'x0': '2010-01-01', + 'x1': '2011-02-02', + 'xref': 'x', + 'y0': -0.5, + 'y1': 0.5, + 'yref': 'y'}, + {'fillcolor': 'rgb(166.25, 167.5, 208.0)', + 'line': {'width': 0}, + 'opacity': 1, + 'type': 'rect', + 'x0': '2011-01-01', + 'x1': '2012-06-05', + 'xref': 'x', + 'y0': 0.5, + 'y1': 1.5, + 'yref': 'y'}], + 'showlegend': False, + 'title': 'Title', + 'width': 500, + 'xaxis': {'rangeselector': {'buttons': [ + {'count': 7, + 'label': '1w', + 'step': 'day', + 'stepmode': 'backward'}, + {'count': 1, + 'label': '1m', + 'step': 'month', + 'stepmode': 'backward'}, + {'count': 6, + 'label': '6m', + 'step': 'month', + 'stepmode': 'backward'}, + {'count': 1, + 'label': 'YTD', + 'step': 'year', + 'stepmode': 'todate'}, + {'count': 1, + 'label': '1y', + 'step': 'year', + 'stepmode': 'backward'}, + {'step': 'all'} + ]}, + 'showgrid': True, + 'type': 'date', + 'zeroline': False}, + 'yaxis': {'autorange': False, + 'range': [-1, 3], + 'showgrid': True, + 'ticktext': ['Run', 'Fast'], + 'tickvals': [0, 1], + 'zeroline': False}} + } + + self.assertEqual(test_gantt_chart['data'][0], + exp_gantt_chart['data'][0]) + + self.assertEqual(test_gantt_chart['data'][1], + exp_gantt_chart['data'][1]) + + self.assertEqual(test_gantt_chart['layout'], + exp_gantt_chart['layout']) + # class TestDistplot(TestCase): # def test_scipy_import_error(self): diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index 83c49eb5450..9715d1add21 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -1018,219 +1018,6 @@ class TestGantt(NumpyTestUtilsMixin, TestCase): def test_df_dataframe(self): - # validate df when it is a dataframe - + # validate dataframe has correct column names df1 = pd.DataFrame([[2, 'Apple']], columns=['Numbers', 'Fruit']) self.assertRaises(PlotlyError, tls.FigureFactory.create_gantt, df1) - - df2 = pd.DataFrame([['Job A', '2009-01-01', '2009-02-30', 25], - ['Job B', '2009-01-01', '2009-02-30', '25']], - columns=['Task', 'Start', 'Finish', 'Complete']) - self.assertRaisesRegexp(PlotlyError, - "The values in the 'Complete' column must " - "be between 0 and 100.", - tls.FigureFactory.create_gantt, df2) - - def test_df_list(self): - - # validate df when it is a list - - df1 = 42 - self.assertRaisesRegexp(PlotlyError, - "You must input either a dataframe or a list " - "of dictionaries.", - tls.FigureFactory.create_gantt, df1) - - df2 = [] - self.assertRaisesRegexp(PlotlyError, - "Your list is empty. It must contain " - "at least one dictionary.", - tls.FigureFactory.create_gantt, df2) - - df3 = [42] - self.assertRaisesRegexp(PlotlyError, - "Your list must only include dictionaries.", - tls.FigureFactory.create_gantt, df3) - - df4 = [{'apple': 2}] - self.assertRaises(PlotlyError, tls.FigureFactory.create_gantt, df4) - - df5 = [{'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30'}, - {'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30', - 'Complete': 25}] - self.assertRaisesRegexp(PlotlyError, - "If you are using 'Complete' as a dictionary " - "key, make sure each dictionary has this key " - "with an assigned value between 0 and 100.", - tls.FigureFactory.create_gantt, df5) - - df6 = [{'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30', - 'Complete': 55}, - {'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30', - 'Complete': 'string'}] - self.assertRaisesRegexp(PlotlyError, - "The values in the 'Complete' column must " - "be between 0 and 100.", - tls.FigureFactory.create_gantt, df6) - - def test_valid_colors(self): - - # check: if color choices are valid - - df = [{'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30', - 'Complete': 55}, - {'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30', - 'Complete': 65}] - - self.assertRaises(PlotlyError, tls.FigureFactory.create_gantt, - df, colors='Weird') - - pattern1 = ( - "If 'colors' is a list then its items must be tripets of the " - "form a,b,c or 'rgbx,y,z' where a,b,c are between 0 and 1 " - "inclusive and x,y,z are between 0 and 255 inclusive." - ) - - self.assertRaisesRegexp(PlotlyError, pattern1, - tls.FigureFactory.create_gantt, df, colors=25) - - pattern2 = ( - "Whoops! The elements in your rgb colors tuples " - "cannot exceed 255.0." - ) - - self.assertRaisesRegexp(PlotlyError, pattern2, - tls.FigureFactory.create_gantt, df, - colors=['rgb(1, 2, 3)', 'rgb(300, 2, 3)']) - - pattern3 = ( - "Whoops! The elements in your rgb colors tuples " - "cannot exceed 1.0." - ) - - self.assertRaisesRegexp(PlotlyError, pattern3, - tls.FigureFactory.create_gantt, df, - colors=[(0.1, 0.2, 0.3), (0.6, 0.8, 1.2)]) - - def test_use_colorscale(self): - - # checks: 'Complete' in inputted array or list - - df = [{'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30'}, - {'Task': 'A Job', - 'Start': '2009-01-01', - 'Finish': '2009-02-30'}] - - pattern = ( - "In order to use colorscale there must be a " - "'Complete' column in the chart." - ) - self.assertRaisesRegexp(PlotlyError, pattern, - tls.FigureFactory.create_gantt, df, - use_colorscale=True) - - def test_gantt_all_args(self): - - # check if gantt chart matches with expected output - - df = [dict(Task="Run", - Start='2010-01-01', - Finish='2011-02-02', - Complete=0), - dict(Task="Fast", - Start='2011-01-01', - Finish='2012-06-05', - Complete=25)] - - test_gantt_chart = tls.FigureFactory.create_gantt( - df, colors='Blues', use_colorscale=True, reverse_colors=True, - title='Title', bar_width=0.5, showgrid_x=True, showgrid_y=True, - height=500, width=500 - ) - - exp_gantt_chart = { - 'data': [{'marker': {'color': 'white'}, - 'name': '', - 'x': ['2010-01-01', '2011-02-02'], - 'y': [0, 0]}, - {'marker': {'color': 'white'}, - 'name': '', - 'x': ['2011-01-01', '2012-06-05'], - 'y': [1, 1]}], - 'layout': {'height': 500, - 'hovermode': 'closest', - 'shapes': [{'fillcolor': 'rgb(220.0, 220.0, 220.0)', - 'line': {'width': 0}, - 'opacity': 1, - 'type': 'rect', - 'x0': '2010-01-01', - 'x1': '2011-02-02', - 'xref': 'x', - 'y0': -0.5, - 'y1': 0.5, - 'yref': 'y'}, - {'fillcolor': 'rgb(166.25, 167.5, 208.0)', - 'line': {'width': 0}, - 'opacity': 1, - 'type': 'rect', - 'x0': '2011-01-01', - 'x1': '2012-06-05', - 'xref': 'x', - 'y0': 0.5, - 'y1': 1.5, - 'yref': 'y'}], - 'showlegend': False, - 'title': 'Title', - 'width': 500, - 'xaxis': { - 'rangeselector': { - 'buttons': [{'count': 7, 'label': '1w', - 'step': 'day', - 'stepmode': 'backward'}, - {'count': 1, 'label': '1m', - 'step': 'month', - 'stepmode': 'backward'}, - {'count': 6, 'label': '6m', - 'step': 'month', - 'stepmode': 'backward'}, - {'count': 1, 'label': 'YTD', - 'step': 'year', - 'stepmode': 'todate'}, - {'count': 1, 'label': '1y', - 'step': 'year', - 'stepmode': 'backward'}, - {'step': 'all'}] - }, - 'showgrid': True, - 'type': 'date', - 'zeroline': False}, - 'yaxis': {'autorange': False, - 'range': [-1, 3], - 'showgrid': True, - 'ticktext': ['Run', 'Fast'], - 'tickvals': [0, 1], - 'zeroline': False}} - } - - self.assert_dict_equal(test_gantt_chart['data'][0], - exp_gantt_chart['data'][0]) - - self.assert_dict_equal(test_gantt_chart['data'][1], - exp_gantt_chart['data'][1]) - - self.assert_dict_equal(test_gantt_chart['layout'], - exp_gantt_chart['layout']) From 66382e29b353e3554a13d49f604cde862d167424 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Thu, 2 Jun 2016 12:14:31 -0400 Subject: [PATCH 12/27] added colors dictionary option --- .../test_tools/test_figure_factory.py | 17 ++ plotly/tools.py | 153 ++++++++++++++++-- 2 files changed, 157 insertions(+), 13 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index ffe9685a622..e39b741ed50 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -1130,6 +1130,8 @@ class TestGantt(TestCase): def test_validate_gantt(self): + # validate the basic gantt inputs + df = [dict(Task='Job A', Start='2009-02-01', Finish='2009-08-30', @@ -1169,6 +1171,8 @@ def test_validate_gantt(self): def test_gantt_index(self): + # validate the index used for gantt + df = [dict(Task='Job A', Start='2009-02-01', Finish='2009-08-30', @@ -1197,6 +1201,8 @@ def test_gantt_index(self): def test_gantt_validate_colors(self): + # validate the gantt colors variable + df = [dict(Task='Job A', Start='2009-02-01', Finish='2009-08-30', Complete=75), dict(Task='Job B', Start='2009-02-01', @@ -1228,6 +1234,17 @@ def test_gantt_validate_colors(self): tls.FigureFactory.create_gantt, df, index_col='Complete', colors=5) + # verify that if colors is a dictionary, its keys span all the + # values in the index column + colors_dict = {75: 'rgb(1, 2, 3)'} + + pattern4 = ("If you are using colors as a dictionary, all of its " + "keys must be all the values in the index column.") + + self.assertRaisesRegexp(PlotlyError, pattern4, + tls.FigureFactory.create_gantt, df, + index_col='Complete', colors=colors_dict) + def test_gantt_all_args(self): # check if gantt chart matches with expected output diff --git a/plotly/tools.py b/plotly/tools.py index 24c38b157d4..633b94811e1 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1765,6 +1765,123 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, fig = dict(data=data, layout=layout) return fig + @staticmethod + def _gantt_dict(chart, colors, title, index_col, show_colorbar, bar_width, + showgrid_x, showgrid_y, height, width, tasks=None, + task_names=None, data=None): + """ + Refer to FigureFactory.create_gantt() for docstring + """ + if tasks is None: + tasks = [] + if task_names is None: + task_names = [] + if data is None: + data = [] + + for index in range(len(chart)): + task = dict(x0=chart[index]['Start'], + x1=chart[index]['Finish'], + name=chart[index]['Task']) + tasks.append(task) + + shape_template = { + 'type': 'rect', + 'xref': 'x', + 'yref': 'y', + 'opacity': 1, + 'line': { + 'width': 0, + }, + 'yref': 'y', + } + + index_vals = [] + for row in range(len(tasks)): + if chart[row][index_col] not in index_vals: + index_vals.append(chart[row][index_col]) + + index_vals.sort() + + # verify each value in index column appears in colors dictionary + for key in index_vals: + if key not in colors: + raise exceptions.PlotlyError("If you are using colors as a " + "dictionary, all of its keys " + "must be all the values in the " + "index column.") + + for index in range(len(tasks)): + tn = tasks[index]['name'] + task_names.append(tn) + del tasks[index]['name'] + tasks[index].update(shape_template) + tasks[index]['y0'] = index - bar_width + tasks[index]['y1'] = index + bar_width + + tasks[index]['fillcolor'] = colors[chart[index][index_col]] + + # add a line for hover text and autorange + data.append( + dict( + x=[tasks[index]['x0'], tasks[index]['x1']], + y=[index, index], + name='', + marker={'color': 'white'} + ) + ) + + layout = dict( + title=title, + showlegend=False, + height=height, + width=width, + shapes=[], + hovermode='closest', + yaxis=dict( + showgrid=showgrid_y, + ticktext=task_names, + tickvals=list(range(len(tasks))), + range=[-1, len(tasks) + 1], + autorange=False, + zeroline=False, + ), + xaxis=dict( + showgrid=showgrid_x, + zeroline=False, + rangeselector=dict( + buttons=list([ + dict(count=7, + label='1w', + step='day', + stepmode='backward'), + dict(count=1, + label='1m', + step='month', + stepmode='backward'), + dict(count=6, + label='6m', + step='month', + stepmode='backward'), + dict(count=1, + label='YTD', + step='year', + stepmode='todate'), + dict(count=1, + label='1y', + step='year', + stepmode='backward'), + dict(step='all') + ]) + ), + type='date' + ) + ) + layout['shapes'] = tasks + + fig = dict(data=data, layout=layout) + return fig + @staticmethod def create_gantt(df, colors=None, index_col=None, show_colorbar=False, reverse_colors=False, title='Gantt Chart', @@ -1776,11 +1893,11 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, :param (array|list) df: input data for gantt chart. Must be either a a dataframe or a list. If dataframe, the columns must include - 'Task', 'Start' and 'Finish'; 'Complete' is optional and is used - to colorscale the bars. If a list, it must contain dictionaries - with the same required column headers, with 'Complete' optional - in the same way as the dataframe - :param (list) colors: a list of 'rgb(a, b, c)' colors where a, b and c + 'Task', 'Start' and 'Finish'. Other columns can be included and + used for indexing. If a list, its elements must be dictionaries + with the same required column headers: 'Task', 'Start' and + 'Finish'. + :param (str|list|dict) colors: a list of 'rgb(a, b, c)' colors where a, b and c are between 0 and 255. Can also be a Plotly colorscale but this is will result in only a 2-color cycle. If number of colors is less than the total number of tasks, colors will cycle @@ -2005,14 +2122,24 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, return fig else: - fig = FigureFactory._gantt_colorscale(chart, colors, - title, index_col, - show_colorbar, - bar_width, showgrid_x, - showgrid_y, height, - width, tasks=None, - task_names=None, data=None) - return fig + if not isinstance(colors, dict): + fig = FigureFactory._gantt_colorscale(chart, colors, + title, index_col, + show_colorbar, + bar_width, showgrid_x, + showgrid_y, height, + width, tasks=None, + task_names=None, + data=None) + return fig + else: + fig = FigureFactory._gantt_dict(chart, colors, title, + index_col, show_colorbar, + bar_width, showgrid_x, + showgrid_y, height, width, + tasks=None, task_names=None, + data=None) + return fig @staticmethod def _find_intermediate_color(lowcolor, highcolor, intermed): From 86f61bc34eec8c3f881f37f63fd50e8b7a86de30 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Thu, 2 Jun 2016 13:34:47 -0400 Subject: [PATCH 13/27] Added dictionary compatibility to colors/fixed doc strings --- .../test_tools/test_figure_factory.py | 11 +++ plotly/tools.py | 92 ++++++++++++++++--- 2 files changed, 90 insertions(+), 13 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index e39b741ed50..e939cdc4869 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -1245,6 +1245,17 @@ def test_gantt_validate_colors(self): tls.FigureFactory.create_gantt, df, index_col='Complete', colors=colors_dict) + # check: index is set if colors is a dictionary + colors_dict_good = {50: 'rgb(1, 2, 3)', 75: 'rgb(5, 10, 15)'} + + pattern5 = ("Error. You have set colors to a dictionary but have not " + "picked an index. An index is required if you are " + "assigning colors to particular values in a dictioanry.") + + self.assertRaisesRegexp(PlotlyError, pattern5, + tls.FigureFactory.create_gantt, df, + colors=colors_dict_good) + def test_gantt_all_args(self): # check if gantt chart matches with expected output diff --git a/plotly/tools.py b/plotly/tools.py index 633b94811e1..2a4401e7306 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1897,10 +1897,19 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, used for indexing. If a list, its elements must be dictionaries with the same required column headers: 'Task', 'Start' and 'Finish'. - :param (str|list|dict) colors: a list of 'rgb(a, b, c)' colors where a, b and c - are between 0 and 255. Can also be a Plotly colorscale but this is - will result in only a 2-color cycle. If number of colors is less - than the total number of tasks, colors will cycle + :param (str|list|dict) colors: either a plotly scale name, an rgb + or hex color, a color tuple or a list of colors. An rgb color is + of the form 'rgb(x, y, z)' where x, y, z belong to the interval + [0, 255] and a color tuple is a tuple of the form (a, b, c) where + a, b and c belong to [0, 1]. If colors is a list, it must + contain the valid color types aforementioned as its members. + If a dictionary, all values of the indexing column must be keys in + colors. + :param (str|float) index_col: the column header (if df is a data + frame) that will function as the indexing column. If df is a list, + index_col must be one of the keys in all the items of df. + :param (bool) show_colorbar: determines if colorbar will be visible. + Only applies if values in the index column are numeric. :param (bool) reverse_colors: reverses the order of selected colors :param (str) title: the title of the chart :param (float) bar_width: the width of the horizontal bars in the plot @@ -1923,10 +1932,10 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, fig = FF.create_gantt(df) # Plot the data - py.iplot(fig, filename='Gantt Chart', world_readable=True) + py.iplot(fig, filename='Simple Gantt Chart', world_readable=True) ``` - Example 2: Colormap by 'Complete' Variable + Example 2: Index by Column with Numerical Entries ``` import plotly.plotly as py from plotly.tools import FigureFactory as FF @@ -1940,11 +1949,61 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, Finish='2009-05-30', Complete=95)] # Create a figure with Plotly colorscale - fig = FF.create_gantt(df, colors='Blues', - bar_width=0.5, showgrid_x=True, showgrid_y=True) + fig = FF.create_gantt(df, colors='Blues', index_col='Complete', + show_colorbar=True, bar_width=0.5, + showgrid_x=True, showgrid_y=True) # Plot the data - py.iplot(fig, filename='Colormap Gantt Chart', world_readable=True) + py.iplot(fig, filename='Numerical Entries', world_readable=True) + ``` + + Example 3: Index by Column with String Entries + ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + + # Make data for chart + df = [dict(Task="Job A", Start='2009-01-01', + Finish='2009-02-30', Resource='Apple'), + dict(Task="Job B", Start='2009-03-05', + Finish='2009-04-15', Resource='Grape'), + dict(Task="Job C", Start='2009-02-20', + Finish='2009-05-30', Resource='Banana')] + + # Create a figure with Plotly colorscale + fig = FF.create_gantt(df, colors=['rgb(200, 50, 25)', + (1, 0, 1), + '#6c4774'], + index_col='Resource', + reverse_colors=True) + + # Plot the data + py.iplot(fig, filename='String Entries', world_readable=True) + ``` + + Example 4: Use a dictionary for colors + ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + + # Make data for chart + df = [dict(Task="Job A", Start='2009-01-01', + Finish='2009-02-30', Resource='Apple'), + dict(Task="Job B", Start='2009-03-05', + Finish='2009-04-15', Resource='Grape'), + dict(Task="Job C", Start='2009-02-20', + Finish='2009-05-30', Resource='Banana')] + + # Make a dictionary of colors + colors = {'Apple': 'rgb(255, 0, 0)', + 'Grape': 'rgb(170, 14, 200)', + 'Banana': (1, 1, 0.2)} + + # Create a figure with Plotly colorscale + fig = FF.create_gantt(df, colors=colors, index_col='Resource') + + # Plot the data + py.iplot(fig, filename='dictioanry colors', world_readable=True) ``` """ plotly_scales = {'Greys': ['rgb(0,0,0)', 'rgb(255,255,255)'], @@ -2115,10 +2174,17 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, colors.reverse() if not index_col: - fig = FigureFactory._gantt(chart, colors, title, - bar_width, showgrid_x, showgrid_y, - height, width, tasks=None, - task_names=None, data=None) + if isinstance(colors, dict): + raise exceptions.PlotlyError("Error. You have set colors to " + "a dictionary but have not " + "picked an index. An index is " + "required if you are assigning " + "colors to particular values " + "in a dictioanry.") + + fig = FigureFactory._gantt(chart, colors, title, bar_width, + showgrid_x, showgrid_y, height, width, + tasks=None, task_names=None, data=None) return fig else: From abff982d319d2e2d902c8780eee63dc118928864 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Fri, 3 Jun 2016 11:58:02 -0400 Subject: [PATCH 14/27] REQ to REQUIRED --- plotly/tools.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index 2a4401e7306..1aec0b33577 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -28,7 +28,7 @@ 'rgb(227, 119, 194)', 'rgb(127, 127, 127)', 'rgb(188, 189, 34)', 'rgb(23, 190, 207)'] -REQ_GANTT_KEYS = ['Task', 'Start', 'Finish'] +REQUIRED_GANTT_KEYS = ['Task', 'Start', 'Finish'] # Warning format @@ -1459,11 +1459,14 @@ def _validate_gantt(df): """ if _pandas_imported and isinstance(df, pd.core.frame.DataFrame): # validate that df has all the required keys - for key in REQ_GANTT_KEYS: + for key in REQUIRED_GANTT_KEYS: if key not in df: raise exceptions.PlotlyError("The columns in your data" "frame must include the " - "keys".format(REQ_GANTT_KEYS)) + "keys".format( + REQUIRED_GANTT_KEYS + ) + ) num_of_rows = len(df.index) chart = [] From e6aa0e48f1b0f8f90f86ec86dfa7169d6a946f38 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Fri, 3 Jun 2016 13:24:20 -0400 Subject: [PATCH 15/27] trace attempts --- plotly/tools.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plotly/tools.py b/plotly/tools.py index 1aec0b33577..4a512afa294 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1775,6 +1775,7 @@ def _gantt_dict(chart, colors, title, index_col, show_colorbar, bar_width, """ Refer to FigureFactory.create_gantt() for docstring """ + from plotly.graph_objs import graph_objs if tasks is None: tasks = [] if task_names is None: @@ -1834,6 +1835,22 @@ def _gantt_dict(chart, colors, title, index_col, show_colorbar, bar_width, ) ) + if show_colorbar is True: + # generate dummy data for colorscale visibility + trace2 = dict( + #x=[tasks[0]['x0'], tasks[0]['x0']], + x=[2, 6], + y=[4, 2], + name='asdf', + visible='legendonly', + marker=dict( + size=10, + color='rgb(25, 50, 150)'), + showlegend=True + ) + + data.append(trace2) + layout = dict( title=title, showlegend=False, From cfa066e2a33a3eedf7014e3670dce99742e7e880 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Wed, 15 Jun 2016 16:32:50 -0400 Subject: [PATCH 16/27] Added color_parser functionality in create_gantt --- plotly/tools.py | 273 +++++++++++++----------------------------------- 1 file changed, 71 insertions(+), 202 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index 541cfc30fcc..3770e47cd0c 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1517,8 +1517,7 @@ def _validate_gantt(df): @staticmethod def _gantt(chart, colors, title, bar_width, showgrid_x, showgrid_y, - height, width, tasks=None, - task_names=None, data=None): + height, width, tasks=None, task_names=None, data=None): """ Refer to FigureFactory.create_gantt() for docstring """ @@ -1636,6 +1635,8 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, if data is None: data = [] + #if chart[index_col] + for index in range(len(chart)): task = dict(x0=chart[index]['Start'], x1=chart[index]['Finish'], @@ -1663,18 +1664,25 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, tasks[index]['y0'] = index - bar_width tasks[index]['y1'] = index + bar_width - colors = FigureFactory._unlabel_rgb(colors) + # unlabel color + colors = FigureFactory._color_parser( + colors, FigureFactory._unlabel_rgb + ) lowcolor = colors[0] highcolor = colors[1] intermed = (chart[index][index_col])/100.0 - intermed_color = FigureFactory._find_intermediate_color(lowcolor, - highcolor, - intermed) - intermed_color = FigureFactory._label_rgb(intermed_color) + intermed_color = FigureFactory._find_intermediate_color( + lowcolor, highcolor, intermed + ) + intermed_color = FigureFactory._color_parser( + intermed_color, FigureFactory._label_rgb + ) tasks[index]['fillcolor'] = intermed_color # relabel colors with 'rgb' - colors = FigureFactory._label_rgb(colors) + colors = FigureFactory._color_parser( + colors, FigureFactory._label_rgb + ) # add a line for hover text and autorange data.append( @@ -1798,7 +1806,6 @@ def _gantt_dict(chart, colors, title, index_col, show_colorbar, bar_width, """ Refer to FigureFactory.create_gantt() for docstring """ - from plotly.graph_objs import graph_objs if tasks is None: tasks = [] if task_names is None: @@ -1833,10 +1840,10 @@ def _gantt_dict(chart, colors, title, index_col, show_colorbar, bar_width, # verify each value in index column appears in colors dictionary for key in index_vals: if key not in colors: - raise exceptions.PlotlyError("If you are using colors as a " - "dictionary, all of its keys " - "must be all the values in the " - "index column.") + raise exceptions.PlotlyError( + "If you are using colors as a dictionary, all of its " + "keys must be all the values in the index column." + ) for index in range(len(tasks)): tn = tasks[index]['name'] @@ -1858,21 +1865,20 @@ def _gantt_dict(chart, colors, title, index_col, show_colorbar, bar_width, ) ) - if show_colorbar is True: + #if show_colorbar is True: # generate dummy data for colorscale visibility - trace2 = dict( - #x=[tasks[0]['x0'], tasks[0]['x0']], - x=[2, 6], - y=[4, 2], - name='asdf', - visible='legendonly', - marker=dict( - size=10, - color='rgb(25, 50, 150)'), - showlegend=True - ) - - data.append(trace2) + # trace2 = dict( + # #x=[tasks[0]['x0'], tasks[0]['x0']], + # x=[2, 6], + # y=[4, 2], + # name='asdf', + # visible='legendonly', + # marker=dict( + # size=10, + # color='rgb(25, 50, 150)'), + # showlegend=True + # ) + # data.append(trace2) layout = dict( title=title, @@ -2049,36 +2055,17 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, py.iplot(fig, filename='dictioanry colors', world_readable=True) ``` """ - plotly_scales = {'Greys': ['rgb(0,0,0)', 'rgb(255,255,255)'], - 'YlGnBu': ['rgb(8,29,88)', 'rgb(255,255,217)'], - 'Greens': ['rgb(0,68,27)', 'rgb(247,252,245)'], - 'YlOrRd': ['rgb(128,0,38)', 'rgb(255,255,204)'], - 'Bluered': ['rgb(0,0,255)', 'rgb(255,0,0)'], - 'RdBu': ['rgb(5,10,172)', 'rgb(178,10,28)'], - 'Reds': ['rgb(220,220,220)', 'rgb(178,10,28)'], - 'Blues': ['rgb(5,10,172)', 'rgb(220,220,220)'], - 'Picnic': ['rgb(0,0,255)', 'rgb(255,0,0)'], - 'Rainbow': ['rgb(150,0,90)', 'rgb(255,0,0)'], - 'Portland': ['rgb(12,51,131)', 'rgb(217,30,30)'], - 'Jet': ['rgb(0,0,131)', 'rgb(128,0,0)'], - 'Hot': ['rgb(0,0,0)', 'rgb(255,255,255)'], - 'Blackbody': ['rgb(0,0,0)', 'rgb(160,200,255)'], - 'Earth': ['rgb(0,0,130)', 'rgb(255,255,255)'], - 'Electric': ['rgb(0,0,0)', 'rgb(255,250,220)'], - 'Viridis': ['rgb(68,1,84)', 'rgb(253,231,37)']} - # validate gantt input data chart = FigureFactory._validate_gantt(df) if index_col: if index_col not in chart[0]: - raise exceptions.PlotlyError("In order to use an indexing " - "column and assign colors to " - "the values of the index, you " - "must choose an actual column " - "name in the dataframe or key " - "if a list of dictionaries is " - "being used.") + raise exceptions.PlotlyError( + "In order to use an indexing column and assign colors to " + "the values of the index, you must choose an actual " + "column name in the dataframe or key if a list of " + "dictionaries is being used.") + # validate gantt index column index_list = [] for dictionary in chart: @@ -2086,169 +2073,51 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, FigureFactory._validate_index(index_list) # Validate colors - if colors is None: - colors = DEFAULT_PLOTLY_COLORS - - if isinstance(colors, str): - if colors in plotly_scales: - colors = plotly_scales[colors] - - elif 'rgb' in colors: - colors = FigureFactory._unlabel_rgb(colors) - for value in colors: - if value > 255.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in your " - "rgb colors " - "tuples cannot " - "exceed 255.0.") - colors = FigureFactory._label_rgb(colors) - - # put colors in list - colors_list = [] - colors_list.append(colors) - colors = colors_list - - elif '#' in colors: - colors = FigureFactory._hex_to_rgb(colors) - colors = FigureFactory._label_rgb(colors) - - # put colors in list - colors_list = [] - colors_list.append(colors) - colors = colors_list - - else: - scale_keys = list(plotly_scales.keys()) - raise exceptions.PlotlyError("If you input a string " - "for 'colors', it must " - "either be a Plotly " - "colorscale, an 'rgb' " - "color or a hex color." - "Valid plotly colorscale " - "names are {}".format(scale_keys)) - elif isinstance(colors, tuple): - for value in colors: - if value > 1.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in " - "your colors " - "tuples cannot " - "exceed 1.0.") - - colors_list = [] - colors_list.append(colors) - colors = colors_list - - colors = FigureFactory._convert_to_RGB_255(colors) - colors = FigureFactory._label_rgb(colors) - - elif isinstance(colors, list): - new_colormap = [] - for color in colors: - if 'rgb' in color: - color = FigureFactory._unlabel_rgb(color) - - for value in color: - if value > 255.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in your " - "rgb colors " - "tuples cannot " - "exceed 255.0.") - - color = FigureFactory._label_rgb(color) - new_colormap.append(color) - elif '#' in color: - color = FigureFactory._hex_to_rgb(color) - color = FigureFactory._label_rgb(color) - new_colormap.append(color) - elif isinstance(color, tuple): - for value in color: - if value > 1.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in " - "your colors " - "tuples cannot " - "exceed 1.0.") - color = FigureFactory._convert_to_RGB_255(color) - color = FigureFactory._label_rgb(color) - new_colormap.append(color) - colors = new_colormap - - elif isinstance(colors, dict): - for name in colors: - if 'rgb' in colors[name]: - color = FigureFactory._unlabel_rgb(colors[name]) - for value in color: - if value > 255.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in your " - "rgb colors " - "tuples cannot " - "exceed 255.0.") - - elif '#' in colors[name]: - color = FigureFactory._hex_to_rgb(colors[name]) - color = FigureFactory._label_rgb(color) - colors[name] = color - - elif isinstance(colors[name], tuple): - for value in colors[name]: - if value > 1.0: - raise exceptions.PlotlyError("Whoops! The " - "elements in " - "your colors " - "tuples cannot " - "exceed 1.0.") - color = FigureFactory._convert_to_RGB_255(colors[name]) - color = FigureFactory._label_rgb(color) - colors[name] = color - + if isinstance(colors, dict): + colors = FigureFactory._validate_colors_dict(colors, 'rgb') else: - raise exceptions.PlotlyError("You must input a valid colors. " - "Valid types include a plotly scale, " - "rgb, hex or tuple color, a list of " - "any color types, or a dictionary " - "with index names each assigned " - "to a color.") + colors = FigureFactory._validate_colors(colors, 'rgb') if reverse_colors is True: colors.reverse() if not index_col: if isinstance(colors, dict): - raise exceptions.PlotlyError("Error. You have set colors to " - "a dictionary but have not " - "picked an index. An index is " - "required if you are assigning " - "colors to particular values " - "in a dictioanry.") - - fig = FigureFactory._gantt(chart, colors, title, bar_width, - showgrid_x, showgrid_y, height, width, - tasks=None, task_names=None, data=None) + raise exceptions.PlotlyError( + "Error. You have set colors to a dictionary but have not " + "picked an index. An index is required if you are " + "assigning colors to particular values in a dictioanry." + ) + fig = FigureFactory._gantt( + chart, colors, title, bar_width, showgrid_x, showgrid_y, + height, width, tasks=None, task_names=None, data=None + ) return fig - else: if not isinstance(colors, dict): - fig = FigureFactory._gantt_colorscale(chart, colors, - title, index_col, - show_colorbar, - bar_width, showgrid_x, - showgrid_y, height, - width, tasks=None, - task_names=None, - data=None) + # check that colors has at least 2 colors + if len(colors) < 2: + raise exceptions.PlotlyError( + "You must use at least 2 colors in 'colors' if you " + "are using a colorscale. However only the first two " + "colors given will be used for the lower and upper " + "bounds on the colormap." + ) + fig = FigureFactory._gantt_colorscale( + chart, colors, title, index_col, show_colorbar, bar_width, + showgrid_x, showgrid_y, height, width, + tasks=None, task_names=None, data=None + ) return fig else: - fig = FigureFactory._gantt_dict(chart, colors, title, - index_col, show_colorbar, - bar_width, showgrid_x, - showgrid_y, height, width, - tasks=None, task_names=None, - data=None) + fig = FigureFactory._gantt_dict( + chart, colors, title, index_col, show_colorbar, bar_width, + showgrid_x, showgrid_y, height, width, + tasks=None, task_names=None, data=None + ) + return fig + @staticmethod def _validate_colors(colors, colortype='tuple'): """ Validates color(s) and returns a list of color(s) of a specified type From ba65217493d347ede5000923be0a040c43a691e2 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Fri, 17 Jun 2016 13:12:19 -0400 Subject: [PATCH 17/27] Added legend support for gantt charts and added more tests --- .../test_tools/test_figure_factory.py | 44 +++--- plotly/tools.py | 133 +++++++++++------- 2 files changed, 110 insertions(+), 67 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index e939cdc4869..e741b65294c 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -1204,9 +1204,9 @@ def test_gantt_validate_colors(self): # validate the gantt colors variable df = [dict(Task='Job A', Start='2009-02-01', - Finish='2009-08-30', Complete=75), + Finish='2009-08-30', Complete=75, Resource='A'), dict(Task='Job B', Start='2009-02-01', - Finish='2009-08-30', Complete=50)] + Finish='2009-08-30', Complete=50, Resource='B')] pattern = ("Whoops! The elements in your rgb colors tuples cannot " "exceed 255.0.") @@ -1225,41 +1225,53 @@ def test_gantt_validate_colors(self): tls.FigureFactory.create_gantt, df, index_col='Complete', colors=(2, 1, 1)) - pattern3 = ("You must input a valid colors. Valid types include a " - "plotly scale, rgb, hex or tuple color, a list of any " - "color types, or a dictionary with index names each " - "assigned to a color.") - - self.assertRaisesRegexp(PlotlyError, pattern3, - tls.FigureFactory.create_gantt, df, - index_col='Complete', colors=5) - # verify that if colors is a dictionary, its keys span all the # values in the index column colors_dict = {75: 'rgb(1, 2, 3)'} - pattern4 = ("If you are using colors as a dictionary, all of its " + pattern3 = ("If you are using colors as a dictionary, all of its " "keys must be all the values in the index column.") - self.assertRaisesRegexp(PlotlyError, pattern4, + self.assertRaisesRegexp(PlotlyError, pattern3, tls.FigureFactory.create_gantt, df, index_col='Complete', colors=colors_dict) # check: index is set if colors is a dictionary colors_dict_good = {50: 'rgb(1, 2, 3)', 75: 'rgb(5, 10, 15)'} - pattern5 = ("Error. You have set colors to a dictionary but have not " + pattern4 = ("Error. You have set colors to a dictionary but have not " "picked an index. An index is required if you are " "assigning colors to particular values in a dictioanry.") - self.assertRaisesRegexp(PlotlyError, pattern5, + self.assertRaisesRegexp(PlotlyError, pattern4, tls.FigureFactory.create_gantt, df, colors=colors_dict_good) + # check: number of colors is equal to or greater than number of + # unique index string values + pattern5 = ("Error. The number of colors in 'colors' must be no less " + "than the number of unique index values in your group " + "column.") + + self.assertRaisesRegexp(PlotlyError, pattern5, + tls.FigureFactory.create_gantt, df, + index_col='Resource', + colors=['#ffffff']) + + # check: if index is numeric, colors has at least 2 colors in it + pattern6 = ("You must use at least 2 colors in 'colors' if you " + "are using a colorscale. However only the first two " + "colors given will be used for the lower and upper " + "bounds on the colormap.") + + self.assertRaisesRegexp(PlotlyError, pattern6, + tls.FigureFactory.create_gantt, df, + index_col='Complete', + colors=['#ffffff']) + def test_gantt_all_args(self): # check if gantt chart matches with expected output - df = [dict(Task="Run", Start='2010-01-01', Finish='2011-02-02', diff --git a/plotly/tools.py b/plotly/tools.py index 3770e47cd0c..7535ca06419 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -53,6 +53,7 @@ DEFAULT_HISTNORM = 'probability density' ALTERNATIVE_HISTNORM = 'probability' + # Warning format def warning_on_one_line(message, category, filename, lineno, file=None, line=None): @@ -1484,12 +1485,10 @@ def _validate_gantt(df): # validate that df has all the required keys for key in REQUIRED_GANTT_KEYS: if key not in df: - raise exceptions.PlotlyError("The columns in your data" - "frame must include the " - "keys".format( - REQUIRED_GANTT_KEYS - ) - ) + raise exceptions.PlotlyError( + "The columns in your dataframe must include the " + "keys".format(REQUIRED_GANTT_KEYS) + ) num_of_rows = len(df.index) chart = [] @@ -1634,8 +1633,7 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, task_names = [] if data is None: data = [] - - #if chart[index_col] + showlegend = False for index in range(len(chart)): task = dict(x0=chart[index]['Start'], @@ -1656,6 +1654,14 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, # compute the color for task based on indexing column if isinstance(chart[0][index_col], Number): + # check that colors has at least 2 colors + if len(colors) < 2: + raise exceptions.PlotlyError( + "You must use at least 2 colors in 'colors' if you " + "are using a colorscale. However only the first two " + "colors given will be used for the lower and upper " + "bounds on the colormap." + ) for index in range(len(tasks)): tn = tasks[index]['name'] task_names.append(tn) @@ -1693,6 +1699,22 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, marker={'color': 'white'} ) ) + + if show_colorbar is True: + # generate dummy data for colorscale visibility + data.append( + dict( + x=[tasks[index]['x0'], tasks[index]['x0']], + y=[index, index], + name='', + marker={'color': 'white', + 'colorscale': [[0, colors[0]], [1, colors[1]]], + 'showscale': True, + 'cmax': 100, + 'cmin': 0} + ) + ) + if isinstance(chart[0][index_col], str): index_vals = [] for row in range(len(tasks)): @@ -1701,6 +1723,13 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, index_vals.sort() + if len(colors) < len(index_vals): + raise exceptions.PlotlyError( + "Error. The number of colors in 'colors' must be no less " + "than the number of unique index values in your group " + "column." + ) + # make a dictionary assignment to each index value index_vals_dict = {} # define color index @@ -1733,24 +1762,27 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, ) ) - if show_colorbar is True: - # generate dummy data for colorscale visibility - data.append( - dict( - x=[tasks[index]['x0'], tasks[index]['x0']], - y=[index, index], - name='', - marker={'color': 'white', - 'colorscale': [[0, colors[0]], [1, colors[1]]], - 'showscale': True, - 'cmax': 100, - 'cmin': 0} - ) - ) + if show_colorbar is True: + # generate dummy data to generate legend + showlegend = True + for k, index_value in enumerate(index_vals): + data.append( + dict( + x=[tasks[index]['x0'], tasks[index]['x0']], + y=[k, k], + showlegend=True, + name=str(index_value), + hoverinfo='none', + marker=dict( + color=colors[k], + size=1 + ) + ) + ) layout = dict( title=title, - showlegend=False, + showlegend=showlegend, height=height, width=width, shapes=[], @@ -1812,6 +1844,7 @@ def _gantt_dict(chart, colors, title, index_col, show_colorbar, bar_width, task_names = [] if data is None: data = [] + showlegend = False for index in range(len(chart)): task = dict(x0=chart[index]['Start'], @@ -1865,24 +1898,27 @@ def _gantt_dict(chart, colors, title, index_col, show_colorbar, bar_width, ) ) - #if show_colorbar is True: - # generate dummy data for colorscale visibility - # trace2 = dict( - # #x=[tasks[0]['x0'], tasks[0]['x0']], - # x=[2, 6], - # y=[4, 2], - # name='asdf', - # visible='legendonly', - # marker=dict( - # size=10, - # color='rgb(25, 50, 150)'), - # showlegend=True - # ) - # data.append(trace2) + if show_colorbar is True: + # generate dummy data to generate legend + showlegend = True + for k, index_value in enumerate(index_vals): + data.append( + dict( + x=[tasks[index]['x0'], tasks[index]['x0']], + y=[k, k], + showlegend=True, + hoverinfo='none', + name=str(index_value), + marker=dict( + color=colors[index_value], + size=1 + ) + ) + ) layout = dict( title=title, - showlegend=False, + showlegend=showlegend, height=height, width=width, shapes=[], @@ -1946,9 +1982,9 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, used for indexing. If a list, its elements must be dictionaries with the same required column headers: 'Task', 'Start' and 'Finish'. - :param (str|list|dict) colors: either a plotly scale name, an rgb - or hex color, a color tuple or a list of colors. An rgb color is - of the form 'rgb(x, y, z)' where x, y, z belong to the interval + :param (str|list|dict|tuple) colors: either a plotly scale name, an + rgb or hex color, a color tuple or a list of colors. An rgb color + is of the form 'rgb(x, y, z)' where x, y, z belong to the interval [0, 255] and a color tuple is a tuple of the form (a, b, c) where a, b and c belong to [0, 1]. If colors is a list, it must contain the valid color types aforementioned as its members. @@ -2024,7 +2060,8 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, (1, 0, 1), '#6c4774'], index_col='Resource', - reverse_colors=True) + reverse_colors=True, + show_colorbar=True) # Plot the data py.iplot(fig, filename='String Entries', world_readable=True) @@ -2049,7 +2086,9 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, 'Banana': (1, 1, 0.2)} # Create a figure with Plotly colorscale - fig = FF.create_gantt(df, colors=colors, index_col='Resource') + fig = FF.create_gantt(df, colors=colors, + index_col='Resource', + show_colorbar=True) # Plot the data py.iplot(fig, filename='dictioanry colors', world_readable=True) @@ -2095,14 +2134,6 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, return fig else: if not isinstance(colors, dict): - # check that colors has at least 2 colors - if len(colors) < 2: - raise exceptions.PlotlyError( - "You must use at least 2 colors in 'colors' if you " - "are using a colorscale. However only the first two " - "colors given will be used for the lower and upper " - "bounds on the colormap." - ) fig = FigureFactory._gantt_colorscale( chart, colors, title, index_col, show_colorbar, bar_width, showgrid_x, showgrid_y, height, width, From 9e52ce41753e52f8a1159ab177f04f9c015501d9 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Fri, 17 Jun 2016 16:39:58 -0400 Subject: [PATCH 18/27] Added dataframe all_args test for gantt//various formatting --- .../test_tools/test_figure_factory.py | 97 ++++++++++--------- .../test_optional/test_figure_factory.py | 89 +++++++++++++++++ plotly/tools.py | 28 ++++++ 3 files changed, 166 insertions(+), 48 deletions(-) diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py index e741b65294c..97fb061b213 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -1132,15 +1132,15 @@ def test_validate_gantt(self): # validate the basic gantt inputs - df = [dict(Task='Job A', - Start='2009-02-01', - Finish='2009-08-30', - Complete='a')] + df = [{'Task': 'Job A', + 'Start': '2009-02-01', + 'Finish': '2009-08-30', + 'Complete': 'a'}] - pattern2 = ("In order to use an indexing column and assign colors to " - "the values of the index, you must choose an actual " - "column name in the dataframe or key if a list of " - "dictionaries is being used.") + pattern2 = ('In order to use an indexing column and assign colors to ' + 'the values of the index, you must choose an actual ' + 'column name in the dataframe or key if a list of ' + 'dictionaries is being used.') self.assertRaisesRegexp(PlotlyError, pattern2, tls.FigureFactory.create_gantt, @@ -1148,23 +1148,23 @@ def test_validate_gantt(self): df = 'foo' - pattern3 = ("You must input either a dataframe or a list of " - "dictionaries.") + pattern3 = ('You must input either a dataframe or a list of ' + 'dictionaries.') self.assertRaisesRegexp(PlotlyError, pattern3, tls.FigureFactory.create_gantt, df) df = [] - pattern4 = ("Your list is empty. It must contain at least one " - "dictionary.") + pattern4 = ('Your list is empty. It must contain at least one ' + 'dictionary.') self.assertRaisesRegexp(PlotlyError, pattern4, tls.FigureFactory.create_gantt, df) df = ['foo'] - pattern5 = ("Your list must only include dictionaries.") + pattern5 = ('Your list must only include dictionaries.') self.assertRaisesRegexp(PlotlyError, pattern5, tls.FigureFactory.create_gantt, df) @@ -1173,27 +1173,27 @@ def test_gantt_index(self): # validate the index used for gantt - df = [dict(Task='Job A', - Start='2009-02-01', - Finish='2009-08-30', - Complete=50)] + df = [{'Task': 'Job A', + 'Start': '2009-02-01', + 'Finish': '2009-08-30', + 'Complete': 50}] - pattern = ("In order to use an indexing column and assign colors to " - "the values of the index, you must choose an actual " - "column name in the dataframe or key if a list of " - "dictionaries is being used.") + pattern = ('In order to use an indexing column and assign colors to ' + 'the values of the index, you must choose an actual ' + 'column name in the dataframe or key if a list of ' + 'dictionaries is being used.') self.assertRaisesRegexp(PlotlyError, pattern, tls.FigureFactory.create_gantt, df, index_col='foo') - df = [dict(Task='Job A', Start='2009-02-01', - Finish='2009-08-30', Complete='a'), - dict(Task='Job A', Start='2009-02-01', - Finish='2009-08-30', Complete=50)] + df = [{'Task': 'Job A', 'Start': '2009-02-01', + 'Finish': '2009-08-30', 'Complete': 'a'}, + {'Task': 'Job A', 'Start': '2009-02-01', + 'Finish': '2009-08-30', 'Complete': 50}] - pattern2 = ("Error in indexing column. Make sure all entries of each " - "column are all numbers or all strings.") + pattern2 = ('Error in indexing column. Make sure all entries of each ' + 'column are all numbers or all strings.') self.assertRaisesRegexp(PlotlyError, pattern2, tls.FigureFactory.create_gantt, @@ -1203,13 +1203,13 @@ def test_gantt_validate_colors(self): # validate the gantt colors variable - df = [dict(Task='Job A', Start='2009-02-01', - Finish='2009-08-30', Complete=75, Resource='A'), - dict(Task='Job B', Start='2009-02-01', - Finish='2009-08-30', Complete=50, Resource='B')] + df = [{'Task': 'Job A', 'Start': '2009-02-01', + 'Finish': '2009-08-30', 'Complete': 75, 'Resource': 'A'}, + {'Task': 'Job B', 'Start': '2009-02-01', + 'Finish': '2009-08-30', 'Complete': 50, 'Resource': 'B'}] - pattern = ("Whoops! The elements in your rgb colors tuples cannot " - "exceed 255.0.") + pattern = ('Whoops! The elements in your rgb colors tuples cannot ' + 'exceed 255.0.') self.assertRaisesRegexp(PlotlyError, pattern, tls.FigureFactory.create_gantt, df, @@ -1218,8 +1218,8 @@ def test_gantt_validate_colors(self): self.assertRaises(PlotlyError, tls.FigureFactory.create_gantt, df, index_col='Complete', colors='foo') - pattern2 = ("Whoops! The elements in your colors tuples cannot " - "exceed 1.0.") + pattern2 = ('Whoops! The elements in your colors tuples cannot ' + 'exceed 1.0.') self.assertRaisesRegexp(PlotlyError, pattern2, tls.FigureFactory.create_gantt, df, @@ -1229,8 +1229,8 @@ def test_gantt_validate_colors(self): # values in the index column colors_dict = {75: 'rgb(1, 2, 3)'} - pattern3 = ("If you are using colors as a dictionary, all of its " - "keys must be all the values in the index column.") + pattern3 = ('If you are using colors as a dictionary, all of its ' + 'keys must be all the values in the index column.') self.assertRaisesRegexp(PlotlyError, pattern3, tls.FigureFactory.create_gantt, df, @@ -1239,9 +1239,9 @@ def test_gantt_validate_colors(self): # check: index is set if colors is a dictionary colors_dict_good = {50: 'rgb(1, 2, 3)', 75: 'rgb(5, 10, 15)'} - pattern4 = ("Error. You have set colors to a dictionary but have not " - "picked an index. An index is required if you are " - "assigning colors to particular values in a dictioanry.") + pattern4 = ('Error. You have set colors to a dictionary but have not ' + 'picked an index. An index is required if you are ' + 'assigning colors to particular values in a dictioanry.') self.assertRaisesRegexp(PlotlyError, pattern4, tls.FigureFactory.create_gantt, df, @@ -1272,14 +1272,15 @@ def test_gantt_validate_colors(self): def test_gantt_all_args(self): # check if gantt chart matches with expected output - df = [dict(Task="Run", - Start='2010-01-01', - Finish='2011-02-02', - Complete=0), - dict(Task="Fast", - Start='2011-01-01', - Finish='2012-06-05', - Complete=25)] + + df = [{'Task': 'Run', + 'Start': '2010-01-01', + 'Finish': '2011-02-02', + 'Complete': 0}, + {'Task': 'Fast', + 'Start': '2011-01-01', + 'Finish': '2012-06-05', + 'Complete': 25}] test_gantt_chart = tls.FigureFactory.create_gantt( df, colors='Blues', index_col='Complete', reverse_colors=True, diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index 8e3eb4ed68f..983e421f5f8 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -1275,6 +1275,95 @@ def test_df_dataframe(self): df1 = pd.DataFrame([[2, 'Apple']], columns=['Numbers', 'Fruit']) self.assertRaises(PlotlyError, tls.FigureFactory.create_gantt, df1) + def test_df_dataframe_all_args(self): + + # check if gantt chart matches with expected output + + df = pd.DataFrame([['Job A', '2009-01-01', '2009-02-30', 'A'], + ['Job B', '2009-03-05', '2009-04-15', 'B']], + columns=['Task', 'Start', 'Finish', 'Resource']) + + test_gantt_chart = tls.FigureFactory.create_gantt( + df, colors='Blues', index_col='Resource' + ) + + exp_gantt_chart = { + 'data': [{'marker': {'color': 'white'}, + 'name': '', + 'x': ['2009-01-01', '2009-02-30'], + 'y': [0, 0]}, + {'marker': {'color': 'white'}, + 'name': '', + 'x': ['2009-03-05', '2009-04-15'], + 'y': [1, 1]}], + 'layout': {'height': 600, + 'hovermode': 'closest', + 'shapes': [{'fillcolor': 'rgb(5.0, 10.0, 172.0)', + 'line': {'width': 0}, + 'opacity': 1, + 'type': 'rect', + 'x0': '2009-01-01', + 'x1': '2009-02-30', + 'xref': 'x', + 'y0': -0.2, + 'y1': 0.2, + 'yref': 'y'}, + {'fillcolor': 'rgb(220.0, 220.0, 220.0)', + 'line': {'width': 0}, + 'opacity': 1, + 'type': 'rect', + 'x0': '2009-03-05', + 'x1': '2009-04-15', + 'xref': 'x', + 'y0': 0.8, + 'y1': 1.2, + 'yref': 'y'}], + 'showlegend': False, + 'title': 'Gantt Chart', + 'width': 900, + 'xaxis': {'rangeselector': {'buttons': [ + {'count': 7, + 'label': '1w', + 'step': 'day', + 'stepmode': 'backward'}, + {'count': 1, + 'label': '1m', + 'step': 'month', + 'stepmode': 'backward'}, + {'count': 6, + 'label': '6m', + 'step': 'month', + 'stepmode': 'backward'}, + {'count': 1, + 'label': 'YTD', + 'step': 'year', + 'stepmode': 'todate'}, + {'count': 1, + 'label': '1y', + 'step': 'year', + 'stepmode': 'backward'}, + {'step': 'all'} + ]}, + 'showgrid': False, + 'type': 'date', + 'zeroline': False}, + 'yaxis': {'autorange': False, + 'range': [-1, 3], + 'showgrid': False, + 'ticktext': ['Job A', 'Job B'], + 'tickvals': [0, 1], + 'zeroline': False}} + } + + self.assertEqual(test_gantt_chart['data'][0], + exp_gantt_chart['data'][0]) + + self.assertEqual(test_gantt_chart['data'][1], + exp_gantt_chart['data'][1]) + + self.assertEqual(test_gantt_chart['layout'], + exp_gantt_chart['layout']) + class TestViolin(NumpyTestUtilsMixin, TestCase): diff --git a/plotly/tools.py b/plotly/tools.py index 7535ca06419..c2f12bc26c5 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -2093,6 +2093,34 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, # Plot the data py.iplot(fig, filename='dictioanry colors', world_readable=True) ``` + + Example 5: Use a pandas dataframe + ``` + import plotly.plotly as py + from plotly.tools import FigureFactory as FF + + # Make data as a dataframe + df = [{'Task': 'Run', + 'Start': '2010-01-01', + 'Finish': '2011-02-02', + 'Complete': 10}, + {'Task': 'Fast', + 'Start': '2011-01-01', + 'Finish': '2012-06-05', + 'Complete': 55}, + {'Task': 'Eat', + 'Start': '2012-01-05', + 'Finish': '2013-07-05', + 'Complete': 94}] + + # Create a figure with Plotly colorscale + fig = FF.create_gantt(df, colors='Blues', index_col='Complete', + show_colorbar=True, bar_width=0.5, + showgrid_x=True, showgrid_y=True) + + # Plot the data + py.iplot(fig, filename='data with dataframe', world_readable=True) + ``` """ # validate gantt input data chart = FigureFactory._validate_gantt(df) From ac64ff366a7da8cb00900e1f06c7252462b1710f Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Fri, 17 Jun 2016 17:27:13 -0400 Subject: [PATCH 19/27] added self.maxDiff=None --- plotly/tests/test_optional/test_figure_factory.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index 983e421f5f8..aca2f0c3ad4 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -1364,6 +1364,8 @@ def test_df_dataframe_all_args(self): self.assertEqual(test_gantt_chart['layout'], exp_gantt_chart['layout']) + self.maxDiff = None + class TestViolin(NumpyTestUtilsMixin, TestCase): From b28f040e783c450f2dc7a24dfc1b592c74052b77 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Sat, 18 Jun 2016 17:47:35 -0400 Subject: [PATCH 20/27] Switch to assertequaldict in test --- plotly/tests/test_optional/test_figure_factory.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index aca2f0c3ad4..3b5d3bb3437 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -1361,10 +1361,8 @@ def test_df_dataframe_all_args(self): self.assertEqual(test_gantt_chart['data'][1], exp_gantt_chart['data'][1]) - self.assertEqual(test_gantt_chart['layout'], - exp_gantt_chart['layout']) - - self.maxDiff = None + self.assert_dict_equal(test_gantt_chart['layout'], + exp_gantt_chart['layout']) class TestViolin(NumpyTestUtilsMixin, TestCase): From 866f5c988a0fc0519f9a4e6052a80c57207341c8 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Sat, 18 Jun 2016 18:06:39 -0400 Subject: [PATCH 21/27] Another one --- .../test_optional/test_figure_factory.py | 123 ++++++++---------- 1 file changed, 55 insertions(+), 68 deletions(-) diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index 3b5d3bb3437..53103fc69ad 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -1279,80 +1279,67 @@ def test_df_dataframe_all_args(self): # check if gantt chart matches with expected output - df = pd.DataFrame([['Job A', '2009-01-01', '2009-02-30', 'A'], - ['Job B', '2009-03-05', '2009-04-15', 'B']], - columns=['Task', 'Start', 'Finish', 'Resource']) + df = pd.DataFrame([['Job A', '2009-01-01', '2009-02-30'], + ['Job B', '2009-03-05', '2009-04-15']], + columns=['Task', 'Start', 'Finish']) test_gantt_chart = tls.FigureFactory.create_gantt( - df, colors='Blues', index_col='Resource' + df, colors='Blues' ) exp_gantt_chart = { + 'data': [{'marker': {'color': 'white'}, - 'name': '', - 'x': ['2009-01-01', '2009-02-30'], - 'y': [0, 0]}, - {'marker': {'color': 'white'}, - 'name': '', - 'x': ['2009-03-05', '2009-04-15'], - 'y': [1, 1]}], - 'layout': {'height': 600, - 'hovermode': 'closest', - 'shapes': [{'fillcolor': 'rgb(5.0, 10.0, 172.0)', - 'line': {'width': 0}, - 'opacity': 1, - 'type': 'rect', - 'x0': '2009-01-01', - 'x1': '2009-02-30', - 'xref': 'x', - 'y0': -0.2, - 'y1': 0.2, - 'yref': 'y'}, - {'fillcolor': 'rgb(220.0, 220.0, 220.0)', - 'line': {'width': 0}, - 'opacity': 1, - 'type': 'rect', - 'x0': '2009-03-05', - 'x1': '2009-04-15', - 'xref': 'x', - 'y0': 0.8, - 'y1': 1.2, - 'yref': 'y'}], - 'showlegend': False, - 'title': 'Gantt Chart', - 'width': 900, - 'xaxis': {'rangeselector': {'buttons': [ - {'count': 7, - 'label': '1w', - 'step': 'day', - 'stepmode': 'backward'}, - {'count': 1, - 'label': '1m', - 'step': 'month', - 'stepmode': 'backward'}, - {'count': 6, - 'label': '6m', - 'step': 'month', - 'stepmode': 'backward'}, - {'count': 1, - 'label': 'YTD', - 'step': 'year', - 'stepmode': 'todate'}, - {'count': 1, - 'label': '1y', - 'step': 'year', - 'stepmode': 'backward'}, - {'step': 'all'} - ]}, - 'showgrid': False, - 'type': 'date', - 'zeroline': False}, - 'yaxis': {'autorange': False, - 'range': [-1, 3], - 'showgrid': False, - 'ticktext': ['Job A', 'Job B'], - 'tickvals': [0, 1], - 'zeroline': False}} + 'name': '', + 'x': ['2009-01-01', '2009-02-30'], + 'y': [0, 0]}, + {'marker': {'color': 'white'}, + 'name': '', + 'x': ['2009-03-05', '2009-04-15'], + 'y': [1, 1]}], + 'layout': {'height': 600, + 'hovermode': 'closest', + 'shapes': [{'fillcolor': 'rgb(5.0, 10.0, 172.0)', + 'line': {'width': 0}, + 'opacity': 1, + 'type': 'rect', + 'x0': '2009-01-01', + 'x1': '2009-02-30', + 'xref': 'x', + 'y0': -0.2, + 'y1': 0.2, + 'yref': 'y'}, + {'fillcolor': 'rgb(220.0, 220.0, 220.0)', + 'line': {'width': 0}, + 'opacity': 1, + 'type': 'rect', + 'x0': '2009-03-05', + 'x1': '2009-04-15', + 'xref': 'x', + 'y0': 0.8, + 'y1': 1.2, + 'yref': 'y'}], + 'showlegend': False, + 'title': 'Gantt Chart', + 'width': 900, + 'xaxis': {'rangeselector': {'buttons': [{'count': 7, + 'label': '1w', + 'step': 'day', + 'stepmode': 'backward'}, + {'count': 1, 'label': '1m', 'step': 'month', 'stepmode': 'backward'}, + {'count': 6, 'label': '6m', 'step': 'month', 'stepmode': 'backward'}, + {'count': 1, 'label': 'YTD', 'step': 'year', 'stepmode': 'todate'}, + {'count': 1, 'label': '1y', 'step': 'year', 'stepmode': 'backward'}, + {'step': 'all'}]}, + 'showgrid': False, + 'type': 'date', + 'zeroline': False}, + 'yaxis': {'autorange': False, + 'range': [-1, 3], + 'showgrid': False, + 'ticktext': ['Job A', 'Job B'], + 'tickvals': [0, 1], + 'zeroline': False}} } self.assertEqual(test_gantt_chart['data'][0], From 2142397d7b4066cd2e8a5827668b18ad958b598f Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Sat, 18 Jun 2016 18:18:47 -0400 Subject: [PATCH 22/27] test 'shapes' in 'layout' only --- plotly/tests/test_optional/test_figure_factory.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index 53103fc69ad..c0493d4b446 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -1348,8 +1348,14 @@ def test_df_dataframe_all_args(self): self.assertEqual(test_gantt_chart['data'][1], exp_gantt_chart['data'][1]) - self.assert_dict_equal(test_gantt_chart['layout'], - exp_gantt_chart['layout']) + self.assert_dict_equal(test_gantt_chart['layout']['shapes'][0], + exp_gantt_chart['layout']['shapes'][0]) + + self.assert_dict_equal(test_gantt_chart['layout']['shapes'][1], + exp_gantt_chart['layout']['shapes'][1]) + +# self.assert_dict_equal(test_gantt_chart['layout'], +# exp_gantt_chart['layout']) class TestViolin(NumpyTestUtilsMixin, TestCase): From bfbe24c086e646009a11a5ae603f116b693cce5f Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Sat, 18 Jun 2016 18:23:54 -0400 Subject: [PATCH 23/27] just test data --- plotly/tests/test_optional/test_figure_factory.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index c0493d4b446..a79a6fae623 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -1348,14 +1348,14 @@ def test_df_dataframe_all_args(self): self.assertEqual(test_gantt_chart['data'][1], exp_gantt_chart['data'][1]) - self.assert_dict_equal(test_gantt_chart['layout']['shapes'][0], - exp_gantt_chart['layout']['shapes'][0]) + #self.assert_dict_equal(test_gantt_chart['layout']['shapes'][0], + # exp_gantt_chart['layout']['shapes'][0]) - self.assert_dict_equal(test_gantt_chart['layout']['shapes'][1], - exp_gantt_chart['layout']['shapes'][1]) + #self.assert_dict_equal(test_gantt_chart['layout']['shapes'][1], + # exp_gantt_chart['layout']['shapes'][1]) -# self.assert_dict_equal(test_gantt_chart['layout'], -# exp_gantt_chart['layout']) + #self.assert_dict_equal(test_gantt_chart['layout'], + # exp_gantt_chart['layout']) class TestViolin(NumpyTestUtilsMixin, TestCase): From afd45a10bf9f042b87d4fd5fae3c4a51e10c0145 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Sat, 18 Jun 2016 18:38:18 -0400 Subject: [PATCH 24/27] Redo test --- .../test_optional/test_figure_factory.py | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index a79a6fae623..ba937891aef 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -1292,14 +1292,10 @@ def test_df_dataframe_all_args(self): 'data': [{'marker': {'color': 'white'}, 'name': '', 'x': ['2009-01-01', '2009-02-30'], - 'y': [0, 0]}, - {'marker': {'color': 'white'}, - 'name': '', - 'x': ['2009-03-05', '2009-04-15'], - 'y': [1, 1]}], + 'y': [0, 0]}], 'layout': {'height': 600, 'hovermode': 'closest', - 'shapes': [{'fillcolor': 'rgb(5.0, 10.0, 172.0)', + 'shapes': [{'fillcolor': 'rgb(31.0, 119.0, 180.0)', 'line': {'width': 0}, 'opacity': 1, 'type': 'rect', @@ -1308,16 +1304,6 @@ def test_df_dataframe_all_args(self): 'xref': 'x', 'y0': -0.2, 'y1': 0.2, - 'yref': 'y'}, - {'fillcolor': 'rgb(220.0, 220.0, 220.0)', - 'line': {'width': 0}, - 'opacity': 1, - 'type': 'rect', - 'x0': '2009-03-05', - 'x1': '2009-04-15', - 'xref': 'x', - 'y0': 0.8, - 'y1': 1.2, 'yref': 'y'}], 'showlegend': False, 'title': 'Gantt Chart', @@ -1335,11 +1321,12 @@ def test_df_dataframe_all_args(self): 'type': 'date', 'zeroline': False}, 'yaxis': {'autorange': False, - 'range': [-1, 3], + 'range': [-1, 2], 'showgrid': False, - 'ticktext': ['Job A', 'Job B'], - 'tickvals': [0, 1], + 'ticktext': ['Job A'], + 'tickvals': [0], 'zeroline': False}} + } self.assertEqual(test_gantt_chart['data'][0], @@ -1354,8 +1341,8 @@ def test_df_dataframe_all_args(self): #self.assert_dict_equal(test_gantt_chart['layout']['shapes'][1], # exp_gantt_chart['layout']['shapes'][1]) - #self.assert_dict_equal(test_gantt_chart['layout'], - # exp_gantt_chart['layout']) + self.assert_dict_equal(test_gantt_chart['layout'], + exp_gantt_chart['layout']) class TestViolin(NumpyTestUtilsMixin, TestCase): From 3ac0da43a9f534298a0677a3558959187a8d72e0 Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Sat, 18 Jun 2016 18:52:05 -0400 Subject: [PATCH 25/27] try again --- plotly/tests/test_optional/test_figure_factory.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index ba937891aef..870553ed7c7 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -1283,9 +1283,7 @@ def test_df_dataframe_all_args(self): ['Job B', '2009-03-05', '2009-04-15']], columns=['Task', 'Start', 'Finish']) - test_gantt_chart = tls.FigureFactory.create_gantt( - df, colors='Blues' - ) + test_gantt_chart = tls.FigureFactory.create_gantt(df) exp_gantt_chart = { @@ -1332,15 +1330,6 @@ def test_df_dataframe_all_args(self): self.assertEqual(test_gantt_chart['data'][0], exp_gantt_chart['data'][0]) - self.assertEqual(test_gantt_chart['data'][1], - exp_gantt_chart['data'][1]) - - #self.assert_dict_equal(test_gantt_chart['layout']['shapes'][0], - # exp_gantt_chart['layout']['shapes'][0]) - - #self.assert_dict_equal(test_gantt_chart['layout']['shapes'][1], - # exp_gantt_chart['layout']['shapes'][1]) - self.assert_dict_equal(test_gantt_chart['layout'], exp_gantt_chart['layout']) From 0ab3ad1af8c067b040f48c0c4865b3366e00bf1c Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Sat, 18 Jun 2016 19:02:13 -0400 Subject: [PATCH 26/27] again --- .../tests/test_optional/test_figure_factory.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index 870553ed7c7..3bf814d7733 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -1293,16 +1293,7 @@ def test_df_dataframe_all_args(self): 'y': [0, 0]}], 'layout': {'height': 600, 'hovermode': 'closest', - 'shapes': [{'fillcolor': 'rgb(31.0, 119.0, 180.0)', - 'line': {'width': 0}, - 'opacity': 1, - 'type': 'rect', - 'x0': '2009-01-01', - 'x1': '2009-02-30', - 'xref': 'x', - 'y0': -0.2, - 'y1': 0.2, - 'yref': 'y'}], + 'shapes': [{'opacity': 1, 'y1': 0.2, 'xref': 'x', 'fillcolor': 'rgb(31.0, 119.0, 180.0)', 'yref': 'y', 'y0': -0.2, 'x0': '2009-01-01', 'x1': '2009-02-30', 'type': 'rect', 'line': {'width': 0}}, {'opacity': 1, 'y1': 1.2, 'xref': 'x', 'fillcolor': 'rgb(255.0, 127.0, 14.0)', 'yref': 'y', 'y0': 0.8, 'x0': '2009-03-05', 'x1': '2009-04-15', 'type': 'rect', 'line': {'width': 0}}], 'showlegend': False, 'title': 'Gantt Chart', 'width': 900, @@ -1319,10 +1310,10 @@ def test_df_dataframe_all_args(self): 'type': 'date', 'zeroline': False}, 'yaxis': {'autorange': False, - 'range': [-1, 2], + 'range': [-1, 3], 'showgrid': False, - 'ticktext': ['Job A'], - 'tickvals': [0], + 'ticktext': ['Job A', 'Job B'], + 'tickvals': [0, 1], 'zeroline': False}} } From a7be116cd90ad84b41629bfc6af86c32c80b4fde Mon Sep 17 00:00:00 2001 From: Adam Kulidjian Date: Sat, 18 Jun 2016 19:26:38 -0400 Subject: [PATCH 27/27] Fixed formatting --- .../test_optional/test_figure_factory.py | 79 ++++++++++++------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/plotly/tests/test_optional/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory.py index 3bf814d7733..c2a0f3da624 100644 --- a/plotly/tests/test_optional/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory.py @@ -1286,36 +1286,57 @@ def test_df_dataframe_all_args(self): test_gantt_chart = tls.FigureFactory.create_gantt(df) exp_gantt_chart = { - 'data': [{'marker': {'color': 'white'}, - 'name': '', - 'x': ['2009-01-01', '2009-02-30'], - 'y': [0, 0]}], - 'layout': {'height': 600, - 'hovermode': 'closest', - 'shapes': [{'opacity': 1, 'y1': 0.2, 'xref': 'x', 'fillcolor': 'rgb(31.0, 119.0, 180.0)', 'yref': 'y', 'y0': -0.2, 'x0': '2009-01-01', 'x1': '2009-02-30', 'type': 'rect', 'line': {'width': 0}}, {'opacity': 1, 'y1': 1.2, 'xref': 'x', 'fillcolor': 'rgb(255.0, 127.0, 14.0)', 'yref': 'y', 'y0': 0.8, 'x0': '2009-03-05', 'x1': '2009-04-15', 'type': 'rect', 'line': {'width': 0}}], - 'showlegend': False, - 'title': 'Gantt Chart', - 'width': 900, - 'xaxis': {'rangeselector': {'buttons': [{'count': 7, - 'label': '1w', - 'step': 'day', - 'stepmode': 'backward'}, - {'count': 1, 'label': '1m', 'step': 'month', 'stepmode': 'backward'}, - {'count': 6, 'label': '6m', 'step': 'month', 'stepmode': 'backward'}, - {'count': 1, 'label': 'YTD', 'step': 'year', 'stepmode': 'todate'}, - {'count': 1, 'label': '1y', 'step': 'year', 'stepmode': 'backward'}, - {'step': 'all'}]}, - 'showgrid': False, - 'type': 'date', - 'zeroline': False}, - 'yaxis': {'autorange': False, - 'range': [-1, 3], - 'showgrid': False, - 'ticktext': ['Job A', 'Job B'], - 'tickvals': [0, 1], - 'zeroline': False}} - + 'name': '', + 'x': ['2009-01-01', '2009-02-30'], + 'y': [0, 0]}], + 'layout': {'height': 600, + 'hovermode': 'closest', + 'shapes': [{'opacity': 1, + 'y1': 0.2, + 'xref': 'x', + 'fillcolor': 'rgb(31.0, 119.0, 180.0)', + 'yref': 'y', + 'y0': -0.2, + 'x0': '2009-01-01', + 'x1': '2009-02-30', + 'type': 'rect', + 'line': {'width': 0}}, + {'opacity': 1, + 'y1': 1.2, + 'xref': 'x', + 'fillcolor': 'rgb(255.0, 127.0, 14.0)', + 'yref': 'y', + 'y0': 0.8, + 'x0': '2009-03-05', + 'x1': '2009-04-15', + 'type': 'rect', + 'line': {'width': 0}}], + 'showlegend': False, + 'title': 'Gantt Chart', + 'width': 900, + 'xaxis': {'rangeselector': {'buttons': [ + {'count': 7, 'label': '1w', + 'step': 'day', 'stepmode': 'backward'}, + {'count': 1, 'label': '1m', + 'step': 'month', 'stepmode': 'backward'}, + {'count': 6, 'label': '6m', + 'step': 'month', 'stepmode': 'backward'}, + {'count': 1, 'label': 'YTD', + 'step': 'year', 'stepmode': 'todate'}, + {'count': 1, 'label': '1y', + 'step': 'year', 'stepmode': 'backward'}, + {'step': 'all'} + ]}, + 'showgrid': False, + 'type': 'date', + 'zeroline': False}, + 'yaxis': {'autorange': False, + 'range': [-1, 3], + 'showgrid': False, + 'ticktext': ['Job A', 'Job B'], + 'tickvals': [0, 1], + 'zeroline': False}} } self.assertEqual(test_gantt_chart['data'][0],