diff --git a/packages/python/plotly/plotly/figure_factory/_gantt.py b/packages/python/plotly/plotly/figure_factory/_gantt.py index dda8177c3e0..ca0a182f766 100644 --- a/packages/python/plotly/plotly/figure_factory/_gantt.py +++ b/packages/python/plotly/plotly/figure_factory/_gantt.py @@ -2,16 +2,32 @@ from numbers import Number +import copy + from plotly import exceptions, optional_imports import plotly.colors as clrs from plotly.figure_factory import utils -from plotly.graph_objs import graph_objs +import plotly.graph_objects as go pd = optional_imports.get_module("pandas") REQUIRED_GANTT_KEYS = ["Task", "Start", "Finish"] +def _get_corner_points(x0, y0, x1, y1): + """ + Returns the corner points of a scatter rectangle + + :param x0: x-start + :param y0: y-lower + :param x1: x-end + :param y1: y-upper + :return: ([x], [y]), tuple of lists containing the x and y values + """ + + return ([x0, x1, x1, x0], [y0, y0, y1, y1]) + + def validate_gantt(df): """ Validates the inputted dataframe or list @@ -64,6 +80,8 @@ def gantt( task_names=None, data=None, group_tasks=False, + show_hover_fill=True, + show_colorbar=True, ): """ Refer to create_gantt() for docstring @@ -85,13 +103,33 @@ def gantt( task["description"] = chart[index]["Description"] tasks.append(task) - shape_template = { - "type": "rect", - "xref": "x", - "yref": "y", - "opacity": 1, - "line": {"width": 0}, + # create a scatter trace for every task group + scatter_data_dict = dict() + marker_data_dict = dict() + + if show_hover_fill: + hoverinfo = "name" + else: + hoverinfo = "skip" + + scatter_data_template = { + "x": [], + "y": [], + "mode": "none", + "fill": "toself", + "hoverinfo": hoverinfo, + } + + marker_data_template = { + "x": [], + "y": [], + "mode": "markers", + "text": [], + "marker": dict(color="", size=1, opacity=0), + "name": "", + "showlegend": False, } + # create the list of task names for index in range(len(tasks)): tn = tasks[index]["name"] @@ -109,7 +147,6 @@ def gantt( for index in range(len(tasks)): tn = tasks[index]["name"] del tasks[index]["name"] - tasks[index].update(shape_template) # If group_tasks is True, all tasks with the same name belong # to the same row. @@ -123,22 +160,59 @@ def gantt( if color_index >= len(colors): color_index = 0 tasks[index]["fillcolor"] = colors[color_index] - # Add a line for hover text and autorange - entry = dict( - x=[tasks[index]["x0"], tasks[index]["x1"]], - y=[groupID, groupID], - name="", - marker={"color": "white"}, + color_id = tasks[index]["fillcolor"] + + if color_id not in scatter_data_dict: + scatter_data_dict[color_id] = copy.deepcopy(scatter_data_template) + + scatter_data_dict[color_id]["fillcolor"] = color_id + scatter_data_dict[color_id]["name"] = str(tn) + scatter_data_dict[color_id]["legendgroup"] = color_id + + # if there are already values append the gap + if len(scatter_data_dict[color_id]["x"]) > 0: + # a gap on the scatterplot separates the rectangles from each other + scatter_data_dict[color_id]["x"].append( + scatter_data_dict[color_id]["x"][-1] + ) + scatter_data_dict[color_id]["y"].append(None) + + xs, ys = _get_corner_points( + tasks[index]["x0"], + tasks[index]["y0"], + tasks[index]["x1"], + tasks[index]["y1"], ) + + scatter_data_dict[color_id]["x"] += xs + scatter_data_dict[color_id]["y"] += ys + + # append dummy markers for showing start and end of interval + if color_id not in marker_data_dict: + marker_data_dict[color_id] = copy.deepcopy(marker_data_template) + marker_data_dict[color_id]["marker"]["color"] = color_id + marker_data_dict[color_id]["legendgroup"] = color_id + + marker_data_dict[color_id]["x"].append(tasks[index]["x0"]) + marker_data_dict[color_id]["x"].append(tasks[index]["x1"]) + marker_data_dict[color_id]["y"].append(groupID) + marker_data_dict[color_id]["y"].append(groupID) + if "description" in tasks[index]: - entry["text"] = tasks[index]["description"] + marker_data_dict[color_id]["text"].append(tasks[index]["description"]) + marker_data_dict[color_id]["text"].append(tasks[index]["description"]) del tasks[index]["description"] - data.append(entry) + else: + marker_data_dict[color_id]["text"].append(None) + marker_data_dict[color_id]["text"].append(None) + color_index += 1 + showlegend = show_colorbar + layout = dict( title=title, - showlegend=False, + showlegend=showlegend, height=height, width=width, shapes=[], @@ -169,9 +243,14 @@ def gantt( type="date", ), ) - layout["shapes"] = tasks - fig = graph_objs.Figure(data=data, layout=layout) + data = [scatter_data_dict[k] for k in sorted(scatter_data_dict)] + data += [marker_data_dict[k] for k in sorted(marker_data_dict)] + + # fig = dict( + # data=data, layout=layout + # ) + fig = go.Figure(data=data, layout=layout) return fig @@ -190,6 +269,7 @@ def gantt_colorscale( task_names=None, data=None, group_tasks=False, + show_hover_fill=True, ): """ Refer to FigureFactory.create_gantt() for docstring @@ -212,14 +292,44 @@ def gantt_colorscale( task["description"] = chart[index]["Description"] tasks.append(task) - shape_template = { - "type": "rect", - "xref": "x", - "yref": "y", - "opacity": 1, - "line": {"width": 0}, + # create a scatter trace for every task group + scatter_data_dict = dict() + # create scatter traces for the start- and endpoints + marker_data_dict = dict() + + if show_hover_fill: + hoverinfo = "name" + else: + hoverinfo = "skip" + + scatter_data_template = { + "x": [], + "y": [], + "mode": "none", + "fill": "toself", + "showlegend": False, + "hoverinfo": hoverinfo, + "legendgroup": "", + } + + marker_data_template = { + "x": [], + "y": [], + "mode": "markers", + "text": [], + "marker": dict(color="", size=1, opacity=0), + "name": "", + "showlegend": False, + "legendgroup": "", } + 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() + # compute the color for task based on indexing column if isinstance(chart[0][index_col], Number): # check that colors has at least 2 colors @@ -247,7 +357,6 @@ def gantt_colorscale( for index in range(len(tasks)): tn = tasks[index]["name"] del tasks[index]["name"] - tasks[index].update(shape_template) # If group_tasks is True, all tasks with the same name belong # to the same row. @@ -266,35 +375,64 @@ def gantt_colorscale( intermed_color = clrs.find_intermediate_color(lowcolor, highcolor, intermed) intermed_color = clrs.color_parser(intermed_color, clrs.label_rgb) tasks[index]["fillcolor"] = intermed_color + color_id = tasks[index]["fillcolor"] + + if color_id not in scatter_data_dict: + scatter_data_dict[color_id] = copy.deepcopy(scatter_data_template) + + scatter_data_dict[color_id]["fillcolor"] = color_id + scatter_data_dict[color_id]["name"] = str(chart[index][index_col]) + scatter_data_dict[color_id]["legendgroup"] = color_id + # relabel colors with 'rgb' colors = clrs.color_parser(colors, clrs.label_rgb) - # add a line for hover text and autorange - entry = dict( - x=[tasks[index]["x0"], tasks[index]["x1"]], - y=[groupID, groupID], - name="", - marker={"color": "white"}, + # if there are already values append the gap + if len(scatter_data_dict[color_id]["x"]) > 0: + # a gap on the scatterplot separates the rectangles from each other + scatter_data_dict[color_id]["x"].append( + scatter_data_dict[color_id]["x"][-1] + ) + scatter_data_dict[color_id]["y"].append(None) + + xs, ys = _get_corner_points( + tasks[index]["x0"], + tasks[index]["y0"], + tasks[index]["x1"], + tasks[index]["y1"], ) + + scatter_data_dict[color_id]["x"] += xs + scatter_data_dict[color_id]["y"] += ys + + # append dummy markers for showing start and end of interval + if color_id not in marker_data_dict: + marker_data_dict[color_id] = copy.deepcopy(marker_data_template) + marker_data_dict[color_id]["marker"]["color"] = color_id + marker_data_dict[color_id]["legendgroup"] = color_id + + marker_data_dict[color_id]["x"].append(tasks[index]["x0"]) + marker_data_dict[color_id]["x"].append(tasks[index]["x1"]) + marker_data_dict[color_id]["y"].append(groupID) + marker_data_dict[color_id]["y"].append(groupID) + if "description" in tasks[index]: - entry["text"] = tasks[index]["description"] + marker_data_dict[color_id]["text"].append(tasks[index]["description"]) + marker_data_dict[color_id]["text"].append(tasks[index]["description"]) del tasks[index]["description"] - data.append(entry) + else: + marker_data_dict[color_id]["text"].append(None) + marker_data_dict[color_id]["text"].append(None) + # add colorbar to one of the traces randomly just for display if show_colorbar is True: - # generate dummy data for colorscale visibility - data.append( + k = list(marker_data_dict.keys())[0] + marker_data_dict[k]["marker"].update( 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, - }, + colorscale=[[0, colors[0]], [1, colors[1]]], + showscale=True, + cmax=100, + cmin=0, ) ) @@ -339,7 +477,7 @@ def gantt_colorscale( for index in range(len(tasks)): tn = tasks[index]["name"] del tasks[index]["name"] - tasks[index].update(shape_template) + # If group_tasks is True, all tasks with the same name belong # to the same row. groupID = index @@ -349,33 +487,70 @@ def gantt_colorscale( tasks[index]["y1"] = groupID + bar_width tasks[index]["fillcolor"] = index_vals_dict[chart[index][index_col]] + color_id = tasks[index]["fillcolor"] + + if color_id not in scatter_data_dict: + scatter_data_dict[color_id] = copy.deepcopy(scatter_data_template) + + scatter_data_dict[color_id]["fillcolor"] = color_id + scatter_data_dict[color_id]["legendgroup"] = color_id + scatter_data_dict[color_id]["name"] = str(chart[index][index_col]) + + # relabel colors with 'rgb' + colors = clrs.color_parser(colors, clrs.label_rgb) - # add a line for hover text and autorange - entry = dict( - x=[tasks[index]["x0"], tasks[index]["x1"]], - y=[groupID, groupID], - name="", - marker={"color": "white"}, + # if there are already values append the gap + if len(scatter_data_dict[color_id]["x"]) > 0: + # a gap on the scatterplot separates the rectangles from each other + scatter_data_dict[color_id]["x"].append( + scatter_data_dict[color_id]["x"][-1] + ) + scatter_data_dict[color_id]["y"].append(None) + + xs, ys = _get_corner_points( + tasks[index]["x0"], + tasks[index]["y0"], + tasks[index]["x1"], + tasks[index]["y1"], ) + + scatter_data_dict[color_id]["x"] += xs + scatter_data_dict[color_id]["y"] += ys + + # append dummy markers for showing start and end of interval + if color_id not in marker_data_dict: + marker_data_dict[color_id] = copy.deepcopy(marker_data_template) + marker_data_dict[color_id]["marker"]["color"] = color_id + marker_data_dict[color_id]["legendgroup"] = color_id + + marker_data_dict[color_id]["x"].append(tasks[index]["x0"]) + marker_data_dict[color_id]["x"].append(tasks[index]["x1"]) + marker_data_dict[color_id]["y"].append(groupID) + marker_data_dict[color_id]["y"].append(groupID) + if "description" in tasks[index]: - entry["text"] = tasks[index]["description"] + marker_data_dict[color_id]["text"].append(tasks[index]["description"]) + marker_data_dict[color_id]["text"].append(tasks[index]["description"]) del tasks[index]["description"] - data.append(entry) + else: + marker_data_dict[color_id]["text"].append(None) + marker_data_dict[color_id]["text"].append(None) 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), - ) - ) + for k in scatter_data_dict: + scatter_data_dict[k]["showlegend"] = showlegend + # add colorbar to one of the traces randomly just for display + # if show_colorbar is True: + # k = list(marker_data_dict.keys())[0] + # marker_data_dict[k]["marker"].update( + # dict( + # colorscale=[[0, colors[0]], [1, colors[1]]], + # showscale=True, + # cmax=100, + # cmin=0, + # ) + # ) layout = dict( title=title, @@ -410,9 +585,14 @@ def gantt_colorscale( type="date", ), ) - layout["shapes"] = tasks - fig = graph_objs.Figure(data=data, layout=layout) + data = [scatter_data_dict[k] for k in sorted(scatter_data_dict)] + data += [marker_data_dict[k] for k in sorted(marker_data_dict)] + + # fig = dict( + # data=data, layout=layout + # ) + fig = go.Figure(data=data, layout=layout) return fig @@ -431,10 +611,12 @@ def gantt_dict( task_names=None, data=None, group_tasks=False, + show_hover_fill=True, ): """ Refer to FigureFactory.create_gantt() for docstring """ + if tasks is None: tasks = [] if task_names is None: @@ -453,12 +635,33 @@ def gantt_dict( task["description"] = chart[index]["Description"] tasks.append(task) - shape_template = { - "type": "rect", - "xref": "x", - "yref": "y", - "opacity": 1, - "line": {"width": 0}, + # create a scatter trace for every task group + scatter_data_dict = dict() + # create scatter traces for the start- and endpoints + marker_data_dict = dict() + + if show_hover_fill: + hoverinfo = "name" + else: + hoverinfo = "skip" + + scatter_data_template = { + "x": [], + "y": [], + "mode": "none", + "fill": "toself", + "hoverinfo": hoverinfo, + "legendgroup": "", + } + + marker_data_template = { + "x": [], + "y": [], + "mode": "markers", + "text": [], + "marker": dict(color="", size=1, opacity=0), + "name": "", + "showlegend": False, } index_vals = [] @@ -492,7 +695,6 @@ def gantt_dict( for index in range(len(tasks)): tn = tasks[index]["name"] del tasks[index]["name"] - tasks[index].update(shape_template) # If group_tasks is True, all tasks with the same name belong # to the same row. @@ -503,34 +705,56 @@ def gantt_dict( tasks[index]["y1"] = groupID + bar_width tasks[index]["fillcolor"] = colors[chart[index][index_col]] + color_id = tasks[index]["fillcolor"] - # add a line for hover text and autorange - entry = dict( - x=[tasks[index]["x0"], tasks[index]["x1"]], - y=[groupID, groupID], - showlegend=False, - name="", - marker={"color": "white"}, + if color_id not in scatter_data_dict: + scatter_data_dict[color_id] = copy.deepcopy(scatter_data_template) + + scatter_data_dict[color_id]["legendgroup"] = color_id + scatter_data_dict[color_id]["fillcolor"] = color_id + + # if there are already values append the gap + if len(scatter_data_dict[color_id]["x"]) > 0: + # a gap on the scatterplot separates the rectangles from each other + scatter_data_dict[color_id]["x"].append( + scatter_data_dict[color_id]["x"][-1] + ) + scatter_data_dict[color_id]["y"].append(None) + + xs, ys = _get_corner_points( + tasks[index]["x0"], + tasks[index]["y0"], + tasks[index]["x1"], + tasks[index]["y1"], ) + + scatter_data_dict[color_id]["x"] += xs + scatter_data_dict[color_id]["y"] += ys + + # append dummy markers for showing start and end of interval + if color_id not in marker_data_dict: + marker_data_dict[color_id] = copy.deepcopy(marker_data_template) + marker_data_dict[color_id]["marker"]["color"] = color_id + marker_data_dict[color_id]["legendgroup"] = color_id + + marker_data_dict[color_id]["x"].append(tasks[index]["x0"]) + marker_data_dict[color_id]["x"].append(tasks[index]["x1"]) + marker_data_dict[color_id]["y"].append(groupID) + marker_data_dict[color_id]["y"].append(groupID) + if "description" in tasks[index]: - entry["text"] = tasks[index]["description"] + marker_data_dict[color_id]["text"].append(tasks[index]["description"]) + marker_data_dict[color_id]["text"].append(tasks[index]["description"]) del tasks[index]["description"] - data.append(entry) + else: + marker_data_dict[color_id]["text"].append(None) + marker_data_dict[color_id]["text"].append(None) 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), - ) - ) + + for index_value in index_vals: + scatter_data_dict[colors[index_value]]["name"] = str(index_value) layout = dict( title=title, @@ -565,9 +789,14 @@ def gantt_dict( type="date", ), ) - layout["shapes"] = tasks - fig = graph_objs.Figure(data=data, layout=layout) + data = [scatter_data_dict[k] for k in sorted(scatter_data_dict)] + data += [marker_data_dict[k] for k in sorted(marker_data_dict)] + + # fig = dict( + # data=data, layout=layout + # ) + fig = go.Figure(data=data, layout=layout) return fig @@ -582,15 +811,15 @@ def create_gantt( showgrid_x=False, showgrid_y=False, height=600, - width=800, + width=900, tasks=None, task_names=None, data=None, group_tasks=False, + show_hover_fill=True, ): """ 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'. Other columns can be included and @@ -610,6 +839,8 @@ def create_gantt( 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) show_hover_fill: enables/disables the hovertext for the + filled area of the chart. :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 @@ -774,6 +1005,8 @@ def create_gantt( task_names=None, data=None, group_tasks=group_tasks, + show_hover_fill=show_hover_fill, + show_colorbar=show_colorbar, ) return fig else: @@ -793,6 +1026,7 @@ def create_gantt( task_names=None, data=None, group_tasks=group_tasks, + show_hover_fill=show_hover_fill, ) return fig else: @@ -811,5 +1045,6 @@ def create_gantt( task_names=None, data=None, group_tasks=group_tasks, + show_hover_fill=show_hover_fill, ) return fig diff --git a/packages/python/plotly/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py b/packages/python/plotly/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py index a792cee7f41..2d009798059 100644 --- a/packages/python/plotly/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py +++ b/packages/python/plotly/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py @@ -2042,109 +2042,121 @@ def test_df_dataframe_all_args(self): test_gantt_chart = ff.create_gantt(df) - exp_gantt_chart = { - "data": [ - { - "marker": {"color": "white"}, - "name": "", - "type": "scatter", - "x": ["2009-01-01", "2009-02-30"], - "y": [0, 0], - }, - { - "marker": {"color": "white"}, - "name": "", - "type": "scatter", - "x": ["2009-03-05", "2009-04-15"], - "y": [1, 1], - }, - ], - "layout": { - "height": 600, - "hovermode": "closest", - "shapes": [ + exp_gantt_chart = go.Figure( + **{ + "data": [ { + "x": ("2009-03-05", "2009-04-15", "2009-04-15", "2009-03-05"), + "y": [0.8, 0.8, 1.2, 1.2], + "mode": "none", + "fill": "toself", + "hoverinfo": "name", + "fillcolor": "rgb(255, 127, 14)", + "name": "Job B", + "legendgroup": "rgb(255, 127, 14)", + }, + { + "x": ("2009-01-01", "2009-02-30", "2009-02-30", "2009-01-01"), + "y": [-0.2, -0.2, 0.2, 0.2], + "mode": "none", + "fill": "toself", + "hoverinfo": "name", "fillcolor": "rgb(31, 119, 180)", - "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", + "name": "Job A", + "legendgroup": "rgb(31, 119, 180)", }, { - "fillcolor": "rgb(255, 127, 14)", - "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", + "x": ("2009-03-05", "2009-04-15"), + "y": [1, 1], + "mode": "markers", + "text": [None, None], + "marker": { + "color": "rgb(255, 127, 14)", + "size": 1, + "opacity": 0, + }, + "name": "", + "showlegend": False, + "legendgroup": "rgb(255, 127, 14)", + }, + { + "x": ("2009-01-01", "2009-02-30"), + "y": [0, 0], + "mode": "markers", + "text": [None, None], + "marker": { + "color": "rgb(31, 119, 180)", + "size": 1, + "opacity": 0, + }, + "name": "", + "showlegend": False, + "legendgroup": "rgb(31, 119, 180)", }, ], - "showlegend": False, - "title": {"text": "Gantt Chart"}, - "width": 800, - "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"}, - ] + "layout": { + "title": "Gantt Chart", + "showlegend": False, + "height": 600, + "width": 900, + "shapes": [], + "hovermode": "closest", + "yaxis": { + "showgrid": False, + "ticktext": ["Job A", "Job B"], + "tickvals": [0, 1], + "range": [-1, 3], + "autorange": False, + "zeroline": False, + }, + "xaxis": { + "showgrid": False, + "zeroline": False, + "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"}, + ] + }, + "type": "date", }, - "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.assert_fig_equal(test_gantt_chart["data"][0], exp_gantt_chart["data"][0]) + } + ) - self.assert_fig_equal(test_gantt_chart["layout"], exp_gantt_chart["layout"]) + self.assert_fig_equal(test_gantt_chart["data"][1], exp_gantt_chart["data"][1]) + self.assert_fig_equal(test_gantt_chart["data"][1], exp_gantt_chart["data"][1]) + self.assert_fig_equal(test_gantt_chart["data"][2], exp_gantt_chart["data"][2]) + self.assert_fig_equal(test_gantt_chart["data"][3], exp_gantt_chart["data"][3]) class TestViolin(NumpyTestUtilsMixin, TestCaseNoTemplate): diff --git a/packages/python/plotly/plotly/tests/test_optional/test_tools/test_figure_factory.py b/packages/python/plotly/plotly/tests/test_optional/test_tools/test_figure_factory.py index 1fd0b059664..35b9fed4c10 100644 --- a/packages/python/plotly/plotly/tests/test_optional/test_tools/test_figure_factory.py +++ b/packages/python/plotly/plotly/tests/test_optional/test_tools/test_figure_factory.py @@ -1554,7 +1554,7 @@ def test_table_with_index(self): self.assert_fig_equal(index_table["layout"], exp_index_table["layout"]) -class TestGantt(TestCase): +class TestGantt(TestCaseNoTemplate, NumpyTestUtilsMixin): def test_validate_gantt(self): # validate the basic gantt inputs @@ -1810,204 +1810,170 @@ def test_gannt_groups_and_descriptions(self): ) exp_gantt_chart = graph_objs.Figure( - { - "data": [ - { - "marker": {"color": "white"}, - "name": "", - "showlegend": False, - "text": "Task A - 1", - "x": ["2008-10-05", "2009-04-15"], - "y": [2, 2], - }, - { - "marker": {"color": "white"}, - "name": "", - "showlegend": False, - "text": "Task B - 1", - "x": ["2008-12-06", "2009-03-15"], - "y": [1, 1], - }, - { - "marker": {"color": "white"}, - "name": "", - "showlegend": False, - "text": "Task C - 1", - "x": ["2008-09-07", "2009-03-15"], - "y": [0, 0], - }, - { - "marker": {"color": "white"}, - "name": "", - "showlegend": False, - "text": "Task C - 2", - "x": ["2009-05-08", "2009-04-15"], - "y": [0, 0], - }, - { - "marker": {"color": "white"}, - "name": "", - "showlegend": False, - "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, + "yaxis": { + "range": [-1, 4], + "zeroline": False, + "ticktext": ["Task C", "Task B", "Task A"], + "tickvals": [0, 1, 2], + "autorange": False, + "showgrid": False, + }, "title": "Gantt Chart", - "width": 800, + "height": 600, + "shapes": [], + "width": 900, "xaxis": { + "zeroline": False, "rangeselector": { "buttons": [ { "count": 7, - "label": "1w", "step": "day", "stepmode": "backward", + "label": "1w", }, { "count": 1, - "label": "1m", "step": "month", "stepmode": "backward", + "label": "1m", }, { "count": 6, - "label": "6m", "step": "month", "stepmode": "backward", + "label": "6m", }, { "count": 1, - "label": "YTD", "step": "year", "stepmode": "todate", + "label": "YTD", }, { "count": 1, - "label": "1y", "step": "year", "stepmode": "backward", + "label": "1y", }, {"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, }, + "hovermode": "closest", }, + "data": [ + { + "legendgroup": "rgb(170, 14, 200)", + "name": "TB", + "fillcolor": "rgb(170, 14, 200)", + "mode": "none", + "hoverinfo": "name", + "y": [0.8, 0.8, 1.2, 1.2], + "x": ["2008-12-06", "2009-03-15", "2009-03-15", "2008-12-06"], + "fill": "toself", + }, + { + "legendgroup": "rgb(220, 0, 0)", + "name": "TA", + "fillcolor": "rgb(220, 0, 0)", + "mode": "none", + "hoverinfo": "name", + "y": [1.8, 1.8, 2.2, 2.2, None, 1.8, 1.8, 2.2, 2.2], + "x": [ + "2008-10-05", + "2009-04-15", + "2009-04-15", + "2008-10-05", + "2008-10-05", + "2009-04-20", + "2009-05-30", + "2009-05-30", + "2009-04-20", + ], + "fill": "toself", + }, + { + "legendgroup": "rgb(255, 230, 41)", + "name": "TC", + "fillcolor": "rgb(255, 230, 41)", + "mode": "none", + "hoverinfo": "name", + "y": [-0.2, -0.2, 0.2, 0.2, None, -0.2, -0.2, 0.2, 0.2], + "x": [ + "2008-09-07", + "2009-03-15", + "2009-03-15", + "2008-09-07", + "2008-09-07", + "2009-05-08", + "2009-04-15", + "2009-04-15", + "2009-05-08", + ], + "fill": "toself", + }, + { + "showlegend": False, + "legendgroup": "rgb(170, 14, 200)", + "name": "", + "text": ["Task B - 1", "Task B - 1"], + "y": [1, 1], + "mode": "markers", + "marker": { + "opacity": 0, + "color": "rgb(170, 14, 200)", + "size": 1, + }, + "x": ["2008-12-06", "2009-03-15"], + }, + { + "showlegend": False, + "legendgroup": "rgb(220, 0, 0)", + "name": "", + "text": [ + "Task A - 1", + "Task A - 1", + "Task A - 2", + "Task A - 2", + ], + "y": [2, 2, 2, 2], + "mode": "markers", + "marker": {"opacity": 0, "color": "rgb(220, 0, 0)", "size": 1}, + "x": ["2008-10-05", "2009-04-15", "2009-04-20", "2009-05-30"], + }, + { + "showlegend": False, + "legendgroup": "rgb(255, 230, 41)", + "name": "", + "text": [ + "Task C - 1", + "Task C - 1", + "Task C - 2", + "Task C - 2", + ], + "y": [0, 0, 0, 0], + "mode": "markers", + "marker": { + "opacity": 0, + "color": "rgb(255, 230, 41)", + "size": 1, + }, + "x": ["2008-09-07", "2009-03-15", "2009-05-08", "2009-04-15"], + }, + ], } ) - 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"]) + self.assert_fig_equal(test_gantt_chart["data"][0], exp_gantt_chart["data"][0]) + self.assert_fig_equal(test_gantt_chart["data"][1], exp_gantt_chart["data"][1]) + self.assert_fig_equal(test_gantt_chart["data"][2], exp_gantt_chart["data"][2]) + self.assert_fig_equal(test_gantt_chart["data"][3], exp_gantt_chart["data"][3]) def test_gantt_all_args(self): @@ -2042,54 +2008,77 @@ def test_gantt_all_args(self): ) exp_gantt_chart = graph_objs.Figure( - { + **{ "data": [ { - "marker": {"color": "white"}, - "name": "", - "x": ["2010-01-01", "2011-02-02"], - "y": [0, 0], + "x": ["2011-01-01", "2012-06-05", "2012-06-05", "2011-01-01"], + "y": [0.5, 0.5, 1.5, 1.5], + "mode": "none", + "fill": "toself", + "showlegend": False, + "hoverinfo": "name", + "legendgroup": "rgb(166.25, 167.5, 208.0)", + "fillcolor": "rgb(166.25, 167.5, 208.0)", + "name": "25", + }, + { + "x": ["2010-01-01", "2011-02-02", "2011-02-02", "2010-01-01"], + "y": [-0.5, -0.5, 0.5, 0.5], + "mode": "none", + "fill": "toself", + "showlegend": False, + "hoverinfo": "name", + "legendgroup": "rgb(220.0, 220.0, 220.0)", + "fillcolor": "rgb(220.0, 220.0, 220.0)", + "name": "0", }, { - "marker": {"color": "white"}, - "name": "", "x": ["2011-01-01", "2012-06-05"], "y": [1, 1], + "mode": "markers", + "text": [None, None], + "marker": { + "color": "rgb(166.25, 167.5, 208.0)", + "size": 1, + "opacity": 0, + }, + "name": "", + "showlegend": False, + "legendgroup": "rgb(166.25, 167.5, 208.0)", + }, + { + "x": ["2010-01-01", "2011-02-02"], + "y": [0, 0], + "mode": "markers", + "text": [None, None], + "marker": { + "color": "rgb(220.0, 220.0, 220.0)", + "size": 1, + "opacity": 0, + }, + "name": "", + "showlegend": False, + "legendgroup": "rgb(220.0, 220.0, 220.0)", }, ], "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", + "showlegend": False, + "height": 500, "width": 500, + "shapes": [], + "hovermode": "closest", + "yaxis": { + "showgrid": True, + "ticktext": ["Run", "Fast"], + "tickvals": [0, 1], + "range": [-1, 3], + "autorange": False, + "zeroline": False, + }, "xaxis": { + "showgrid": True, + "zeroline": False, "rangeselector": { "buttons": [ { @@ -2125,27 +2114,19 @@ def test_gantt_all_args(self): {"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.assert_fig_equal(test_gantt_chart["data"][0], exp_gantt_chart["data"][0]) + + self.assert_fig_equal(test_gantt_chart["data"][1], exp_gantt_chart["data"][1]) - self.assertEqual(test_gantt_chart["data"][1], exp_gantt_chart["data"][1]) + self.assert_fig_equal(test_gantt_chart["data"][2], exp_gantt_chart["data"][2]) - self.assertEqual(test_gantt_chart["layout"], exp_gantt_chart["layout"]) + self.assert_fig_equal(test_gantt_chart["layout"], exp_gantt_chart["layout"]) class Test2D_Density(TestCaseNoTemplate, NumpyTestUtilsMixin):