From 1947333de2c313b4d8d992fb2ca906e61711d89f Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 18 Nov 2019 16:27:23 -0500 Subject: [PATCH 01/15] first draft of adding pie, sunburst, funnel and funnelarea --- .../python/plotly/plotly/express/__init__.py | 8 ++ .../plotly/plotly/express/_chart_types.py | 115 ++++++++++++++++++ .../python/plotly/plotly/express/_core.py | 20 ++- packages/python/plotly/plotly/express/_doc.py | 35 +++++- 4 files changed, 174 insertions(+), 4 deletions(-) diff --git a/packages/python/plotly/plotly/express/__init__.py b/packages/python/plotly/plotly/express/__init__.py index 3ec382d0f3..1b918f3207 100644 --- a/packages/python/plotly/plotly/express/__init__.py +++ b/packages/python/plotly/plotly/express/__init__.py @@ -39,6 +39,10 @@ choropleth, density_contour, density_heatmap, + pie, + sunburst, + funnel, + funnel_area, ) from ._imshow import imshow @@ -77,6 +81,10 @@ "strip", "histogram", "choropleth", + "pie", + "sunburst", + "funnel" + "funnel_area" "imshow", "data", "colors", diff --git a/packages/python/plotly/plotly/express/_chart_types.py b/packages/python/plotly/plotly/express/_chart_types.py index cbf2fff85c..9bf0177838 100644 --- a/packages/python/plotly/plotly/express/_chart_types.py +++ b/packages/python/plotly/plotly/express/_chart_types.py @@ -1115,3 +1115,118 @@ def parallel_categories( parallel_categories.__doc__ = make_docstring(parallel_categories) + + +def pie( + data_frame=None, + text=None, + values=None, + hover_name=None, + hover_data=None, + custom_data=None, + labels={}, + title=None, + template=None, + width=None, + height=None, + opacity=None, + ): + """ + In a pie plot, each row of `data_frame` is represented as a sector of a pie. + """ + return make_figure( + args=locals(), + constructor=go.Pie, + ) + + +pie.__doc__ = make_docstring(pie) + + +def sunburst( + data_frame=None, + text=None, + values=None, + parents=None, + ids=None, + hover_name=None, + hover_data=None, + custom_data=None, + labels={}, + title=None, + template=None, + width=None, + height=None, + branchvalues=None, + maxdepth=None, + ): + """ + In a pie plot, each row of `data_frame` is represented as a sector of a pie. + """ + return make_figure( + args=locals(), + constructor=go.Sunburst, + trace_patch=dict(branchvalues=branchvalues, maxdepth=maxdepth) + ) + + +sunburst.__doc__ = make_docstring(sunburst) + + + +def funnel( + data_frame=None, + x=None, + y=None, + color=None, + color_discrete_sequence=None, + color_discrete_map={}, + orientation=None, + textinfo=None, + hover_name=None, + hover_data=None, + custom_data=None, + labels={}, + title=None, + template=None, + width=None, + height=None, + opacity=None, + ): + """ + In a funnel plot, each row of `data_frame` is represented as a rectangular sector of a funnel. + """ + return make_figure( + args=locals(), + constructor=go.Funnel, + trace_patch=dict(opacity=opacity, orientation=orientation, textinfo=textinfo), + ) + + +funnel.__doc__ = make_docstring(funnel, override_dict=dict(textinfo=["str", "Determines which trace information appear on the graph. In the case of having multiple funnels, percentages & totals are computed separately (per trace)."])) + + +def funnel_area( + data_frame=None, + values=None, + text=None, + textinfo=None, + hover_name=None, + hover_data=None, + custom_data=None, + labels={}, + title=None, + template=None, + width=None, + height=None, + ): + """ + In a funnel area plot, each row of `data_frame` is represented as a trapezoidal sector of a funnel. + """ + return make_figure( + args=locals(), + constructor=go.Funnelarea, + ) + + +funnel_area.__doc__ = make_docstring(funnel_area, override_dict=dict(textinfo=["str", "Determines which trace information appear on the graph. In the case of having multiple funnels, percentages & totals are computed separately (per trace)."])) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 503edaf7b9..03bd99f09e 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -305,11 +305,25 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): elif k == "locations": result[k] = g[v] mapping_labels[v_label] = "%{location}" + elif k == "values": + result[k] = g[v] + mapping_labels["value"] = "%{value}" + elif k == "parents": + result[k] = g[v] + mapping_labels["parent"] = "%{parent}" + elif k == "ids": + result[k] = g[v] + mapping_labels["id"] = "%{id}" + elif k == "text": + if trace_spec.constructor in [go.Sunburst, go.Treemap]: + result["labels"] = g[v] + else: + result[k] = g[v] else: if v: result[k] = g[v] mapping_labels[v_label] = "%%{%s}" % k - if trace_spec.constructor not in [go.Parcoords, go.Parcats]: + if trace_spec.constructor not in [go.Parcoords, go.Parcats, go.Sunburst, go.Treemap]: hover_lines = [k + "=" + v for k, v in mapping_labels.items()] result["hovertemplate"] = hover_header + "
".join(hover_lines) return result, fit_results @@ -956,6 +970,7 @@ def infer_config(args, constructor, trace_patch): attrables = ( ["x", "y", "z", "a", "b", "c", "r", "theta", "size", "dimensions"] + ["custom_data", "hover_name", "hover_data", "text"] + + ["values", "parents", "ids"] + ["error_x", "error_x_minus"] + ["error_y", "error_y_minus", "error_z", "error_z_minus"] + ["lat", "lon", "locations", "animation_group"] @@ -997,6 +1012,8 @@ def infer_config(args, constructor, trace_patch): grouped_attrs.append("marker.color") show_colorbar = bool("color" in attrs and args["color"]) + else: + show_colorbar=False # Compute line_dash grouping attribute if "line_dash" in args: @@ -1148,6 +1165,7 @@ def make_figure(args, constructor, trace_patch={}, layout_patch={}): go.Parcoords, go.Choropleth, go.Histogram2d, + go.Sunburst, ]: trace.update( legendgroup=trace_name, diff --git a/packages/python/plotly/plotly/express/_doc.py b/packages/python/plotly/plotly/express/_doc.py index 950a3953be..f836a7de0b 100644 --- a/packages/python/plotly/plotly/express/_doc.py +++ b/packages/python/plotly/plotly/express/_doc.py @@ -66,6 +66,21 @@ colref_desc, "Values from this column or array_like are used to position marks along the angular axis in polar coordinates.", ], + values=[ + colref_type, + colref_desc, + "Values from this column or array_like are used to set values associated to sectors." + ], + parents=[ + colref_type, + colref_desc, + "Values from this column or array_like are used to set values associated to sectors." + ], + ids=[ + colref_type, + colref_desc, + "Values from this column or array_like are used to set values associated to sectors." + ], lat=[ colref_type, colref_desc, @@ -442,21 +457,35 @@ nbins=["int", "Positive integer.", "Sets the number of bins."], nbinsx=["int", "Positive integer.", "Sets the number of bins along the x axis."], nbinsy=["int", "Positive integer.", "Sets the number of bins along the y axis."], + branchvalues=["str", "'total' or 'remainder'", + "Determines how the items in `values` are summed. When" + "set to 'total', items in `values` are taken to be value" + "of all its descendants. When set to 'remainder', items" + "in `values` corresponding to the root and the branches" + ":sectors are taken to be the extra part not part of the" + "sum of the values at their leaves."], + maxdepth=["int", "Positive integer", + "Sets the number of rendered sectors from any given `level`. Set `maxdepth` to -1 to render all the" + "levels in the hierarchy."] ) -def make_docstring(fn): +def make_docstring(fn, override_dict={}): tw = TextWrapper(width=77, initial_indent=" ", subsequent_indent=" ") result = (fn.__doc__ or "") + "\nParameters\n----------\n" for param in inspect.getargspec(fn)[0]: - param_desc_list = docs[param][1:] + if override_dict.get(param): + param_doc = override_dict[param] + else: + param_doc = docs[param] + param_desc_list = param_doc[1:] param_desc = ( tw.fill(" ".join(param_desc_list or "")) if param in docs else "(documentation missing from map)" ) - param_type = docs[param][0] + param_type = param_doc[0] result += "%s: %s\n%s\n" % (param, param_type, param_desc) result += "\nReturns\n-------\n" result += " A `Figure` object." From 668eb90ad09fe6aa5770f446874df773fcd82cc7 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 18 Nov 2019 16:28:56 -0500 Subject: [PATCH 02/15] black --- .../python/plotly/plotly/express/__init__.py | 4 +- .../plotly/plotly/express/_chart_types.py | 45 +++++++++++-------- .../python/plotly/plotly/express/_core.py | 9 +++- packages/python/plotly/plotly/express/_doc.py | 32 +++++++------ 4 files changed, 54 insertions(+), 36 deletions(-) diff --git a/packages/python/plotly/plotly/express/__init__.py b/packages/python/plotly/plotly/express/__init__.py index 1b918f3207..248ad6de50 100644 --- a/packages/python/plotly/plotly/express/__init__.py +++ b/packages/python/plotly/plotly/express/__init__.py @@ -83,9 +83,7 @@ "choropleth", "pie", "sunburst", - "funnel" - "funnel_area" - "imshow", + "funnel" "funnel_area" "imshow", "data", "colors", "set_mapbox_access_token", diff --git a/packages/python/plotly/plotly/express/_chart_types.py b/packages/python/plotly/plotly/express/_chart_types.py index 9bf0177838..fb7463b6cb 100644 --- a/packages/python/plotly/plotly/express/_chart_types.py +++ b/packages/python/plotly/plotly/express/_chart_types.py @@ -1130,14 +1130,11 @@ def pie( width=None, height=None, opacity=None, - ): +): """ In a pie plot, each row of `data_frame` is represented as a sector of a pie. """ - return make_figure( - args=locals(), - constructor=go.Pie, - ) + return make_figure(args=locals(), constructor=go.Pie,) pie.__doc__ = make_docstring(pie) @@ -1159,21 +1156,20 @@ def sunburst( height=None, branchvalues=None, maxdepth=None, - ): +): """ In a pie plot, each row of `data_frame` is represented as a sector of a pie. """ return make_figure( args=locals(), constructor=go.Sunburst, - trace_patch=dict(branchvalues=branchvalues, maxdepth=maxdepth) + trace_patch=dict(branchvalues=branchvalues, maxdepth=maxdepth), ) sunburst.__doc__ = make_docstring(sunburst) - def funnel( data_frame=None, x=None, @@ -1192,7 +1188,7 @@ def funnel( width=None, height=None, opacity=None, - ): +): """ In a funnel plot, each row of `data_frame` is represented as a rectangular sector of a funnel. """ @@ -1203,7 +1199,15 @@ def funnel( ) -funnel.__doc__ = make_docstring(funnel, override_dict=dict(textinfo=["str", "Determines which trace information appear on the graph. In the case of having multiple funnels, percentages & totals are computed separately (per trace)."])) +funnel.__doc__ = make_docstring( + funnel, + override_dict=dict( + textinfo=[ + "str", + "Determines which trace information appear on the graph. In the case of having multiple funnels, percentages & totals are computed separately (per trace).", + ] + ), +) def funnel_area( @@ -1219,14 +1223,19 @@ def funnel_area( template=None, width=None, height=None, - ): +): """ In a funnel area plot, each row of `data_frame` is represented as a trapezoidal sector of a funnel. """ - return make_figure( - args=locals(), - constructor=go.Funnelarea, - ) - - -funnel_area.__doc__ = make_docstring(funnel_area, override_dict=dict(textinfo=["str", "Determines which trace information appear on the graph. In the case of having multiple funnels, percentages & totals are computed separately (per trace)."])) + return make_figure(args=locals(), constructor=go.Funnelarea,) + + +funnel_area.__doc__ = make_docstring( + funnel_area, + override_dict=dict( + textinfo=[ + "str", + "Determines which trace information appear on the graph. In the case of having multiple funnels, percentages & totals are computed separately (per trace).", + ] + ), +) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 03bd99f09e..89d6e03ecc 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -323,7 +323,12 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): if v: result[k] = g[v] mapping_labels[v_label] = "%%{%s}" % k - if trace_spec.constructor not in [go.Parcoords, go.Parcats, go.Sunburst, go.Treemap]: + if trace_spec.constructor not in [ + go.Parcoords, + go.Parcats, + go.Sunburst, + go.Treemap, + ]: hover_lines = [k + "=" + v for k, v in mapping_labels.items()] result["hovertemplate"] = hover_header + "
".join(hover_lines) return result, fit_results @@ -1013,7 +1018,7 @@ def infer_config(args, constructor, trace_patch): show_colorbar = bool("color" in attrs and args["color"]) else: - show_colorbar=False + show_colorbar = False # Compute line_dash grouping attribute if "line_dash" in args: diff --git a/packages/python/plotly/plotly/express/_doc.py b/packages/python/plotly/plotly/express/_doc.py index f836a7de0b..eab4e1086b 100644 --- a/packages/python/plotly/plotly/express/_doc.py +++ b/packages/python/plotly/plotly/express/_doc.py @@ -69,17 +69,17 @@ values=[ colref_type, colref_desc, - "Values from this column or array_like are used to set values associated to sectors." + "Values from this column or array_like are used to set values associated to sectors.", ], parents=[ colref_type, colref_desc, - "Values from this column or array_like are used to set values associated to sectors." + "Values from this column or array_like are used to set values associated to sectors.", ], ids=[ colref_type, colref_desc, - "Values from this column or array_like are used to set values associated to sectors." + "Values from this column or array_like are used to set values associated to sectors.", ], lat=[ colref_type, @@ -457,16 +457,22 @@ nbins=["int", "Positive integer.", "Sets the number of bins."], nbinsx=["int", "Positive integer.", "Sets the number of bins along the x axis."], nbinsy=["int", "Positive integer.", "Sets the number of bins along the y axis."], - branchvalues=["str", "'total' or 'remainder'", - "Determines how the items in `values` are summed. When" - "set to 'total', items in `values` are taken to be value" - "of all its descendants. When set to 'remainder', items" - "in `values` corresponding to the root and the branches" - ":sectors are taken to be the extra part not part of the" - "sum of the values at their leaves."], - maxdepth=["int", "Positive integer", - "Sets the number of rendered sectors from any given `level`. Set `maxdepth` to -1 to render all the" - "levels in the hierarchy."] + branchvalues=[ + "str", + "'total' or 'remainder'", + "Determines how the items in `values` are summed. When" + "set to 'total', items in `values` are taken to be value" + "of all its descendants. When set to 'remainder', items" + "in `values` corresponding to the root and the branches" + ":sectors are taken to be the extra part not part of the" + "sum of the values at their leaves.", + ], + maxdepth=[ + "int", + "Positive integer", + "Sets the number of rendered sectors from any given `level`. Set `maxdepth` to -1 to render all the" + "levels in the hierarchy.", + ], ) From 78ed0f23dac3e6b792b92909a925c13502b432be Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 19 Nov 2019 16:23:04 -0500 Subject: [PATCH 03/15] labels and names --- .../python/plotly/plotly/express/__init__.py | 6 +- .../plotly/plotly/express/_chart_types.py | 60 +++++++++++++++++-- .../python/plotly/plotly/express/_core.py | 18 ++++-- packages/python/plotly/plotly/express/_doc.py | 6 ++ 4 files changed, 77 insertions(+), 13 deletions(-) diff --git a/packages/python/plotly/plotly/express/__init__.py b/packages/python/plotly/plotly/express/__init__.py index 248ad6de50..c825233aca 100644 --- a/packages/python/plotly/plotly/express/__init__.py +++ b/packages/python/plotly/plotly/express/__init__.py @@ -41,6 +41,7 @@ density_heatmap, pie, sunburst, + treemap, funnel, funnel_area, ) @@ -83,7 +84,10 @@ "choropleth", "pie", "sunburst", - "funnel" "funnel_area" "imshow", + "treemap", + "funnel", + "funnel_area", + "imshow", "data", "colors", "set_mapbox_access_token", diff --git a/packages/python/plotly/plotly/express/_chart_types.py b/packages/python/plotly/plotly/express/_chart_types.py index fb7463b6cb..84935021a7 100644 --- a/packages/python/plotly/plotly/express/_chart_types.py +++ b/packages/python/plotly/plotly/express/_chart_types.py @@ -1119,8 +1119,9 @@ def parallel_categories( def pie( data_frame=None, - text=None, + names=None, values=None, + textinfo=None, hover_name=None, hover_data=None, custom_data=None, @@ -1130,19 +1131,35 @@ def pie( width=None, height=None, opacity=None, + hole=None, ): """ In a pie plot, each row of `data_frame` is represented as a sector of a pie. """ - return make_figure(args=locals(), constructor=go.Pie,) + return make_figure( + args=locals(), + constructor=go.Pie, + trace_patch=dict(showlegend=True, hole=hole) + ) -pie.__doc__ = make_docstring(pie) +pie.__doc__ = make_docstring(pie, + override_dict=dict( + textinfo=[ + "str", + "Determines which trace information appear on the graph.", + ], + hole=[ + "float", + "Sets the fraction of the radius to cut out of the pie." + "Use this to make a donut chart." + ], + ),) def sunburst( data_frame=None, - text=None, + names=None, values=None, parents=None, ids=None, @@ -1158,7 +1175,8 @@ def sunburst( maxdepth=None, ): """ - In a pie plot, each row of `data_frame` is represented as a sector of a pie. + A sunburst plot represents hierarchial data as sectors laid out over + several levels of concentric rings. """ return make_figure( args=locals(), @@ -1170,6 +1188,36 @@ def sunburst( sunburst.__doc__ = make_docstring(sunburst) +def treemap( + data_frame=None, + names=None, + values=None, + parents=None, + ids=None, + hover_name=None, + hover_data=None, + custom_data=None, + labels={}, + title=None, + template=None, + width=None, + height=None, + branchvalues=None, + maxdepth=None, +): + """ + A treemap plot represents hierarchial data as nested rectangular sectors. + """ + return make_figure( + args=locals(), + constructor=go.Treemap, + trace_patch=dict(branchvalues=branchvalues, maxdepth=maxdepth), + ) + + +treemap.__doc__ = make_docstring(treemap) + + def funnel( data_frame=None, x=None, @@ -1213,7 +1261,7 @@ def funnel( def funnel_area( data_frame=None, values=None, - text=None, + names=None, textinfo=None, hover_name=None, hover_data=None, diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 89d6e03ecc..eee55b4053 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -307,16 +307,21 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): mapping_labels[v_label] = "%{location}" elif k == "values": result[k] = g[v] - mapping_labels["value"] = "%{value}" + _label = "value" if v_label == 'values' else v_label + mapping_labels[_label] = "%{value}" elif k == "parents": result[k] = g[v] - mapping_labels["parent"] = "%{parent}" + _label = "parent" if v_label == 'parents' else v_label + mapping_labels[_label] = "%{parent}" elif k == "ids": result[k] = g[v] - mapping_labels["id"] = "%{id}" - elif k == "text": - if trace_spec.constructor in [go.Sunburst, go.Treemap]: + _label = "id" if v_label == 'ids' else v_label + mapping_labels[_label] = "%{id}" + elif k == "names": + if trace_spec.constructor in [go.Sunburst, go.Treemap, go.Pie, go.Funnelarea]: result["labels"] = g[v] + _label = "label" if v_label == 'names' else v_label + mapping_labels[_label] = "%{label}" else: result[k] = g[v] else: @@ -975,7 +980,7 @@ def infer_config(args, constructor, trace_patch): attrables = ( ["x", "y", "z", "a", "b", "c", "r", "theta", "size", "dimensions"] + ["custom_data", "hover_name", "hover_data", "text"] - + ["values", "parents", "ids"] + + ["names", "values", "parents", "ids"] + ["error_x", "error_x_minus"] + ["error_y", "error_y_minus", "error_z", "error_z_minus"] + ["lat", "lon", "locations", "animation_group"] @@ -1171,6 +1176,7 @@ def make_figure(args, constructor, trace_patch={}, layout_patch={}): go.Choropleth, go.Histogram2d, go.Sunburst, + go.Treemap, ]: trace.update( legendgroup=trace_name, diff --git a/packages/python/plotly/plotly/express/_doc.py b/packages/python/plotly/plotly/express/_doc.py index eab4e1086b..a0f08ead73 100644 --- a/packages/python/plotly/plotly/express/_doc.py +++ b/packages/python/plotly/plotly/express/_doc.py @@ -183,6 +183,12 @@ colref_desc, "Values from this column or array_like appear in the figure as text labels.", ], + names=[ + colref_type, + colref_desc, + "Values from this column or array_like are used as labels for sectors.", + ], + locationmode=[ "str", "One of 'ISO-3', 'USA-states', or 'country names'", From 1916166f1844158ab3fce38cbd13446292b9ec04 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 19 Nov 2019 16:31:26 -0500 Subject: [PATCH 04/15] hovertemplate --- .../plotly/plotly/express/_chart_types.py | 23 ++++++++----------- .../python/plotly/plotly/express/_core.py | 17 ++++++++------ packages/python/plotly/plotly/express/_doc.py | 1 - 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/python/plotly/plotly/express/_chart_types.py b/packages/python/plotly/plotly/express/_chart_types.py index 84935021a7..691a5788d6 100644 --- a/packages/python/plotly/plotly/express/_chart_types.py +++ b/packages/python/plotly/plotly/express/_chart_types.py @@ -1137,24 +1137,21 @@ def pie( In a pie plot, each row of `data_frame` is represented as a sector of a pie. """ return make_figure( - args=locals(), - constructor=go.Pie, - trace_patch=dict(showlegend=True, hole=hole) - ) + args=locals(), constructor=go.Pie, trace_patch=dict(showlegend=True, hole=hole) + ) -pie.__doc__ = make_docstring(pie, - override_dict=dict( - textinfo=[ - "str", - "Determines which trace information appear on the graph.", - ], +pie.__doc__ = make_docstring( + pie, + override_dict=dict( + textinfo=["str", "Determines which trace information appear on the graph.",], hole=[ "float", "Sets the fraction of the radius to cut out of the pie." - "Use this to make a donut chart." - ], - ),) + "Use this to make a donut chart.", + ], + ), +) def sunburst( diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index eee55b4053..dafc71b807 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -307,20 +307,25 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): mapping_labels[v_label] = "%{location}" elif k == "values": result[k] = g[v] - _label = "value" if v_label == 'values' else v_label + _label = "value" if v_label == "values" else v_label mapping_labels[_label] = "%{value}" elif k == "parents": result[k] = g[v] - _label = "parent" if v_label == 'parents' else v_label + _label = "parent" if v_label == "parents" else v_label mapping_labels[_label] = "%{parent}" elif k == "ids": result[k] = g[v] - _label = "id" if v_label == 'ids' else v_label + _label = "id" if v_label == "ids" else v_label mapping_labels[_label] = "%{id}" elif k == "names": - if trace_spec.constructor in [go.Sunburst, go.Treemap, go.Pie, go.Funnelarea]: + if trace_spec.constructor in [ + go.Sunburst, + go.Treemap, + go.Pie, + go.Funnelarea, + ]: result["labels"] = g[v] - _label = "label" if v_label == 'names' else v_label + _label = "label" if v_label == "names" else v_label mapping_labels[_label] = "%{label}" else: result[k] = g[v] @@ -331,8 +336,6 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): if trace_spec.constructor not in [ go.Parcoords, go.Parcats, - go.Sunburst, - go.Treemap, ]: hover_lines = [k + "=" + v for k, v in mapping_labels.items()] result["hovertemplate"] = hover_header + "
".join(hover_lines) diff --git a/packages/python/plotly/plotly/express/_doc.py b/packages/python/plotly/plotly/express/_doc.py index a0f08ead73..f83a153642 100644 --- a/packages/python/plotly/plotly/express/_doc.py +++ b/packages/python/plotly/plotly/express/_doc.py @@ -188,7 +188,6 @@ colref_desc, "Values from this column or array_like are used as labels for sectors.", ], - locationmode=[ "str", "One of 'ISO-3', 'USA-states', or 'country names'", From 1b06d3aeddfc47e67fcaaae6610ddbfd659e2d4b Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 19 Nov 2019 16:34:36 -0500 Subject: [PATCH 05/15] added tests --- .../test_core/test_px/test_px_functions.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py new file mode 100644 index 0000000000..3b60ce797e --- /dev/null +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py @@ -0,0 +1,52 @@ +import plotly.express as px +import plotly.graph_objects as go +from numpy.testing import assert_array_equal + +def _compare_figures(go_trace, px_fig): + """Compare a figure created with a go trace and a figure created with + a px function call. Check that all values inside the go Figure are the + same in the px figure (which sets more parameters). + """ + go_fig = go.Figure(go_trace) + go_fig = go_fig.to_plotly_json() + px_fig = px_fig.to_plotly_json() + del go_fig["layout"]["template"] + del px_fig["layout"]["template"] + for key in go_fig['data'][0]: + assert_array_equal(go_fig['data'][0][key], px_fig['data'][0][key]) + for key in go_fig['layout']: + assert go_fig['layout'][key] == px_fig['layout'][key] + + +def test_pie_like_px(): + # Pie + labels = ['Oxygen','Hydrogen','Carbon_Dioxide','Nitrogen'] + values = [4500, 2500, 1053, 500] + + fig = px.pie(names=labels, values=values) + trace = go.Pie(labels=labels, values=values) + _compare_figures(trace, fig) + + labels = ["Eve", "Cain", "Seth", "Enos", "Noam", "Abel", "Awan", "Enoch", "Azura"] + parents = ["", "Eve", "Eve", "Seth", "Seth", "Eve", "Eve", "Awan", "Eve" ] + values = [10, 14, 12, 10, 2, 6, 6, 4, 4] + # Sunburst + fig = px.sunburst(names=labels, parents=parents, values=values) + trace = go.Sunburst(labels=labels, parents=parents, values=values) + _compare_figures(trace, fig) + # Treemap + fig = px.treemap(names=labels, parents=parents, values=values) + trace = go.Treemap(labels=labels, parents=parents, values=values) + _compare_figures(trace, fig) + + # Funnel + x = ['A', 'B', 'C'] + y = [3, 2, 1] + fig = px.funnel(y=y, x=x) + trace = go.Funnel(y=y, x=x) + _compare_figures(trace, fig) + # Funnelarea + fig = px.funnel_area(values=y, names=x) + trace = go.Funnelarea(values=y, labels=x) + _compare_figures(trace, fig) + From 6c5313b4a4b24b7bfc673e64c15aff4147f3a380 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 19 Nov 2019 16:39:26 -0500 Subject: [PATCH 06/15] doc --- packages/python/plotly/plotly/express/_doc.py | 4 ++-- .../tests/test_core/test_px/test_px_functions.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/python/plotly/plotly/express/_doc.py b/packages/python/plotly/plotly/express/_doc.py index f83a153642..8948e6b321 100644 --- a/packages/python/plotly/plotly/express/_doc.py +++ b/packages/python/plotly/plotly/express/_doc.py @@ -74,12 +74,12 @@ parents=[ colref_type, colref_desc, - "Values from this column or array_like are used to set values associated to sectors.", + "Values from this column or array_like are used as parents in sunburst and treemap charts.", ], ids=[ colref_type, colref_desc, - "Values from this column or array_like are used to set values associated to sectors.", + "Values from this column or array_like are used to set ids of sectors", ], lat=[ colref_type, diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py index 3b60ce797e..3070ac0225 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py @@ -2,6 +2,7 @@ import plotly.graph_objects as go from numpy.testing import assert_array_equal + def _compare_figures(go_trace, px_fig): """Compare a figure created with a go trace and a figure created with a px function call. Check that all values inside the go Figure are the @@ -12,15 +13,15 @@ def _compare_figures(go_trace, px_fig): px_fig = px_fig.to_plotly_json() del go_fig["layout"]["template"] del px_fig["layout"]["template"] - for key in go_fig['data'][0]: - assert_array_equal(go_fig['data'][0][key], px_fig['data'][0][key]) - for key in go_fig['layout']: - assert go_fig['layout'][key] == px_fig['layout'][key] + for key in go_fig["data"][0]: + assert_array_equal(go_fig["data"][0][key], px_fig["data"][0][key]) + for key in go_fig["layout"]: + assert go_fig["layout"][key] == px_fig["layout"][key] def test_pie_like_px(): # Pie - labels = ['Oxygen','Hydrogen','Carbon_Dioxide','Nitrogen'] + labels = ["Oxygen", "Hydrogen", "Carbon_Dioxide", "Nitrogen"] values = [4500, 2500, 1053, 500] fig = px.pie(names=labels, values=values) @@ -28,7 +29,7 @@ def test_pie_like_px(): _compare_figures(trace, fig) labels = ["Eve", "Cain", "Seth", "Enos", "Noam", "Abel", "Awan", "Enoch", "Azura"] - parents = ["", "Eve", "Eve", "Seth", "Seth", "Eve", "Eve", "Awan", "Eve" ] + parents = ["", "Eve", "Eve", "Seth", "Seth", "Eve", "Eve", "Awan", "Eve"] values = [10, 14, 12, 10, 2, 6, 6, 4, 4] # Sunburst fig = px.sunburst(names=labels, parents=parents, values=values) @@ -40,7 +41,7 @@ def test_pie_like_px(): _compare_figures(trace, fig) # Funnel - x = ['A', 'B', 'C'] + x = ["A", "B", "C"] y = [3, 2, 1] fig = px.funnel(y=y, x=x) trace = go.Funnel(y=y, x=x) @@ -49,4 +50,3 @@ def test_pie_like_px(): fig = px.funnel_area(values=y, names=x) trace = go.Funnelarea(values=y, labels=x) _compare_figures(trace, fig) - From 2e2444f604e85d38daa024b306f47150d74811ec Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 25 Nov 2019 13:10:02 -0500 Subject: [PATCH 07/15] continuous color scale for sunburst and treemap --- .../plotly/plotly/express/_chart_types.py | 16 ++++++++++- .../python/plotly/plotly/express/_core.py | 7 +++++ .../test_core/test_px/test_px_functions.py | 28 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/express/_chart_types.py b/packages/python/plotly/plotly/express/_chart_types.py index 691a5788d6..d9247bac36 100644 --- a/packages/python/plotly/plotly/express/_chart_types.py +++ b/packages/python/plotly/plotly/express/_chart_types.py @@ -1121,6 +1121,10 @@ def pie( data_frame=None, names=None, values=None, + color=None, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, textinfo=None, hover_name=None, hover_data=None, @@ -1160,6 +1164,10 @@ def sunburst( values=None, parents=None, ids=None, + color=None, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, hover_name=None, hover_data=None, custom_data=None, @@ -1191,6 +1199,10 @@ def treemap( values=None, parents=None, ids=None, + color=None, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, hover_name=None, hover_data=None, custom_data=None, @@ -1272,7 +1284,9 @@ def funnel_area( """ In a funnel area plot, each row of `data_frame` is represented as a trapezoidal sector of a funnel. """ - return make_figure(args=locals(), constructor=go.Funnelarea,) + return make_figure( + args=locals(), constructor=go.Funnelarea, trace_patch=dict(showlegend=True) + ) funnel_area.__doc__ = make_docstring( diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index dafc71b807..3bf6530bad 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -291,6 +291,13 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): result["z"] = g[v] result["coloraxis"] = "coloraxis1" mapping_labels[v_label] = "%{z}" + elif trace_spec.constructor in [go.Sunburst, go.Treemap]: + colorable = "marker" + if colorable not in result: + result[colorable] = dict() + result[colorable]["colors"] = g[v] + result[colorable]["coloraxis"] = "coloraxis1" + mapping_labels[v_label] = "%{color}" else: colorable = "marker" if trace_spec.constructor in [go.Parcats, go.Parcoords]: diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py index 3070ac0225..4265935e44 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py @@ -50,3 +50,31 @@ def test_pie_like_px(): fig = px.funnel_area(values=y, names=x) trace = go.Funnelarea(values=y, labels=x) _compare_figures(trace, fig) + + +def test_pie_like_colors(): + labels = ["Eve", "Cain", "Seth", "Enos", "Noam", "Abel", "Awan", "Enoch", "Azura"] + parents = ["", "Eve", "Eve", "Seth", "Seth", "Eve", "Eve", "Awan", "Eve"] + values = [10, 14, 12, 10, 2, 6, 6, 4, 4] + # Sunburst + fig = px.sunburst( + names=labels, + parents=parents, + values=values, + color=values, + color_continuous_scale="Viridis", + range_color=(5, 15), + ) + assert fig.layout.coloraxis.cmin, fig.layout.coloraxis.cmax == (5, 15) + assert fig.layout.coloraxis.colorscale[0] == (0.0, "#440154") + # Treemap + fig = px.treemap( + names=labels, + parents=parents, + values=values, + color=values, + color_continuous_scale="Viridis", + range_color=(5, 15), + ) + assert fig.layout.coloraxis.cmin, fig.layout.coloraxis.cmax == (5, 15) + assert fig.layout.coloraxis.colorscale[0] == (0.0, "#440154") From ae6221d7bfa4b956207d4fe1de62b7a51bdb96ad Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 26 Nov 2019 13:26:48 -0500 Subject: [PATCH 08/15] color scales for treemap and sunburst --- .../plotly/plotly/express/_chart_types.py | 9 ++- .../python/plotly/plotly/express/_core.py | 75 +++++++++++++++++-- 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/packages/python/plotly/plotly/express/_chart_types.py b/packages/python/plotly/plotly/express/_chart_types.py index d9247bac36..0b9f70defe 100644 --- a/packages/python/plotly/plotly/express/_chart_types.py +++ b/packages/python/plotly/plotly/express/_chart_types.py @@ -1122,9 +1122,8 @@ def pie( names=None, values=None, color=None, - color_continuous_scale=None, - range_color=None, - color_continuous_midpoint=None, + color_discrete_sequence=None, + color_discrete_map={}, textinfo=None, hover_name=None, hover_data=None, @@ -1168,6 +1167,8 @@ def sunburst( color_continuous_scale=None, range_color=None, color_continuous_midpoint=None, + color_discrete_sequence=None, + color_discrete_map={}, hover_name=None, hover_data=None, custom_data=None, @@ -1203,6 +1204,8 @@ def treemap( color_continuous_scale=None, range_color=None, color_continuous_midpoint=None, + color_discrete_sequence=None, + color_discrete_map={}, hover_name=None, hover_data=None, custom_data=None, diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 3bf6530bad..3bf60c2253 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -59,6 +59,19 @@ def get_trendline_results(fig): return fig._px_trendlines +def make_color_mapping(cat_list, discrete_colorscale): + mapping = {} + colors = [] + taken = 0 + length = len(discrete_colorscale) + for cat in cat_list: + if mapping.get(cat) is None: + mapping[cat] = discrete_colorscale[taken % length] + taken += 1 + colors.append(mapping[cat]) + return colors + + Mapping = namedtuple( "Mapping", [ @@ -295,9 +308,31 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): colorable = "marker" if colorable not in result: result[colorable] = dict() - result[colorable]["colors"] = g[v] - result[colorable]["coloraxis"] = "coloraxis1" - mapping_labels[v_label] = "%{color}" + print("ok") + if args.get("color_is_continuous"): + print( + "continuous scale", args["color_continuous_scale"], + ) + result[colorable]["colors"] = g[v] + result[colorable]["colorscale"] = args["color_continuous_scale"] + # result[colorable]["coloraxis"] = "coloraxis1" + mapping_labels[v_label] = "%{color}" + else: + print( + "discrete", + args["color_discrete_sequence"], + args.get("color_is_continuous"), + ) + result[colorable]["colors"] = make_color_mapping( + g[v], args["color_discrete_sequence"] + ) + elif trace_spec.constructor == go.Pie: + colorable = "marker" + if colorable not in result: + result[colorable] = dict() + result[colorable]["colors"] = make_color_mapping( + g[v], args["color_discrete_sequence"] + ) else: colorable = "marker" if trace_spec.constructor in [go.Parcats, go.Parcoords]: @@ -708,6 +743,16 @@ def one_group(x): def apply_default_cascade(args): # first we apply px.defaults to unspecified args + # If a discrete or a continuous colorscale is given then we do not set the other type + # This is used for Sunburst and Treemap which accept the two + # if ("color_discrete_sequence" in args and "color_continuous_scale" in args): + # if args["color_discrete_sequence"] is None and args["color_continuous_scale"] is None: + # for param in ["color_discrete_sequence", "color_continuous_scale"]: + # args[param] = getattr(defaults, param) + # else: + # if param in args and args[param] is None: + # args[param] = getattr(defaults, param) + for param in ( ["color_discrete_sequence", "color_continuous_scale"] + ["symbol_sequence", "line_dash_sequence", "template"] @@ -733,6 +778,9 @@ def apply_default_cascade(args): # if colors not set explicitly or in px.defaults, defer to a template # if the template doesn't have one, we set some final fallback defaults if "color_continuous_scale" in args: + if args["color_continuous_scale"] is not None: + print("True in cascade") + args["color_is_continuous"] = True if ( args["color_continuous_scale"] is None and args["template"].layout.colorscale.sequential @@ -744,6 +792,9 @@ def apply_default_cascade(args): args["color_continuous_scale"] = sequential.Viridis if "color_discrete_sequence" in args: + if args["color_discrete_sequence"] is not None: + print("False in cascade") + args["color_is_continuous"] = False if args["color_discrete_sequence"] is None and args["template"].layout.colorway: args["color_discrete_sequence"] = args["template"].layout.colorway if args["color_discrete_sequence"] is None: @@ -1024,14 +1075,26 @@ def infer_config(args, constructor, trace_patch): and args["data_frame"][args["color"]].dtype.kind in "bifc" ): attrs.append("color") + if not "color_is_continuous" in args: + print("True in infer 2") + args["color_is_continuous"] = True + elif constructor in [go.Sunburst, go.Treemap]: + attrs.append("color") else: - grouped_attrs.append("marker.color") + if constructor not in [go.Pie]: + grouped_attrs.append("marker.color") elif "line_group" in args or constructor == go.Histogram2dContour: grouped_attrs.append("line.color") - else: + elif constructor not in [go.Pie, go.Sunburst, go.Treemap]: grouped_attrs.append("marker.color") + else: + attrs.append("color") - show_colorbar = bool("color" in attrs and args["color"]) + show_colorbar = bool( + "color" in attrs + and args["color"] + and constructor not in [go.Pie, go.Sunburst, go.Treemap] + ) else: show_colorbar = False From 861b0661e219853a6aa20f6fbdbced6c6c72a595 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 26 Nov 2019 13:55:55 -0500 Subject: [PATCH 09/15] colorway --- .../python/plotly/plotly/express/_chart_types.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/express/_chart_types.py b/packages/python/plotly/plotly/express/_chart_types.py index 0b9f70defe..80a802549e 100644 --- a/packages/python/plotly/plotly/express/_chart_types.py +++ b/packages/python/plotly/plotly/express/_chart_types.py @@ -1139,8 +1139,13 @@ def pie( """ In a pie plot, each row of `data_frame` is represented as a sector of a pie. """ + if color_discrete_sequence is not None: + layout_patch = {"piecolorway": color_discrete_sequence} return make_figure( - args=locals(), constructor=go.Pie, trace_patch=dict(showlegend=True, hole=hole) + args=locals(), + constructor=go.Pie, + trace_patch=dict(showlegend=True, hole=hole), + layout_patch=layout_patch, ) @@ -1184,10 +1189,13 @@ def sunburst( A sunburst plot represents hierarchial data as sectors laid out over several levels of concentric rings. """ + if color_discrete_sequence is not None: + layout_patch = {"sunburstcolorway": color_discrete_sequence} return make_figure( args=locals(), constructor=go.Sunburst, trace_patch=dict(branchvalues=branchvalues, maxdepth=maxdepth), + layout_patch=layout_patch, ) @@ -1220,10 +1228,13 @@ def treemap( """ A treemap plot represents hierarchial data as nested rectangular sectors. """ + if color_discrete_sequence is not None: + layout_patch = {"treemapcolorway": color_discrete_sequence} return make_figure( args=locals(), constructor=go.Treemap, trace_patch=dict(branchvalues=branchvalues, maxdepth=maxdepth), + layout_patch=layout_patch, ) From e2902b4dcd1d3a177b1f93ce65c237c6b9721d2d Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 26 Nov 2019 16:00:09 -0500 Subject: [PATCH 10/15] added tests --- .../test_core/test_px/test_px_functions.py | 63 ++++++++++++------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py index 4265935e44..263f493bc9 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py @@ -1,6 +1,7 @@ import plotly.express as px import plotly.graph_objects as go from numpy.testing import assert_array_equal +import numpy as np def _compare_figures(go_trace, px_fig): @@ -52,29 +53,45 @@ def test_pie_like_px(): _compare_figures(trace, fig) -def test_pie_like_colors(): +def test_sunburst_treemap_colorscales(): labels = ["Eve", "Cain", "Seth", "Enos", "Noam", "Abel", "Awan", "Enoch", "Azura"] parents = ["", "Eve", "Eve", "Seth", "Seth", "Eve", "Eve", "Awan", "Eve"] values = [10, 14, 12, 10, 2, 6, 6, 4, 4] - # Sunburst - fig = px.sunburst( - names=labels, - parents=parents, - values=values, - color=values, - color_continuous_scale="Viridis", - range_color=(5, 15), - ) - assert fig.layout.coloraxis.cmin, fig.layout.coloraxis.cmax == (5, 15) - assert fig.layout.coloraxis.colorscale[0] == (0.0, "#440154") - # Treemap - fig = px.treemap( - names=labels, - parents=parents, - values=values, - color=values, - color_continuous_scale="Viridis", - range_color=(5, 15), - ) - assert fig.layout.coloraxis.cmin, fig.layout.coloraxis.cmax == (5, 15) - assert fig.layout.coloraxis.colorscale[0] == (0.0, "#440154") + for func, colorway in zip( + [px.sunburst, px.treemap], ["sunburstcolorway", "treemapcolorway"] + ): + # Continuous colorscale + fig = func( + names=labels, + parents=parents, + values=values, + color=values, + color_continuous_scale="Viridis", + range_color=(5, 15), + ) + assert fig.layout.coloraxis.cmin, fig.layout.coloraxis.cmax == (5, 15) + # Discrete colorscale, color arg passed + color_seq = px.colors.sequential.Reds + fig = func( + names=labels, + parents=parents, + values=values, + color=labels, + color_discrete_sequence=color_seq, + ) + assert np.all([col in color_seq for col in fig.data[0].marker.colors]) + # Numerical color arg passed, fall back to continuous + fig = func(names=labels, parents=parents, values=values, color=values,) + assert [ + el[0] == px.colors.sequential.Viridis + for i, el in enumerate(fig.layout.coloraxis.colorscale) + ] + # Discrete colorscale, no color arg passed + color_seq = px.colors.sequential.Reds + fig = func( + names=labels, + parents=parents, + values=values, + color_discrete_sequence=color_seq, + ) + assert list(fig.layout[colorway]) == color_seq From b9f90849558b885457b1a1ede2ae8fa5e56fe4ec Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 26 Nov 2019 16:16:42 -0500 Subject: [PATCH 11/15] added funnelarea --- .../plotly/plotly/express/_chart_types.py | 21 ++++++++++++-- .../python/plotly/plotly/express/_core.py | 28 +++++++------------ .../test_core/test_px/test_px_functions.py | 21 ++++++++++++++ 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/packages/python/plotly/plotly/express/_chart_types.py b/packages/python/plotly/plotly/express/_chart_types.py index 80a802549e..0a04575fdb 100644 --- a/packages/python/plotly/plotly/express/_chart_types.py +++ b/packages/python/plotly/plotly/express/_chart_types.py @@ -1141,6 +1141,8 @@ def pie( """ if color_discrete_sequence is not None: layout_patch = {"piecolorway": color_discrete_sequence} + else: + layout_patch = {} return make_figure( args=locals(), constructor=go.Pie, @@ -1191,6 +1193,8 @@ def sunburst( """ if color_discrete_sequence is not None: layout_patch = {"sunburstcolorway": color_discrete_sequence} + else: + layout_patch = {} return make_figure( args=locals(), constructor=go.Sunburst, @@ -1230,6 +1234,8 @@ def treemap( """ if color_discrete_sequence is not None: layout_patch = {"treemapcolorway": color_discrete_sequence} + else: + layout_patch = {} return make_figure( args=locals(), constructor=go.Treemap, @@ -1283,8 +1289,11 @@ def funnel( def funnel_area( data_frame=None, - values=None, names=None, + values=None, + color=None, + color_discrete_sequence=None, + color_discrete_map={}, textinfo=None, hover_name=None, hover_data=None, @@ -1294,12 +1303,20 @@ def funnel_area( template=None, width=None, height=None, + opacity=None, ): """ In a funnel area plot, each row of `data_frame` is represented as a trapezoidal sector of a funnel. """ + if color_discrete_sequence is not None: + layout_patch = {"funnelareacolorway": color_discrete_sequence} + else: + layout_patch = {} return make_figure( - args=locals(), constructor=go.Funnelarea, trace_patch=dict(showlegend=True) + args=locals(), + constructor=go.Funnelarea, + trace_patch=dict(showlegend=True), + layout_patch=layout_patch, ) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 3bf60c2253..724d1344e6 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -308,25 +308,16 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): colorable = "marker" if colorable not in result: result[colorable] = dict() - print("ok") + # Other if/else block if args.get("color_is_continuous"): - print( - "continuous scale", args["color_continuous_scale"], - ) result[colorable]["colors"] = g[v] - result[colorable]["colorscale"] = args["color_continuous_scale"] - # result[colorable]["coloraxis"] = "coloraxis1" + result[colorable]["coloraxis"] = "coloraxis1" mapping_labels[v_label] = "%{color}" else: - print( - "discrete", - args["color_discrete_sequence"], - args.get("color_is_continuous"), - ) result[colorable]["colors"] = make_color_mapping( g[v], args["color_discrete_sequence"] ) - elif trace_spec.constructor == go.Pie: + elif trace_spec.constructor in [go.Pie, go.Funnelarea]: colorable = "marker" if colorable not in result: result[colorable] = dict() @@ -779,7 +770,6 @@ def apply_default_cascade(args): # if the template doesn't have one, we set some final fallback defaults if "color_continuous_scale" in args: if args["color_continuous_scale"] is not None: - print("True in cascade") args["color_is_continuous"] = True if ( args["color_continuous_scale"] is None @@ -793,7 +783,6 @@ def apply_default_cascade(args): if "color_discrete_sequence" in args: if args["color_discrete_sequence"] is not None: - print("False in cascade") args["color_is_continuous"] = False if args["color_discrete_sequence"] is None and args["template"].layout.colorway: args["color_discrete_sequence"] = args["template"].layout.colorway @@ -1076,16 +1065,15 @@ def infer_config(args, constructor, trace_patch): ): attrs.append("color") if not "color_is_continuous" in args: - print("True in infer 2") args["color_is_continuous"] = True elif constructor in [go.Sunburst, go.Treemap]: attrs.append("color") else: - if constructor not in [go.Pie]: + if constructor not in [go.Pie, go.Funnelarea]: grouped_attrs.append("marker.color") elif "line_group" in args or constructor == go.Histogram2dContour: grouped_attrs.append("line.color") - elif constructor not in [go.Pie, go.Sunburst, go.Treemap]: + elif constructor not in [go.Pie, go.Funnelarea]: grouped_attrs.append("marker.color") else: attrs.append("color") @@ -1093,7 +1081,11 @@ def infer_config(args, constructor, trace_patch): show_colorbar = bool( "color" in attrs and args["color"] - and constructor not in [go.Pie, go.Sunburst, go.Treemap] + and constructor not in [go.Pie, go.Funnelarea] + and ( + constructor not in [go.Treemap, go.Sunburst] + or args.get("color_is_continuous") + ) ) else: show_colorbar = False diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py index 263f493bc9..1d40850a7d 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py @@ -95,3 +95,24 @@ def test_sunburst_treemap_colorscales(): color_discrete_sequence=color_seq, ) assert list(fig.layout[colorway]) == color_seq + + +def test_pie_funnelarea_colorscale(): + labels = ["A", "B", "C", "D"] + values = [3, 2, 1, 4] + for func, colorway in zip( + [px.sunburst, px.treemap], ["sunburstcolorway", "treemapcolorway"] + ): + # Discrete colorscale, no color arg passed + color_seq = px.colors.sequential.Reds + fig = func(names=labels, values=values, color_discrete_sequence=color_seq,) + assert list(fig.layout[colorway]) == color_seq + # Discrete colorscale, color arg passed + color_seq = px.colors.sequential.Reds + fig = func( + names=labels, + values=values, + color=labels, + color_discrete_sequence=color_seq, + ) + assert np.all([col in color_seq for col in fig.data[0].marker.colors]) From 947e7c28119b5dc523e514a7dd102d5876f69409 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 26 Nov 2019 23:01:39 -0500 Subject: [PATCH 12/15] minor improvements --- .../plotly/plotly/express/_chart_types.py | 30 +++++++++++++++---- .../python/plotly/plotly/express/_core.py | 17 ++--------- .../test_core/test_px/test_px_functions.py | 25 ++++++++++++++++ 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/packages/python/plotly/plotly/express/_chart_types.py b/packages/python/plotly/plotly/express/_chart_types.py index 0a04575fdb..4dc35becec 100644 --- a/packages/python/plotly/plotly/express/_chart_types.py +++ b/packages/python/plotly/plotly/express/_chart_types.py @@ -1252,19 +1252,37 @@ def funnel( x=None, y=None, color=None, - color_discrete_sequence=None, - color_discrete_map={}, - orientation=None, - textinfo=None, + facet_row=None, + facet_col=None, + facet_col_wrap=0, hover_name=None, hover_data=None, custom_data=None, + text=None, + error_x=None, + error_x_minus=None, + error_y=None, + error_y_minus=None, + animation_frame=None, + animation_group=None, + category_orders={}, labels={}, + color_discrete_sequence=None, + color_discrete_map={}, + color_continuous_scale=None, + range_color=None, + color_continuous_midpoint=None, + opacity=None, + orientation="h", + barmode="relative", + log_x=False, + log_y=False, + range_x=None, + range_y=None, title=None, template=None, width=None, height=None, - opacity=None, ): """ In a funnel plot, each row of `data_frame` is represented as a rectangular sector of a funnel. @@ -1272,7 +1290,7 @@ def funnel( return make_figure( args=locals(), constructor=go.Funnel, - trace_patch=dict(opacity=opacity, orientation=orientation, textinfo=textinfo), + trace_patch=dict(opacity=opacity, orientation=orientation), ) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 724d1344e6..f848fdfcff 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -734,15 +734,6 @@ def one_group(x): def apply_default_cascade(args): # first we apply px.defaults to unspecified args - # If a discrete or a continuous colorscale is given then we do not set the other type - # This is used for Sunburst and Treemap which accept the two - # if ("color_discrete_sequence" in args and "color_continuous_scale" in args): - # if args["color_discrete_sequence"] is None and args["color_continuous_scale"] is None: - # for param in ["color_discrete_sequence", "color_continuous_scale"]: - # args[param] = getattr(defaults, param) - # else: - # if param in args and args[param] is None: - # args[param] = getattr(defaults, param) for param in ( ["color_discrete_sequence", "color_continuous_scale"] @@ -769,8 +760,6 @@ def apply_default_cascade(args): # if colors not set explicitly or in px.defaults, defer to a template # if the template doesn't have one, we set some final fallback defaults if "color_continuous_scale" in args: - if args["color_continuous_scale"] is not None: - args["color_is_continuous"] = True if ( args["color_continuous_scale"] is None and args["template"].layout.colorscale.sequential @@ -782,8 +771,6 @@ def apply_default_cascade(args): args["color_continuous_scale"] = sequential.Viridis if "color_discrete_sequence" in args: - if args["color_discrete_sequence"] is not None: - args["color_is_continuous"] = False if args["color_discrete_sequence"] is None and args["template"].layout.colorway: args["color_discrete_sequence"] = args["template"].layout.colorway if args["color_discrete_sequence"] is None: @@ -1064,10 +1051,10 @@ def infer_config(args, constructor, trace_patch): and args["data_frame"][args["color"]].dtype.kind in "bifc" ): attrs.append("color") - if not "color_is_continuous" in args: - args["color_is_continuous"] = True + args["color_is_continuous"] = True elif constructor in [go.Sunburst, go.Treemap]: attrs.append("color") + args["color_is_continuous"] = False else: if constructor not in [go.Pie, go.Funnelarea]: grouped_attrs.append("marker.color") diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py index 1d40850a7d..c61d8f2d76 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py @@ -86,6 +86,20 @@ def test_sunburst_treemap_colorscales(): el[0] == px.colors.sequential.Viridis for i, el in enumerate(fig.layout.coloraxis.colorscale) ] + # Numerical color arg passed, continuous colorscale + # even if color_discrete_sequence if passed + fig = func( + names=labels, + parents=parents, + values=values, + color=values, + color_discrete_sequence=color_seq, + ) + assert [ + el[0] == px.colors.sequential.Viridis + for i, el in enumerate(fig.layout.coloraxis.colorscale) + ] + # Discrete colorscale, no color arg passed color_seq = px.colors.sequential.Reds fig = func( @@ -116,3 +130,14 @@ def test_pie_funnelarea_colorscale(): color_discrete_sequence=color_seq, ) assert np.all([col in color_seq for col in fig.data[0].marker.colors]) + + +def test_funnel(): + fig = px.funnel(x=[5, 4, 3], y=["A", "B", "C"]) + assert fig.data[0].marker.color == "#636efa" + fig = px.funnel( + x=[5, 4, 3, 3, 2, 1], + y=["A", "B", "C", "A", "B", "C"], + color=["0", "0", "0", "1", "1", "1"], + ) + assert len(fig.data) == 2 From ea878471752b6beb4774c321ddaf123e401ad0a8 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Wed, 27 Nov 2019 09:05:38 -0500 Subject: [PATCH 13/15] removed test --- .../plotly/plotly/tests/test_core/test_px/test_px_functions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py index c61d8f2d76..339accf9d5 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py +++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py @@ -133,8 +133,6 @@ def test_pie_funnelarea_colorscale(): def test_funnel(): - fig = px.funnel(x=[5, 4, 3], y=["A", "B", "C"]) - assert fig.data[0].marker.color == "#636efa" fig = px.funnel( x=[5, 4, 3, 3, 2, 1], y=["A", "B", "C", "A", "B", "C"], From 9d6360a635453be91571fea6660103ec6f52cd24 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Wed, 27 Nov 2019 22:32:06 -0500 Subject: [PATCH 14/15] tweaks to pie/funnel --- .../plotly/plotly/express/_chart_types.py | 38 +++---------- .../python/plotly/plotly/express/_core.py | 53 ++++++++----------- 2 files changed, 29 insertions(+), 62 deletions(-) diff --git a/packages/python/plotly/plotly/express/_chart_types.py b/packages/python/plotly/plotly/express/_chart_types.py index 4dc35becec..ab52c5ab0a 100644 --- a/packages/python/plotly/plotly/express/_chart_types.py +++ b/packages/python/plotly/plotly/express/_chart_types.py @@ -1124,7 +1124,6 @@ def pie( color=None, color_discrete_sequence=None, color_discrete_map={}, - textinfo=None, hover_name=None, hover_data=None, custom_data=None, @@ -1146,7 +1145,7 @@ def pie( return make_figure( args=locals(), constructor=go.Pie, - trace_patch=dict(showlegend=True, hole=hole), + trace_patch=dict(showlegend=(names is not None), hole=hole), layout_patch=layout_patch, ) @@ -1154,7 +1153,6 @@ def pie( pie.__doc__ = make_docstring( pie, override_dict=dict( - textinfo=["str", "Determines which trace information appear on the graph.",], hole=[ "float", "Sets the fraction of the radius to cut out of the pie." @@ -1230,7 +1228,7 @@ def treemap( maxdepth=None, ): """ - A treemap plot represents hierarchial data as nested rectangular sectors. + A treemap plot represents hierarchial data as nested rectangular sectors. """ if color_discrete_sequence is not None: layout_patch = {"treemapcolorway": color_discrete_sequence} @@ -1259,22 +1257,14 @@ def funnel( hover_data=None, custom_data=None, text=None, - error_x=None, - error_x_minus=None, - error_y=None, - error_y_minus=None, animation_frame=None, animation_group=None, category_orders={}, labels={}, color_discrete_sequence=None, color_discrete_map={}, - color_continuous_scale=None, - range_color=None, - color_continuous_midpoint=None, opacity=None, orientation="h", - barmode="relative", log_x=False, log_y=False, range_x=None, @@ -1294,15 +1284,7 @@ def funnel( ) -funnel.__doc__ = make_docstring( - funnel, - override_dict=dict( - textinfo=[ - "str", - "Determines which trace information appear on the graph. In the case of having multiple funnels, percentages & totals are computed separately (per trace).", - ] - ), -) +funnel.__doc__ = make_docstring(funnel) def funnel_area( @@ -1312,7 +1294,6 @@ def funnel_area( color=None, color_discrete_sequence=None, color_discrete_map={}, - textinfo=None, hover_name=None, hover_data=None, custom_data=None, @@ -1333,17 +1314,10 @@ def funnel_area( return make_figure( args=locals(), constructor=go.Funnelarea, - trace_patch=dict(showlegend=True), + trace_patch=dict(showlegend=(names is not None)), layout_patch=layout_patch, ) -funnel_area.__doc__ = make_docstring( - funnel_area, - override_dict=dict( - textinfo=[ - "str", - "Determines which trace information appear on the graph. In the case of having multiple funnels, percentages & totals are computed separately (per trace).", - ] - ), -) +funnel_area.__doc__ = make_docstring(funnel_area) + diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index f848fdfcff..2052fe57ef 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -59,19 +59,6 @@ def get_trendline_results(fig): return fig._px_trendlines -def make_color_mapping(cat_list, discrete_colorscale): - mapping = {} - colors = [] - taken = 0 - length = len(discrete_colorscale) - for cat in cat_list: - if mapping.get(cat) is None: - mapping[cat] = discrete_colorscale[taken % length] - taken += 1 - colors.append(mapping[cat]) - return colors - - Mapping = namedtuple( "Mapping", [ @@ -304,26 +291,28 @@ def make_trace_kwargs(args, trace_spec, g, mapping_labels, sizeref): result["z"] = g[v] result["coloraxis"] = "coloraxis1" mapping_labels[v_label] = "%{z}" - elif trace_spec.constructor in [go.Sunburst, go.Treemap]: - colorable = "marker" - if colorable not in result: - result[colorable] = dict() - # Other if/else block + elif trace_spec.constructor in [ + go.Sunburst, + go.Treemap, + go.Pie, + go.Funnelarea, + ]: + if "marker" not in result: + result["marker"] = dict() + if args.get("color_is_continuous"): - result[colorable]["colors"] = g[v] - result[colorable]["coloraxis"] = "coloraxis1" + result["marker"]["colors"] = g[v] + result["marker"]["coloraxis"] = "coloraxis1" mapping_labels[v_label] = "%{color}" else: - result[colorable]["colors"] = make_color_mapping( - g[v], args["color_discrete_sequence"] - ) - elif trace_spec.constructor in [go.Pie, go.Funnelarea]: - colorable = "marker" - if colorable not in result: - result[colorable] = dict() - result[colorable]["colors"] = make_color_mapping( - g[v], args["color_discrete_sequence"] - ) + result["marker"]["colors"] = [] + mapping = {} + for cat in g[v]: + if mapping.get(cat) is None: + mapping[cat] = args["color_discrete_sequence"][ + len(mapping) % len(args["color_discrete_sequence"]) + ] + result["marker"]["colors"].append(mapping[cat]) else: colorable = "marker" if trace_spec.constructor in [go.Parcats, go.Parcoords]: @@ -1064,6 +1053,10 @@ def infer_config(args, constructor, trace_patch): grouped_attrs.append("marker.color") else: attrs.append("color") + if constructor in [go.Pie, go.Funnelarea]: + if args["hover_data"] is None: + args["hover_data"] = [] + args["hover_data"].append(args["color"]) show_colorbar = bool( "color" in attrs From 0dd2f39c868d50d660c7b24f45e74aa1732e0472 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Thu, 28 Nov 2019 09:27:36 -0500 Subject: [PATCH 15/15] fix tests --- packages/python/plotly/plotly/express/_chart_types.py | 1 - packages/python/plotly/plotly/express/_core.py | 11 +++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/python/plotly/plotly/express/_chart_types.py b/packages/python/plotly/plotly/express/_chart_types.py index ab52c5ab0a..8cbd4d85b6 100644 --- a/packages/python/plotly/plotly/express/_chart_types.py +++ b/packages/python/plotly/plotly/express/_chart_types.py @@ -1320,4 +1320,3 @@ def funnel_area( funnel_area.__doc__ = make_docstring(funnel_area) - diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 2052fe57ef..923e6ea3df 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1045,18 +1045,17 @@ def infer_config(args, constructor, trace_patch): attrs.append("color") args["color_is_continuous"] = False else: - if constructor not in [go.Pie, go.Funnelarea]: - grouped_attrs.append("marker.color") + grouped_attrs.append("marker.color") elif "line_group" in args or constructor == go.Histogram2dContour: grouped_attrs.append("line.color") - elif constructor not in [go.Pie, go.Funnelarea]: - grouped_attrs.append("marker.color") - else: + elif constructor in [go.Pie, go.Funnelarea]: attrs.append("color") - if constructor in [go.Pie, go.Funnelarea]: + if args["color"]: if args["hover_data"] is None: args["hover_data"] = [] args["hover_data"].append(args["color"]) + else: + grouped_attrs.append("marker.color") show_colorbar = bool( "color" in attrs