diff --git a/plotly/figure_factory/_annotated_heatmap.py b/plotly/figure_factory/_annotated_heatmap.py index b8ba35aa0df..3400aba0e48 100644 --- a/plotly/figure_factory/_annotated_heatmap.py +++ b/plotly/figure_factory/_annotated_heatmap.py @@ -64,6 +64,7 @@ def create_annotated_heatmap(z, x=None, y=None, annotation_text=None, defined, the colors are defined logically as black or white depending on the heatmap's colorscale. :param (bool) showscale: Display colorscale. Default = False + :param (bool) reversescale: Reverse colorscale. Default = False :param kwargs: kwargs passed through plotly.graph_objs.Heatmap. These kwargs describe other attributes about the annotated Heatmap trace such as the colorscale. For more information on valid kwargs @@ -98,14 +99,14 @@ def create_annotated_heatmap(z, x=None, y=None, annotation_text=None, if x or y: trace = dict(type='heatmap', z=z, x=x, y=y, colorscale=colorscale, - showscale=showscale, **kwargs) + showscale=showscale, reversescale=reversescale, **kwargs) layout = dict(annotations=annotations, xaxis=dict(ticks='', dtick=1, side='top', gridcolor='rgb(0, 0, 0)'), yaxis=dict(ticks='', dtick=1, ticksuffix=' ')) else: trace = dict(type='heatmap', z=z, colorscale=colorscale, - showscale=showscale, **kwargs) + showscale=showscale, reversescale=reversescale, **kwargs) layout = dict(annotations=annotations, xaxis=dict(ticks='', side='top', gridcolor='rgb(0, 0, 0)', @@ -127,6 +128,12 @@ def to_rgb_color_list(color_str, default): return default +def should_use_black_text(background_color): + return (background_color[0] * 0.299 + + background_color[1] * 0.587 + + background_color[2] * 0.114) > 186 + + class _AnnotatedHeatmap(object): """ Refer to TraceFactory.create_annotated_heatmap() for docstring @@ -173,21 +180,24 @@ def get_text_color(self): 'Earth', 'Electric', 'Viridis', 'Cividis'] # Plotly colorscales ranging from a darker shade to a lighter shade colorscales_reverse = ['Reds'] + + white = '#FFFFFF' + black = '#000000' if self.font_colors: min_text_color = self.font_colors[0] max_text_color = self.font_colors[-1] elif self.colorscale in colorscales and self.reversescale: - min_text_color = '#000000' - max_text_color = '#FFFFFF' + min_text_color = black + max_text_color = white elif self.colorscale in colorscales: - min_text_color = '#FFFFFF' - max_text_color = '#000000' + min_text_color = white + max_text_color = black elif self.colorscale in colorscales_reverse and self.reversescale: - min_text_color = '#FFFFFF' - max_text_color = '#000000' + min_text_color = white + max_text_color = black elif self.colorscale in colorscales_reverse: - min_text_color = '#000000' - max_text_color = '#FFFFFF' + min_text_color = black + max_text_color = white elif isinstance(self.colorscale, list): min_col = to_rgb_color_list(self.colorscale[0][1], @@ -195,17 +205,22 @@ def get_text_color(self): max_col = to_rgb_color_list(self.colorscale[-1][1], [255, 255, 255]) - if (min_col[0]*0.299 + min_col[1]*0.587 + min_col[2]*0.114) > 186: - min_text_color = '#000000' + # swap min/max colors if reverse scale + if self.reversescale: + min_col, max_col = max_col, min_col + + if should_use_black_text(min_col): + min_text_color = black else: - min_text_color = '#FFFFFF' - if (max_col[0]*0.299 + max_col[1]*0.587 + max_col[2]*0.114) > 186: - max_text_color = '#000000' + min_text_color = white + + if should_use_black_text(max_col): + max_text_color = black else: - max_text_color = '#FFFFFF' + max_text_color = white else: - min_text_color = '#000000' - max_text_color = '#000000' + min_text_color = black + max_text_color = black return min_text_color, max_text_color def get_z_mid(self): diff --git a/plotly/tests/test_optional/test_tools/test_figure_factory.py b/plotly/tests/test_optional/test_tools/test_figure_factory.py index e9a11ff0ae6..36ae36eaf9e 100644 --- a/plotly/tests/test_optional/test_tools/test_figure_factory.py +++ b/plotly/tests/test_optional/test_tools/test_figure_factory.py @@ -756,6 +756,7 @@ def test_simple_annotated_heatmap(self): expected_a_heat = { 'data': [{'colorscale': 'RdBu', 'showscale': False, + 'reversescale': False, 'type': 'heatmap', 'z': [[1, 0, 0.5], [0.25, 0.75, 0.45]]}], 'layout': {'annotations': [{'font': {'color': '#000000'}, @@ -831,6 +832,7 @@ def test_annotated_heatmap_kwargs(self): expected_a = {'data': [{'colorscale': [[0, 'rgb(255,255,255)'], [1, '#e6005a']], 'showscale': False, + 'reversescale': False, 'type': 'heatmap', 'x': ['A', 'B'], 'y': ['One', 'Two', 'Three'], @@ -891,6 +893,85 @@ def test_annotated_heatmap_kwargs(self): self.assert_fig_equal(a['layout'], expected_a['layout']) + def test_annotated_heatmap_reversescale(self): + + # we should be able to create an annotated heatmap with x and y axes + # lables, a defined colorscale, and supplied text. + + z = [[1, 0], [.25, .75], [.45, .5]] + text = [['first', 'second'], ['third', 'fourth'], ['fifth', 'sixth']] + a = ff.create_annotated_heatmap(z, + x=['A', 'B'], + y=['One', 'Two', 'Three'], + annotation_text=text, + reversescale=True, + colorscale=[[0, 'rgb(255,255,255)'], + [1, '#e6005a']]) + expected_a = {'data': [{'colorscale': + [[0, 'rgb(255,255,255)'], [1, '#e6005a']], + 'showscale': False, + 'reversescale': True, + 'type': 'heatmap', + 'x': ['A', 'B'], + 'y': ['One', 'Two', 'Three'], + 'z': [[1, 0], [0.25, 0.75], [0.45, 0.5]]}], + 'layout': {'annotations': [ + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'first', + 'x': 'A', + 'xref': 'x', + 'y': 'One', + 'yref': 'y'}, + {'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': 'second', + 'x': 'B', + 'xref': 'x', + 'y': 'One', + 'yref': 'y'}, + {'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': 'third', + 'x': 'A', + 'xref': 'x', + 'y': 'Two', + 'yref': 'y'}, + {'font': {'color': '#000000'}, + 'showarrow': False, + 'text': 'fourth', + 'x': 'B', + 'xref': 'x', + 'y': 'Two', + 'yref': 'y'}, + {'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': 'fifth', + 'x': 'A', + 'xref': 'x', + 'y': 'Three', + 'yref': 'y'}, + {'font': {'color': '#FFFFFF'}, + 'showarrow': False, + 'text': 'sixth', + 'x': 'B', + 'xref': 'x', + 'y': 'Three', + 'yref': 'y'}], + 'xaxis': {'dtick': 1, + 'gridcolor': 'rgb(0, 0, 0)', + 'side': 'top', + 'ticks': ''}, + 'yaxis': {'dtick': 1, 'ticks': '', + 'ticksuffix': ' '}}} + self.assert_fig_equal( + a['data'][0], + expected_a['data'][0], + ) + + self.assert_fig_equal(a['layout'], + expected_a['layout']) + class TestTable(TestCase, NumpyTestUtilsMixin):