From d79605df2402ac3970adeb131444690f6985cfc5 Mon Sep 17 00:00:00 2001 From: Andreas Schmitz Date: Fri, 21 Oct 2016 12:56:33 +0200 Subject: [PATCH 1/5] [tools.py] Gantt Chart: added an option which allows grouping of tasks --- plotly/tools.py | 120 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 91 insertions(+), 29 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index e8af25dc5d2..b421725c302 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1724,7 +1724,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, group_tasks=False): """ Refer to FigureFactory.create_gantt() for docstring """ @@ -1751,15 +1751,30 @@ def _gantt(chart, colors, title, bar_width, showgrid_x, showgrid_y, }, 'yref': 'y', } + # create the list of task names + for index in range(len(tasks)): + tn = tasks[index]['name'] + # Is added to task_names if group_tasks is set to False, + # or if the option is used (True) it only adds them if the + # name is not already in the list + if not group_tasks or tn not in task_names: + task_names.append(tn) + task_names.reverse() + 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 + + # If group_tasks is True, all tasks with the same name belong + # to the same row. + groupID = index + if group_tasks: + groupID = task_names.index(tn) + tasks[index]['y0'] = groupID - bar_width + tasks[index]['y1'] = groupID + bar_width # check if colors need to be looped if color_index >= len(colors): @@ -1769,7 +1784,7 @@ def _gantt(chart, colors, title, bar_width, showgrid_x, showgrid_y, data.append( dict( x=[tasks[index]['x0'], tasks[index]['x1']], - y=[index, index], + y=[groupID, groupID], name='', marker={'color': 'white'} ) @@ -1786,8 +1801,8 @@ def _gantt(chart, colors, title, bar_width, showgrid_x, showgrid_y, yaxis=dict( showgrid=showgrid_y, ticktext=task_names, - tickvals=list(range(len(tasks))), - range=[-1, len(tasks) + 1], + tickvals=list(range(len(task_names))), + range=[-1, len(task_names) + 1], autorange=False, zeroline=False, ), @@ -1830,7 +1845,7 @@ def _gantt(chart, colors, title, bar_width, showgrid_x, showgrid_y, @staticmethod 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): + width, tasks=None, task_names=None, data=None, group_tasks=False): """ Refer to FigureFactory.create_gantt() for docstring """ @@ -1870,13 +1885,29 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, "colors given will be used for the lower and upper " "bounds on the colormap." ) + + # create the list of task names + for index in range(len(tasks)): + tn = tasks[index]['name'] + # Is added to task_names if group_tasks is set to False, + # or if the option is used (True) it only adds them if the + # name is not already in the list + if not group_tasks or tn not in task_names: + task_names.append(tn) + task_names.reverse() + 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 + + # If group_tasks is True, all tasks with the same name belong + # to the same row. + groupID = index + if group_tasks: + groupID = task_names.index(tn) + tasks[index]['y0'] = groupID - bar_width + tasks[index]['y1'] = groupID + bar_width # unlabel color colors = FigureFactory._color_parser( @@ -1902,7 +1933,7 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, data.append( dict( x=[tasks[index]['x0'], tasks[index]['x1']], - y=[index, index], + y=[groupID, groupID], name='', marker={'color': 'white'} ) @@ -1948,13 +1979,27 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, index_vals_dict[key] = colors[c_index] c_index += 1 + # create the list of task names + for index in range(len(tasks)): + tn = tasks[index]['name'] + # Is added to task_names if group_tasks is set to False, + # or if the option is used (True) it only adds them if the + # name is not already in the list + if not group_tasks or tn not in task_names: + task_names.append(tn) + task_names.reverse() + 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 + # If group_tasks is True, all tasks with the same name belong + # to the same row. + groupID = index + if group_tasks: + groupID = task_names.index(tn) + tasks[index]['y0'] = groupID - bar_width + tasks[index]['y1'] = groupID + bar_width tasks[index]['fillcolor'] = index_vals_dict[ chart[index][index_col] @@ -1964,7 +2009,7 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, data.append( dict( x=[tasks[index]['x0'], tasks[index]['x1']], - y=[index, index], + y=[groupID, groupID], name='', marker={'color': 'white'} ) @@ -1998,8 +2043,8 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, yaxis=dict( showgrid=showgrid_y, ticktext=task_names, - tickvals=list(range(len(tasks))), - range=[-1, len(tasks) + 1], + tickvals=list(range(len(task_names))), + range=[-1, len(task_names) + 1], autorange=False, zeroline=False, ), @@ -2042,7 +2087,7 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, @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): + task_names=None, data=None, group_tasks=False): """ Refer to FigureFactory.create_gantt() for docstring """ @@ -2086,13 +2131,28 @@ def _gantt_dict(chart, colors, title, index_col, show_colorbar, bar_width, "keys must be all the values in the index column." ) + # create the list of task names + for index in range(len(tasks)): + tn = tasks[index]['name'] + # Is added to task_names if group_tasks is set to False, + # or if the option is used (True) it only adds them if the + # name is not already in the list + if not group_tasks or tn not in task_names: + task_names.append(tn) + task_names.reverse() + 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 + + # If group_tasks is True, all tasks with the same name belong + # to the same row. + groupID = index + if group_tasks: + groupID = task_names.index(tn) + tasks[index]['y0'] = groupID - bar_width + tasks[index]['y1'] = groupID + bar_width tasks[index]['fillcolor'] = colors[chart[index][index_col]] @@ -2100,7 +2160,7 @@ def _gantt_dict(chart, colors, title, index_col, show_colorbar, bar_width, data.append( dict( x=[tasks[index]['x0'], tasks[index]['x1']], - y=[index, index], + y=[groupID, groupID], name='', marker={'color': 'white'} ) @@ -2134,8 +2194,8 @@ def _gantt_dict(chart, colors, title, index_col, show_colorbar, bar_width, yaxis=dict( showgrid=showgrid_y, ticktext=task_names, - tickvals=list(range(len(tasks))), - range=[-1, len(tasks) + 1], + tickvals=list(range(len(task_names))), + range=[-1, len(task_names) + 1], autorange=False, zeroline=False, ), @@ -2180,7 +2240,7 @@ 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, - task_names=None, data=None): + task_names=None, data=None, group_tasks=False): """ Returns figure for a gantt chart @@ -2359,7 +2419,8 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, ) fig = FigureFactory._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, + group_tasks=group_tasks ) return fig else: @@ -2367,14 +2428,14 @@ def create_gantt(df, colors=None, index_col=None, show_colorbar=False, 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 + tasks=None, task_names=None, data=None, group_tasks=group_tasks ) 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 + tasks=None, task_names=None, data=None, group_tasks=group_tasks ) return fig @@ -2403,6 +2464,7 @@ def _validate_colors(colors, colortype='tuple'): else: colors = list(colors) + # convert color elements in list to tuple color for j, each_color in enumerate(colors): if 'rgb' in each_color: From f7eaefa21a789172f685467407e8e413aebb0b6b Mon Sep 17 00:00:00 2001 From: Andreas Schmitz Date: Fri, 21 Oct 2016 13:19:22 +0200 Subject: [PATCH 2/5] [tools.py] Gantt Chart: added code to allow for description texts in Gantt chart tasks --- plotly/tools.py | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index b421725c302..df83aa43382 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1739,6 +1739,8 @@ def _gantt(chart, colors, title, bar_width, showgrid_x, showgrid_y, task = dict(x0=chart[index]['Start'], x1=chart[index]['Finish'], name=chart[index]['Task']) + if 'Description' in chart[index]: + task['description'] = chart[index]['Description'] tasks.append(task) shape_template = { @@ -1781,14 +1783,16 @@ def _gantt(chart, colors, title, bar_width, showgrid_x, showgrid_y, color_index = 0 tasks[index]['fillcolor'] = colors[color_index] # Add a line for hover text and autorange - data.append( - dict( + entry = dict( x=[tasks[index]['x0'], tasks[index]['x1']], y=[groupID, groupID], name='', marker={'color': 'white'} ) - ) + if "description" in tasks[index]: + entry['text'] = tasks[index]['description'] + del tasks[index]['description'] + data.append(entry) color_index += 1 layout = dict( @@ -1862,6 +1866,8 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, task = dict(x0=chart[index]['Start'], x1=chart[index]['Finish'], name=chart[index]['Task']) + if 'Description' in chart[index]: + task['description'] = chart[index]['Description'] tasks.append(task) shape_template = { @@ -1930,14 +1936,17 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, ) # add a line for hover text and autorange - data.append( - dict( + entry = dict( x=[tasks[index]['x0'], tasks[index]['x1']], y=[groupID, groupID], name='', marker={'color': 'white'} ) - ) + if "description" in tasks[index]: + entry['text'] = tasks[index]['description'] + del tasks[index]['description'] + data.append(entry) + if show_colorbar is True: # generate dummy data for colorscale visibility @@ -2006,14 +2015,16 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, ] # add a line for hover text and autorange - data.append( - dict( + entry = dict( x=[tasks[index]['x0'], tasks[index]['x1']], y=[groupID, groupID], name='', marker={'color': 'white'} ) - ) + if "description" in tasks[index]: + entry['text'] = tasks[index]['description'] + del tasks[index]['description'] + data.append(entry) if show_colorbar is True: # generate dummy data to generate legend @@ -2103,6 +2114,8 @@ def _gantt_dict(chart, colors, title, index_col, show_colorbar, bar_width, task = dict(x0=chart[index]['Start'], x1=chart[index]['Finish'], name=chart[index]['Task']) + if 'Description' in chart[index]: + task['description'] = chart[index]['Description'] tasks.append(task) shape_template = { @@ -2157,14 +2170,16 @@ def _gantt_dict(chart, colors, title, index_col, show_colorbar, bar_width, tasks[index]['fillcolor'] = colors[chart[index][index_col]] # add a line for hover text and autorange - data.append( - dict( + entry = dict( x=[tasks[index]['x0'], tasks[index]['x1']], y=[groupID, groupID], name='', marker={'color': 'white'} ) - ) + if "description" in tasks[index]: + entry['text'] = tasks[index]['description'] + del tasks[index]['description'] + data.append(entry) if show_colorbar is True: # generate dummy data to generate legend From dae49b1b5b69b54511a8e1c5b8c4ec25d651562b Mon Sep 17 00:00:00 2001 From: Andreas Schmitz Date: Fri, 21 Oct 2016 13:29:21 +0200 Subject: [PATCH 3/5] [tools.py] Gannt Chart: fixed a problem with test: test_gantt_all_args --- plotly/tools.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index df83aa43382..e52a9a93b72 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1761,7 +1761,10 @@ def _gantt(chart, colors, title, bar_width, showgrid_x, showgrid_y, # name is not already in the list if not group_tasks or tn not in task_names: task_names.append(tn) - task_names.reverse() + # Gurantees that for grouped tasks the tasks that are inserted first + # are shown at the top + if group_tasks: + task_names.reverse() color_index = 0 @@ -1900,7 +1903,10 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, # name is not already in the list if not group_tasks or tn not in task_names: task_names.append(tn) - task_names.reverse() + # Gurantees that for grouped tasks the tasks that are inserted first + # are shown at the top + if group_tasks: + task_names.reverse() for index in range(len(tasks)): tn = tasks[index]['name'] @@ -1996,7 +2002,10 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, # name is not already in the list if not group_tasks or tn not in task_names: task_names.append(tn) - task_names.reverse() + # Gurantees that for grouped tasks the tasks that are inserted first + # are shown at the top + if group_tasks: + task_names.reverse() for index in range(len(tasks)): tn = tasks[index]['name'] @@ -2152,7 +2161,10 @@ def _gantt_dict(chart, colors, title, index_col, show_colorbar, bar_width, # name is not already in the list if not group_tasks or tn not in task_names: task_names.append(tn) - task_names.reverse() + # Gurantees that for grouped tasks the tasks that are inserted first + # are shown at the top + if group_tasks: + task_names.reverse() for index in range(len(tasks)): tn = tasks[index]['name'] From 61df27d7455d1363ecb5d098692a4e607ef3f482 Mon Sep 17 00:00:00 2001 From: Andreas Schmitz Date: Fri, 21 Oct 2016 13:46:50 +0200 Subject: [PATCH 4/5] [tools.py] Gannt Chart: added a dedicated test for the grouping and description option --- .../test_tools/test_figure_factory.py | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) 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 4fb488bfb0f..9ebc68c55f7 100644 --- a/plotly/tests/test_core/test_tools/test_figure_factory.py +++ b/plotly/tests/test_core/test_tools/test_figure_factory.py @@ -1269,6 +1269,181 @@ def test_gantt_validate_colors(self): index_col='Complete', colors=['#ffffff']) + + def test_gannt_groups_and_descriptions(self): + + # check if grouped gantt chart matches with expected output + + df = [ + dict(Task='Task A', Description='Task A - 1', Start='2008-10-05', + Finish='2009-04-15', IndexCol = 'TA'), + dict(Task="Task B", Description='Task B - 1', Start='2008-12-06', + Finish='2009-03-15', IndexCol = 'TB'), + dict(Task="Task C", Description='Task C - 1', Start='2008-09-07', + Finish='2009-03-15', IndexCol = 'TC'), + dict(Task="Task C", Description='Task C - 2', Start='2009-05-08', + Finish='2009-04-15', IndexCol = 'TC'), + dict(Task="Task A", Description='Task A - 2', Start='2009-04-20', + Finish='2009-05-30', IndexCol = 'TA') + ] + + test_gantt_chart = tls.FigureFactory.create_gantt( + df, colors=dict(TA='rgb(220, 0, 0)', TB='rgb(170, 14, 200)', + TC=(1, 0.9, 0.16)), show_colorbar=True, index_col='IndexCol', + group_tasks=True + ) + + exp_gantt_chart = { + 'data': [{'marker': {'color': 'white'}, + 'name': '', + 'text': 'Task A - 1', + 'x': ['2008-10-05', '2009-04-15'], + 'y': [2, 2]}, + {'marker': {'color': 'white'}, + 'name': '', + 'text': 'Task B - 1', + 'x': ['2008-12-06', '2009-03-15'], + 'y': [1, 1]}, + {'marker': {'color': 'white'}, + 'name': '', + 'text': 'Task C - 1', + 'x': ['2008-09-07', '2009-03-15'], + 'y': [0, 0]}, + {'marker': {'color': 'white'}, + 'name': '', + 'text': 'Task C - 2', + 'x': ['2009-05-08', '2009-04-15'], + 'y': [0, 0]}, + {'marker': {'color': 'white'}, + 'name': '', + 'text': 'Task A - 2', + 'x': ['2009-04-20', '2009-05-30'], + 'y': [2, 2]}, + {'hoverinfo': 'none', + 'marker': {'color': 'rgb(220, 0, 0)', 'size': 1}, + 'name': 'TA', + 'showlegend': True, + 'x': ['2009-04-20', '2009-04-20'], + 'y': [0, 0]}, + {'hoverinfo': 'none', + 'marker': {'color': 'rgb(170, 14, 200)', 'size': 1}, + 'name': 'TB', + 'showlegend': True, + 'x': ['2009-04-20', '2009-04-20'], + 'y': [1, 1]}, + {'hoverinfo': 'none', + 'marker': {'color': 'rgb(255, 230, 41)', 'size': 1}, + 'name': 'TC', + 'showlegend': True, + 'x': ['2009-04-20', '2009-04-20'], + 'y': [2, 2]}], + 'layout': {'height': 600, + 'hovermode': 'closest', + 'shapes': [{'fillcolor': 'rgb(220, 0, 0)', + 'line': {'width': 0}, + 'opacity': 1, + 'type': 'rect', + 'x0': '2008-10-05', + 'x1': '2009-04-15', + 'xref': 'x', + 'y0': 1.8, + 'y1': 2.2, + 'yref': 'y'}, + {'fillcolor': 'rgb(170, 14, 200)', + 'line': {'width': 0}, + 'opacity': 1, + 'type': 'rect', + 'x0': '2008-12-06', + 'x1': '2009-03-15', + 'xref': 'x', + 'y0': 0.8, + 'y1': 1.2, + 'yref': 'y'}, + {'fillcolor': 'rgb(255, 230, 41)', + 'line': {'width': 0}, + 'opacity': 1, + 'type': 'rect', + 'x0': '2008-09-07', + 'x1': '2009-03-15', + 'xref': 'x', + 'y0': -0.2, + 'y1': 0.2, + 'yref': 'y'}, + {'fillcolor': 'rgb(255, 230, 41)', + 'line': {'width': 0}, + 'opacity': 1, + 'type': 'rect', + 'x0': '2009-05-08', + 'x1': '2009-04-15', + 'xref': 'x', + 'y0': -0.2, + 'y1': 0.2, + 'yref': 'y'}, + {'fillcolor': 'rgb(220, 0, 0)', + 'line': {'width': 0}, + 'opacity': 1, + 'type': 'rect', + 'x0': '2009-04-20', + 'x1': '2009-05-30', + 'xref': 'x', + 'y0': 1.8, + 'y1': 2.2, + 'yref': 'y'}], + 'showlegend': True, + '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, 4], + 'showgrid': False, + 'ticktext': ['Task C', 'Task B', 'Task A'], + 'tickvals': [0, 1, 2], + '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['data'][2], + exp_gantt_chart['data'][2]) + + self.assertEqual(test_gantt_chart['data'][3], + exp_gantt_chart['data'][3]) + + self.assertEqual(test_gantt_chart['data'][4], + exp_gantt_chart['data'][4]) + + self.assertEqual(test_gantt_chart['layout'], + exp_gantt_chart['layout']) + + + def test_gantt_all_args(self): # check if gantt chart matches with expected output From 43f3f9a209479ec52ddae39eef80603c2e5e6a54 Mon Sep 17 00:00:00 2001 From: Andreas Schmitz Date: Fri, 21 Oct 2016 13:52:49 +0200 Subject: [PATCH 5/5] [tools.py] Gannt Chart: Typo fixes --- plotly/tools.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plotly/tools.py b/plotly/tools.py index e52a9a93b72..30d8dba0238 100644 --- a/plotly/tools.py +++ b/plotly/tools.py @@ -1761,7 +1761,7 @@ def _gantt(chart, colors, title, bar_width, showgrid_x, showgrid_y, # name is not already in the list if not group_tasks or tn not in task_names: task_names.append(tn) - # Gurantees that for grouped tasks the tasks that are inserted first + # Guarantees that for grouped tasks the tasks that are inserted first # are shown at the top if group_tasks: task_names.reverse() @@ -1903,8 +1903,8 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, # name is not already in the list if not group_tasks or tn not in task_names: task_names.append(tn) - # Gurantees that for grouped tasks the tasks that are inserted first - # are shown at the top + # Guarantees that for grouped tasks the tasks that are inserted + # first are shown at the top if group_tasks: task_names.reverse() @@ -2002,8 +2002,8 @@ def _gantt_colorscale(chart, colors, title, index_col, show_colorbar, # name is not already in the list if not group_tasks or tn not in task_names: task_names.append(tn) - # Gurantees that for grouped tasks the tasks that are inserted first - # are shown at the top + # Guarantees that for grouped tasks the tasks that are inserted + # first are shown at the top if group_tasks: task_names.reverse() @@ -2161,7 +2161,7 @@ def _gantt_dict(chart, colors, title, index_col, show_colorbar, bar_width, # name is not already in the list if not group_tasks or tn not in task_names: task_names.append(tn) - # Gurantees that for grouped tasks the tasks that are inserted first + # Guarantees that for grouped tasks the tasks that are inserted first # are shown at the top if group_tasks: task_names.reverse()