Skip to content

Commit

Permalink
Fix reversed color scale issues in annotated heatmap figure factory (#…
Browse files Browse the repository at this point in the history
…1251)

* Fix reversed color scale in annotated heatmap.  Currently, `reversescale` does not get included in the `trace` dictionary. Therefore, the color scale doesn't change when setting it to `True` (although the color of annotations gets inverted).
* Small refactor to pull out black/white colors as variables and add should_use_black_text function to hold color contrast equation
* Handle reversescale when passing in a custom colorscale
  • Loading branch information
jonmmease authored Oct 30, 2018
1 parent c935193 commit 32b6701
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 18 deletions.
51 changes: 33 additions & 18 deletions plotly/figure_factory/_annotated_heatmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)',
Expand All @@ -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
Expand Down Expand Up @@ -173,39 +180,47 @@ 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],
[255, 255, 255])
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):
Expand Down
81 changes: 81 additions & 0 deletions plotly/tests/test_optional/test_tools/test_figure_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'},
Expand Down Expand Up @@ -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'],
Expand Down Expand Up @@ -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):

Expand Down

0 comments on commit 32b6701

Please sign in to comment.