Skip to content

Commit 93e334b

Browse files
committed
Add tables and annotated heatmaps to FigureFactory
- add FF option for annotated heatmaps with logical text colouring - users can supply a matrix of text for annotations otherwise the value from the z matrix is used - add FF option to create a table (defaults to striped table) - input options are a z matrix or a pandas data frame - option for user to supply table colours and font colours
1 parent 06e0be5 commit 93e334b

File tree

1 file changed

+317
-4
lines changed

1 file changed

+317
-4
lines changed

plotly/tools.py

Lines changed: 317 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ def warning_on_one_line(message, category, filename, lineno,
4949
except ImportError:
5050
_numpy_imported = False
5151

52+
try:
53+
import pandas as pd
54+
_pandas_imported = True
55+
except ImportError:
56+
_pandas_imported = False
57+
5258
try:
5359
import scipy as scp
5460
_scipy_imported = True
@@ -1428,10 +1434,12 @@ class FigureFactory(object):
14281434
without notice.
14291435
14301436
Supported chart types include candlestick, open high low close, quiver,
1431-
and streamline. See FigureFactory.create_candlestick,
1432-
FigureFactory.create_ohlc, FigureFactory.create_quiver, or
1433-
FigureFactory.create_streamline for for more infomation and examples of a
1434-
specific chart type.
1437+
streamline, distplot, dendrogram, annotated heatmap, and tables. See
1438+
FigureFactory.create_candlestick, FigureFactory.create_ohlc,
1439+
FigureFactory.create_quiver, FigureFactory.create_streamline,
1440+
FigureFactory.create_distplot, FigureFactory.create_dendrogram,
1441+
FigureFactory.create_annotated_heatmap, or FigureFactory.create_table for
1442+
more infomation and examples of a specific chart type.
14351443
"""
14361444

14371445
@staticmethod
@@ -1585,6 +1593,13 @@ def _flatten(array):
15851593
"flattened! Make sure your data is "
15861594
"entered as lists or ndarrays!")
15871595

1596+
@staticmethod
1597+
def _hex_to_rgb(value):
1598+
value = value.lstrip('#')
1599+
lv = len(value)
1600+
return tuple(int(value[i:i + lv // 3], 16)
1601+
for i in range(0, lv, lv // 3))
1602+
15881603
@staticmethod
15891604
def create_quiver(x, y, u, v, scale=.1, arrow_scale=.3,
15901605
angle=math.pi / 9, **kwargs):
@@ -2509,6 +2524,107 @@ def create_dendrogram(X, orientation="bottom", labels=None,
25092524
return {'layout': dendrogram.layout,
25102525
'data': dendrogram.data}
25112526

2527+
@staticmethod
2528+
def create_annotated_heatmap(z, x=None, y=None, text=None,
2529+
fontcolor=None, showscale=False,
2530+
**kwargs):
2531+
"""
2532+
BETA function that creates annotated heatmaps
2533+
2534+
The distplot can be composed of all or any combination of the following
2535+
3 components: (1) histogram, (2) curve: (a) kernal density estimation
2536+
or (b) normal curve, and (3) rug plot. Additionally, multiple distplots
2537+
(from multiple datasets) can be created in the same plot.
2538+
2539+
:param (list[list]) z: z matrix to create heatmap.
2540+
:param (list) x: x labels. Default = None
2541+
:param (list) y: y labels. Default = None
2542+
:param (list[list]) text: Text for annotations. Should be the same
2543+
dimmensions as the z matrix. If no text is added, the the values of
2544+
the z matrix are annotated. Default = None
2545+
:param (list) font_color: Add histogram to distplot? Default = True
2546+
:param (bool) showscale: Display colorscale. Default = False
2547+
:param kwargs: kwargs passed through plotly.graph_objs.Heatmap.
2548+
These kwargs describe other attributes about the annotated Heatmap
2549+
trace such as the colorscale. For more information on valid kwargs
2550+
call help(plotly.graph_objs.Heatmap)
2551+
2552+
Example 1: Simple distplot of 1 data set
2553+
```
2554+
import plotly.plotly as py
2555+
from plotly.tools import FigureFactory as FF
2556+
```
2557+
"""
2558+
# TODO: protected until #282
2559+
from plotly.graph_objs import graph_objs
2560+
# FigureFactory._validate_annotated_heatmap()
2561+
annotations = _AnnotatedHeatmap(z, x, y, text, fontcolor,
2562+
**kwargs).make_annotations()
2563+
2564+
if x and y:
2565+
trace = dict(type='heatmap',
2566+
z=z,
2567+
x=x,
2568+
y=y,
2569+
showscale=showscale,
2570+
**kwargs)
2571+
else:
2572+
trace = dict(type='heatmap',
2573+
z=z,
2574+
showscale=showscale,
2575+
**kwargs)
2576+
2577+
data = [trace]
2578+
layout = dict(annotations=annotations,
2579+
xaxis=dict(ticks='', side='top',
2580+
gridcolor='rgb(0, 0, 0)'),
2581+
yaxis=dict(ticks='', ticksuffix=' '))
2582+
return graph_objs.Figure(data=data, layout=layout)
2583+
2584+
@staticmethod
2585+
def create_table(z, index=False,
2586+
colorscale=[[0, 'purple'], [.5, 'grey'], [1, 'white']],
2587+
fontcolor=['#000000'], line_color=[], index_title='',
2588+
height_constant=30, showscale=False,
2589+
**kwargs):
2590+
"""
2591+
BETA function that creates Plotly tables
2592+
"""
2593+
# TODO: protected until #282
2594+
from plotly.graph_objs import graph_objs
2595+
# FigureFactory._validate_annotated_heatmap()
2596+
table_matrix = _Table(z, index, colorscale, fontcolor, index_title,
2597+
**kwargs).get_table_matrix()
2598+
annotations = _Table(z, index, colorscale, fontcolor, index_title,
2599+
**kwargs).make_table_annotations()
2600+
2601+
trace = dict(type='heatmap',
2602+
z=table_matrix,
2603+
opacity=.7,
2604+
colorscale=colorscale,
2605+
showscale=showscale,
2606+
**kwargs)
2607+
2608+
data = [trace]
2609+
layout = dict(annotations=annotations,
2610+
height=len(table_matrix)*height_constant + 200,
2611+
yaxis=dict(autorange='reversed',
2612+
zeroline=False,
2613+
gridcolor=line_color,
2614+
gridwidth=2,
2615+
ticks='',
2616+
dtick=1,
2617+
tick0=.5,
2618+
showticklabels=False),
2619+
xaxis=dict(zeroline=False,
2620+
gridcolor=line_color,
2621+
gridwidth=2,
2622+
ticks='',
2623+
dtick=1,
2624+
tick0=-0.5,
2625+
showticklabels=False))
2626+
return graph_objs.Figure(data=data, layout=layout)
2627+
25122628

25132629
class _Quiver(FigureFactory):
25142630
"""
@@ -3442,3 +3558,200 @@ def get_dendrogram_traces(self, X, colorscale):
34423558
trace_list.append(trace)
34433559

34443560
return trace_list, icoord, dcoord, ordered_labels, P['leaves']
3561+
3562+
3563+
class _AnnotatedHeatmap(FigureFactory):
3564+
"""
3565+
Refer to TraceFactory.create_annotated_heatmap() for docstring
3566+
"""
3567+
def __init__(self, z, x=[], y=[], text=[],
3568+
fontcolor=[], colorscale=[],
3569+
reversescale=False, **kwargs):
3570+
from plotly.graph_objs import graph_objs
3571+
self.z = z
3572+
if x:
3573+
self.x = x
3574+
else:
3575+
self.x = range(len(z[0]))
3576+
if y:
3577+
self.y = y
3578+
else:
3579+
self.y = range(len(z))
3580+
if text:
3581+
self.text = text
3582+
else:
3583+
self.text = self.z
3584+
if colorscale:
3585+
self.colorscale = colorscale
3586+
else:
3587+
self.colorscale = 'RdBu'
3588+
self.reversescale = reversescale
3589+
self.fontcolor = fontcolor
3590+
3591+
def get_text_color(self):
3592+
'''
3593+
Get font color for annotations.
3594+
3595+
The annotated heatmap can feature 2 text colors: min_color_text and max_color_text
3596+
If no The user can define the text color for heatmap values >=
3597+
(max_value - min_value)/2 and for heatmap values <
3598+
(max_value - min_value)/2 Depending on the colorscale of the heatmap the text will be either
3599+
:rtype (string, string) min_color_text, max_color_text: text
3600+
color for the annotations
3601+
'''
3602+
colorscales = ['Greys', 'Greens', 'Blues',
3603+
'YIGnBu', 'YIOrRd', 'RdBu',
3604+
'Picnic', 'Jet', 'Hot', 'Blackbody',
3605+
'Earth', 'Electric', 'Viridis']
3606+
colorscales_opp = ['Reds']
3607+
if self.fontcolor:
3608+
min_color_text = self.fontcolor[0]
3609+
max_color_text = self.fontcolor[-1]
3610+
elif self.colorscale in colorscales and self.reversescale:
3611+
min_color_text = '#000000'
3612+
max_color_text = '#FFFFFF'
3613+
elif self.colorscale in colorscales:
3614+
min_color_text = '#FFFFFF'
3615+
max_color_text = '#000000'
3616+
elif self.colorscale in colorscales_opp and self.reversescale:
3617+
min_color_text = '#FFFFFF'
3618+
max_color_text = '#000000'
3619+
elif self.colorscale in colorscales_opp:
3620+
min_color_text = '#000000'
3621+
max_color_text = '#FFFFFF'
3622+
elif isinstance(self.colorscale, list):
3623+
if 'rgb' in self.colorscale[0][1]:
3624+
min_col = map(int,
3625+
self.colorscale[0][1].strip('rgb()').split(','))
3626+
max_col = map(int,
3627+
self.colorscale[-1][1].strip('rgb()').split(','))
3628+
elif '#' in self.colorscale[0][1]:
3629+
min_col = FigureFactory._hex_to_rgb(self.colorscale[0][1])
3630+
max_col = FigureFactory._hex_to_rgb(self.colorscale[-1][1])
3631+
else:
3632+
min_col = [255, 255, 255]
3633+
max_col = [255, 255, 255]
3634+
3635+
if (min_col[0]*0.299 + min_col[1]*0.587 + min_col[2]*0.114) > 186:
3636+
min_color_text = '#000000'
3637+
else:
3638+
min_color_text = '#FFFFFF'
3639+
if (max_col[0]*0.299 + max_col[1]*0.587 + max_col[2]*0.114) > 186:
3640+
max_color_text = '#000000'
3641+
else:
3642+
max_color_text = '#FFFFFF'
3643+
else:
3644+
min_color_text = '#000000'
3645+
max_color_text = '#000000'
3646+
return min_color_text, max_color_text
3647+
3648+
def make_annotations(self):
3649+
'''
3650+
Generate annotations
3651+
3652+
:rtype (dict) annotations:
3653+
'''
3654+
from plotly.graph_objs import graph_objs
3655+
min_color_text, max_color_text = _AnnotatedHeatmap.get_text_color(self)
3656+
annotations = []
3657+
for n, row in enumerate(self.text):
3658+
for m, val in enumerate(row):
3659+
annotations.append(
3660+
graph_objs.Annotation(
3661+
text=str(val),
3662+
x=self.x[m],
3663+
y=self.y[n],
3664+
xref='x1',
3665+
yref='y1',
3666+
font=dict(color=max_color_text if val >
3667+
max(max(self.z)) / 2 else min_color_text),
3668+
showarrow=False))
3669+
return annotations
3670+
3671+
3672+
class _Table(FigureFactory):
3673+
"""
3674+
Refer to TraceFactory.create_annotated_heatmap() for docstring
3675+
"""
3676+
def __init__(self, z, index, colorscale,
3677+
fontcolor, index_title='',
3678+
**kwargs):
3679+
from plotly.graph_objs import graph_objs
3680+
if _pandas_imported and isinstance(z, pd.DataFrame):
3681+
headers = z.columns.tolist()
3682+
z_index = z.index.tolist()
3683+
z = z.values.tolist()
3684+
z.insert(0, headers)
3685+
if index:
3686+
z_index.insert(0, index_title)
3687+
for i in range(len(z)):
3688+
z[i].insert(0, z_index[i])
3689+
self.z = z
3690+
self.x = range(len(z[0]))
3691+
self.y = range(len(z))
3692+
if colorscale:
3693+
self.colorscale = colorscale
3694+
else:
3695+
self.colorscale = 'RdBu'
3696+
self.fontcolor = fontcolor
3697+
self.index = index
3698+
3699+
def get_table_matrix(self):
3700+
'''
3701+
Create
3702+
3703+
:rtype (list[list] table_matrix):
3704+
'''
3705+
header = [0] * len(self.z[0])
3706+
odd_row = [.5] * len(self.z[0])
3707+
even_row = [1] * len(self.z[0])
3708+
table_matrix = [None] * len(self.z)
3709+
table_matrix[0] = header
3710+
for i in range(1, len(self.z), 2):
3711+
table_matrix[i] = odd_row
3712+
for i in range(2, len(self.z), 2):
3713+
table_matrix[i] = even_row
3714+
if self.index:
3715+
for array in table_matrix:
3716+
array[0] = 0
3717+
return table_matrix
3718+
3719+
def get_table_fontcolor(self):
3720+
'''
3721+
Make fontcolor array if a fontcolor array is not supplied.
3722+
'''
3723+
if len(self.fontcolor) == 1:
3724+
self.fontcolor *= len(self.z)
3725+
elif len(self.fontcolor) == 3:
3726+
all_font_color = range(len(self.z))
3727+
all_font_color[0] = self.fontcolor[0]
3728+
for i in range(1, len(self.z), 2):
3729+
all_font_color[i] = self.fontcolor[1]
3730+
for i in range(2, len(self.z), 2):
3731+
all_font_color[i] = self.fontcolor[2]
3732+
self.fontcolor = all_font_color
3733+
3734+
def make_table_annotations(self):
3735+
'''
3736+
Generates annotations...
3737+
'''
3738+
from plotly.graph_objs import graph_objs
3739+
table_matrix = _Table.get_table_matrix(self)
3740+
_Table.get_table_fontcolor(self)
3741+
annotations = []
3742+
for n, row in enumerate(self.z):
3743+
for m, val in enumerate(row):
3744+
annotations.append(
3745+
graph_objs.Annotation(
3746+
text=('<b>' + str(val) + '</b>' if n < 1 or
3747+
self.index and m < 1 else str(val)),
3748+
x=self.x[m] - .45,
3749+
y=self.y[n],
3750+
xref='x1',
3751+
yref='y1',
3752+
align="left",
3753+
xanchor="left",
3754+
font=dict(color=self.fontcolor[n]),
3755+
showarrow=False))
3756+
return annotations
3757+

0 commit comments

Comments
 (0)