diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8cdf5ed2b46..0f97382ec45 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,11 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
+## [1.9.1] - 2015-11-26
+### Added
+- The FigureFactory can now create annotated heatmaps with `.create_annotated_heatmap`.
+- The FigureFactory can now create tables with `.create_table`.
+
## [1.9.0] - 2015-11-15
- Previously, using plotly offline required a paid license.
No more: `plotly.js` is now shipped inside this package to allow
diff --git a/plotly/tests/test_core/test_tools/test_figure_factory.py b/plotly/tests/test_core/test_tools/test_figure_factory.py
index 3616eb1e9cf..32744650bcb 100644
--- a/plotly/tests/test_core/test_tools/test_figure_factory.py
+++ b/plotly/tests/test_core/test_tools/test_figure_factory.py
@@ -686,6 +686,446 @@ def test_datetime_candlestick(self):
self.assertEqual(candle, exp_candle)
+class TestAnnotatedHeatmap(TestCase):
+
+ def test_unequal_z_text_size(self):
+
+ # check: PlotlyError if z and text are not the same dimensions
+
+ kwargs = {'z': [[1, 2], [1, 2]], 'annotation_text': [[1, 2, 3], [1]]}
+ self.assertRaises(PlotlyError,
+ tls.FigureFactory.create_annotated_heatmap,
+ **kwargs)
+
+ kwargs = {'z': [[1], [1]], 'annotation_text': [[1], [1], [1]]}
+ self.assertRaises(PlotlyError,
+ tls.FigureFactory.create_annotated_heatmap,
+ **kwargs)
+
+ def test_incorrect_x_size(self):
+
+ # check: PlotlyError if x is the wrong size
+
+ kwargs = {'z': [[1, 2], [1, 2]], 'x': ['A']}
+ self.assertRaises(PlotlyError,
+ tls.FigureFactory.create_annotated_heatmap,
+ **kwargs)
+
+ def test_incorrect_y_size(self):
+
+ # check: PlotlyError if y is the wrong size
+
+ kwargs = {'z': [[1, 2], [1, 2]], 'y': [1, 2, 3]}
+ self.assertRaises(PlotlyError,
+ tls.FigureFactory.create_annotated_heatmap,
+ **kwargs)
+
+ def test_simple_annotated_heatmap(self):
+
+ # we should be able to create a heatmap with annotated values with a
+ # logical text color
+
+ z = [[1, 0, .5], [.25, .75, .45]]
+ a_heat = tls.FigureFactory.create_annotated_heatmap(z)
+ expected_a_heat = {
+ 'data': [{'colorscale': 'RdBu',
+ 'showscale': False,
+ 'type': 'heatmap',
+ 'z': [[1, 0, 0.5], [0.25, 0.75, 0.45]]}],
+ 'layout': {'annotations': [{'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': '1',
+ 'x': 0,
+ 'xref': 'x1',
+ 'y': 0,
+ 'yref': 'y1'},
+ {'font': {'color': '#FFFFFF'},
+ 'showarrow': False,
+ 'text': '0',
+ 'x': 1,
+ 'xref': 'x1',
+ 'y': 0,
+ 'yref': 'y1'},
+ {'font': {'color': '#FFFFFF'},
+ 'showarrow': False,
+ 'text': '0.5',
+ 'x': 2,
+ 'xref': 'x1',
+ 'y': 0,
+ 'yref': 'y1'},
+ {'font': {'color': '#FFFFFF'},
+ 'showarrow': False,
+ 'text': '0.25',
+ 'x': 0,
+ 'xref': 'x1',
+ 'y': 1,
+ 'yref': 'y1'},
+ {'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': '0.75',
+ 'x': 1,
+ 'xref': 'x1',
+ 'y': 1,
+ 'yref': 'y1'},
+ {'font': {'color': '#FFFFFF'},
+ 'showarrow': False,
+ 'text': '0.45',
+ 'x': 2,
+ 'xref': 'x1',
+ 'y': 1,
+ 'yref': 'y1'}],
+ 'xaxis': {'gridcolor': 'rgb(0, 0, 0)',
+ 'showticklabels': False,
+ 'side': 'top',
+ 'ticks': ''},
+ 'yaxis': {'showticklabels': False, 'ticks': '',
+ 'ticksuffix': ' '}}}
+ self.assertEqual(a_heat, expected_a_heat)
+
+ def test_annotated_heatmap_kwargs(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 = tls.FigureFactory.create_annotated_heatmap(z, x=['A', 'B'],
+ y=['One', 'Two',
+ 'Three'],
+ annotation_text=text,
+ colorscale=[[0,
+ '#ffffff'],
+ [1,
+ '#e6005a']]
+ )
+ expected_a = {'data': [{'colorscale': [[0, '#ffffff'], [1, '#e6005a']],
+ 'showscale': False,
+ 'type': 'heatmap',
+ 'x': ['A', 'B'],
+ 'y': ['One', 'Two', 'Three'],
+ 'z': [[1, 0], [0.25, 0.75], [0.45, 0.5]]}],
+ 'layout': {'annotations': [{'font': {'color': '#FFFFFF'},
+ 'showarrow': False,
+ 'text': 'first',
+ 'x': 'A',
+ 'xref': 'x1',
+ 'y': 'One',
+ 'yref': 'y1'},
+ {'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': 'second',
+ 'x': 'B',
+ 'xref': 'x1',
+ 'y': 'One',
+ 'yref': 'y1'},
+ {'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': 'third',
+ 'x': 'A',
+ 'xref': 'x1',
+ 'y': 'Two',
+ 'yref': 'y1'},
+ {'font': {'color': '#FFFFFF'},
+ 'showarrow': False,
+ 'text': 'fourth',
+ 'x': 'B',
+ 'xref': 'x1',
+ 'y': 'Two',
+ 'yref': 'y1'},
+ {'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': 'fifth',
+ 'x': 'A',
+ 'xref': 'x1',
+ 'y': 'Three',
+ 'yref': 'y1'},
+ {'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': 'sixth',
+ 'x': 'B',
+ 'xref': 'x1',
+ 'y': 'Three',
+ 'yref': 'y1'}],
+ 'xaxis': {'dtick': 1,
+ 'gridcolor': 'rgb(0, 0, 0)',
+ 'side': 'top',
+ 'ticks': ''},
+ 'yaxis': {'dtick': 1, 'ticks': '',
+ 'ticksuffix': ' '}}}
+ self.assertEqual(a, expected_a)
+
+
+class TestTable(TestCase):
+
+ def test_fontcolor_input(self):
+
+ # check: PlotlyError if fontcolor input is incorrect
+
+ kwargs = {'table_text': [['one', 'two'], [1, 2], [1, 2], [1, 2]],
+ 'fontcolor': '#000000'}
+ self.assertRaises(PlotlyError,
+ tls.FigureFactory.create_table, **kwargs)
+
+ kwargs = {'table_text': [['one', 'two'], [1, 2], [1, 2], [1, 2]],
+ 'fontcolor': ['red', 'blue']}
+ self.assertRaises(PlotlyError,
+ tls.FigureFactory.create_table, **kwargs)
+
+ def test_simple_table(self):
+
+ # we should be able to create a striped table by suppling a text matrix
+
+ text = [['Country', 'Year', 'Population'], ['US', 2000, 282200000],
+ ['Canada', 2000, 27790000], ['US', 1980, 226500000]]
+ table = tls.FigureFactory.create_table(text)
+ expected_table = {'data': [{'colorscale': [[0, '#00083e'],
+ [0.5, '#ededee'],
+ [1, '#ffffff']],
+ 'hoverinfo': 'none',
+ 'opacity': 0.75,
+ 'showscale': False,
+ 'type': 'heatmap',
+ 'z': [[0, 0, 0], [0.5, 0.5, 0.5],
+ [1, 1, 1], [0.5, 0.5, 0.5]]}],
+ 'layout': {'annotations': [{'align': 'left',
+ 'font': {'color': '#ffffff'},
+ 'showarrow': False,
+ 'text': 'Country',
+ 'x': -0.45,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 0,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#ffffff'},
+ 'showarrow': False,
+ 'text': 'Year',
+ 'x': 0.55,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 0,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#ffffff'},
+ 'showarrow': False,
+ 'text': 'Population',
+ 'x': 1.55,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 0,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': 'US',
+ 'x': -0.45,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 1,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': '2000',
+ 'x': 0.55,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 1,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': '282200000',
+ 'x': 1.55,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 1,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': 'Canada',
+ 'x': -0.45,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 2,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': '2000',
+ 'x': 0.55,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 2,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': '27790000',
+ 'x': 1.55,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 2,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': 'US',
+ 'x': -0.45,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 3,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': '1980',
+ 'x': 0.55,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 3,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': '226500000',
+ 'x': 1.55,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 3,
+ 'yref': 'y1'}],
+ 'height': 170,
+ 'margin': {'b': 0, 'l': 0, 'r': 0, 't': 0},
+ 'xaxis': {'dtick': 1,
+ 'gridwidth': 2,
+ 'showticklabels': False,
+ 'tick0': -0.5,
+ 'ticks': '',
+ 'zeroline': False},
+ 'yaxis': {'autorange': 'reversed',
+ 'dtick': 1,
+ 'gridwidth': 2,
+ 'showticklabels': False,
+ 'tick0': 0.5,
+ 'ticks': '',
+ 'zeroline': False}}}
+ self.assertEqual(table, expected_table)
+
+ def test_table_with_index(self):
+
+ # we should be able to create a striped table where the first column
+ # matches the coloring of the header
+
+ text = [['Country', 'Year', 'Population'], ['US', 2000, 282200000],
+ ['Canada', 2000, 27790000]]
+ index_table = tls.FigureFactory.create_table(text, index=True,
+ index_title='Title')
+ exp_index_table = {'data': [{'colorscale': [[0, '#00083e'], [0.5, '#ededee'], [1, '#ffffff']],
+ 'hoverinfo': 'none',
+ 'opacity': 0.75,
+ 'showscale': False,
+ 'type': 'heatmap',
+ 'z': [[0, 0, 0], [0, 0.5, 0.5], [0, 1, 1]]}],
+ 'layout': {'annotations': [{'align': 'left',
+ 'font': {'color': '#ffffff'},
+ 'showarrow': False,
+ 'text': 'Country',
+ 'x': -0.45,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 0,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#ffffff'},
+ 'showarrow': False,
+ 'text': 'Year',
+ 'x': 0.55,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 0,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#ffffff'},
+ 'showarrow': False,
+ 'text': 'Population',
+ 'x': 1.55,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 0,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#ffffff'},
+ 'showarrow': False,
+ 'text': 'US',
+ 'x': -0.45,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 1,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': '2000',
+ 'x': 0.55,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 1,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': '282200000',
+ 'x': 1.55,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 1,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#ffffff'},
+ 'showarrow': False,
+ 'text': 'Canada',
+ 'x': -0.45,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 2,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': '2000',
+ 'x': 0.55,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 2,
+ 'yref': 'y1'},
+ {'align': 'left',
+ 'font': {'color': '#000000'},
+ 'showarrow': False,
+ 'text': '27790000',
+ 'x': 1.55,
+ 'xanchor': 'left',
+ 'xref': 'x1',
+ 'y': 2,
+ 'yref': 'y1'}],
+ 'height': 140,
+ 'margin': {'b': 0, 'l': 0, 'r': 0, 't': 0},
+ 'xaxis': {'dtick': 1,
+ 'gridwidth': 2,
+ 'showticklabels': False,
+ 'tick0': -0.5,
+ 'ticks': '',
+ 'zeroline': False},
+ 'yaxis': {'autorange': 'reversed',
+ 'dtick': 1,
+ 'gridwidth': 2,
+ 'showticklabels': False,
+ 'tick0': 0.5,
+ 'ticks': '',
+ 'zeroline': False}}}
+ self.assertEqual(index_table, exp_index_table)
+
+
# class TestDistplot(TestCase):
# def test_scipy_import_error(self):
diff --git a/plotly/tools.py b/plotly/tools.py
index f26dc7d3e43..71835639ec6 100644
--- a/plotly/tools.py
+++ b/plotly/tools.py
@@ -49,6 +49,12 @@ def warning_on_one_line(message, category, filename, lineno,
except ImportError:
_numpy_imported = False
+try:
+ import pandas as pd
+ _pandas_imported = True
+except ImportError:
+ _pandas_imported = False
+
try:
import scipy as scp
_scipy_imported = True
@@ -1428,10 +1434,12 @@ class FigureFactory(object):
without notice.
Supported chart types include candlestick, open high low close, quiver,
- and streamline. See FigureFactory.create_candlestick,
- FigureFactory.create_ohlc, FigureFactory.create_quiver, or
- FigureFactory.create_streamline for for more infomation and examples of a
- specific chart type.
+ streamline, distplot, dendrogram, annotated heatmap, and tables. See
+ FigureFactory.create_candlestick, FigureFactory.create_ohlc,
+ FigureFactory.create_quiver, FigureFactory.create_streamline,
+ FigureFactory.create_distplot, FigureFactory.create_dendrogram,
+ FigureFactory.create_annotated_heatmap, or FigureFactory.create_table for
+ more information and examples of a specific chart type.
"""
@staticmethod
@@ -1492,7 +1500,7 @@ def _validate_ohlc(open, high, low, close, direction, **kwargs):
@staticmethod
def _validate_distplot(hist_data, curve_type):
"""
- distplot specific validations
+ Distplot-specific validations
:raises: (PlotlyError) If hist_data is not a list of lists
:raises: (PlotlyError) If curve_type is not valid (i.e. not 'kde' or
@@ -1546,7 +1554,7 @@ def _validate_positive_scalars(**kwargs):
@staticmethod
def _validate_streamline(x, y):
"""
- streamline specific validations
+ Streamline-specific validations
Specifically, this checks that x and y are both evenly spaced,
and that the package numpy is available.
@@ -1569,6 +1577,55 @@ def _validate_streamline(x, y):
raise exceptions.PlotlyError("y must be a 1 dimensional, "
"evenly spaced array")
+ @staticmethod
+ def _validate_annotated_heatmap(z, x, y, annotation_text):
+ """
+ Annotated-heatmap-specific validations
+
+ Check that if a text matrix is supplied, it has the same
+ dimensions as the z matrix.
+
+ See FigureFactory.create_annotated_heatmap() for params
+
+ :raises: (PlotlyError) If z and text matrices do not have the same
+ dimensions.
+ """
+ if annotation_text is not None and isinstance(annotation_text, list):
+ FigureFactory._validate_equal_length(z, annotation_text)
+ for lst in range(len(z)):
+ if len(z[lst]) != len(annotation_text[lst]):
+ raise exceptions.PlotlyError("z and text should have the "
+ "same dimensions")
+
+ if x:
+ if len(x) != len(z[0]):
+ raise exceptions.PlotlyError("oops, the x list that you "
+ "provided does not match the "
+ "width of your z matrix ")
+
+ if y:
+ if len(y) != len(z):
+ raise exceptions.PlotlyError("oops, the y list that you "
+ "provided does not match the "
+ "length of your z matrix ")
+
+ @staticmethod
+ def _validate_table(table_text, font_colors):
+ """
+ Table-specific validations
+
+ Check that font_colors is supplied correctly (1, 3, or len(text)
+ colors).
+
+ :raises: (PlotlyError) If font_colors is supplied incorretly.
+
+ See FigureFactory.create_table() for params
+ """
+ font_colors_len_options = [1, 3, len(table_text)]
+ if len(font_colors) not in font_colors_len_options:
+ raise exceptions.PlotlyError("Oops, font_colors should be a list "
+ "of length 1, 3 or len(text)")
+
@staticmethod
def _flatten(array):
"""
@@ -1585,6 +1642,21 @@ def _flatten(array):
"flattened! Make sure your data is "
"entered as lists or ndarrays!")
+ @staticmethod
+ def _hex_to_rgb(value):
+ """
+ Calculates rgb values from a hex color code.
+
+ :param (string) value: Hex color string
+
+ :rtype (tuple) (r_value, g_value, b_value): tuple of rgb values
+ """
+ value = value.lstrip('#')
+ hex_total_length = len(value)
+ rgb_section_length = hex_total_length // 3
+ return tuple(int(value[i:i + rgb_section_length], 16)
+ for i in range(0, hex_total_length, rgb_section_length))
+
@staticmethod
def create_quiver(x, y, u, v, scale=.1, arrow_scale=.3,
angle=math.pi / 9, **kwargs):
@@ -2509,6 +2581,189 @@ def create_dendrogram(X, orientation="bottom", labels=None,
return {'layout': dendrogram.layout,
'data': dendrogram.data}
+ @staticmethod
+ def create_annotated_heatmap(z, x=None, y=None, annotation_text=None,
+ colorscale='RdBu', font_colors=None,
+ showscale=False, reversescale=False,
+ **kwargs):
+ """
+ BETA function that creates annotated heatmaps
+
+ This function adds annotations to each cell of the heatmap.
+
+ :param (list[list]|ndarray) z: z matrix to create heatmap.
+ :param (list) x: x axis labels.
+ :param (list) y: y axis labels.
+ :param (list[list]|ndarray) annotation_text: Text strings for
+ annotations. Should have the same dimensions as the z matrix. If no
+ text is added, the values of the z matrix are annotated. Default =
+ z matrix values.
+ :param (list|str) colorscale: heatmap colorscale.
+ :param (list) font_colors: List of two color strings: [min_text_color,
+ max_text_color] where min_text_color is applied to annotations for
+ heatmap values < (max_value - min_value)/2. If font_colors is not
+ defined, the colors are defined logically as black or white
+ depending on the heatmap's colorscale.
+ :param (bool) showscale: Display 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
+ call help(plotly.graph_objs.Heatmap)
+
+ Example 1: Simple annotated heatmap with default configuration
+ ```
+ import plotly.plotly as py
+ from plotly.tools import FigureFactory as FF
+
+ z = [[0.300000, 0.00000, 0.65, 0.300000],
+ [1, 0.100005, 0.45, 0.4300],
+ [0.300000, 0.00000, 0.65, 0.300000],
+ [1, 0.100005, 0.45, 0.00000]]
+
+ figure = FF.create_annotated_heatmap(z)
+ py.iplot(figure)
+ ```
+ """
+ # TODO: protected until #282
+ from plotly.graph_objs import graph_objs
+
+ # Avoiding mutables in the call signature
+ font_colors = font_colors if font_colors is not None else []
+ FigureFactory._validate_annotated_heatmap(z, x, y, annotation_text)
+ annotations = _AnnotatedHeatmap(z, x, y, annotation_text,
+ colorscale, font_colors, reversescale,
+ **kwargs).make_annotations()
+
+ if x or y:
+ trace = dict(type='heatmap', z=z, x=x, y=y, colorscale=colorscale,
+ showscale=showscale, **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)
+ layout = dict(annotations=annotations,
+ xaxis=dict(ticks='', side='top',
+ gridcolor='rgb(0, 0, 0)',
+ showticklabels=False),
+ yaxis=dict(ticks='', ticksuffix=' ',
+ showticklabels=False))
+
+ data = [trace]
+
+ return graph_objs.Figure(data=data, layout=layout)
+
+ @staticmethod
+ def create_table(table_text, colorscale=None, font_colors=None,
+ index=False, index_title='', annotation_offset=.45,
+ height_constant=30, hoverinfo='none', **kwargs):
+ """
+ BETA function that creates data tables
+
+ :param (pandas.Dataframe | list[list]) text: data for table.
+ :param (str|list[list]) colorscale: Colorscale for table where the
+ color at value 0 is the header color, .5 is the first table color
+ and 1 is the second table color. (Set .5 and 1 to avoid the striped
+ table effect). Default=[[0, '#66b2ff'], [.5, '#d9d9d9'],
+ [1, '#ffffff']]
+ :param (list) font_colors: Color for fonts in table. Can be a single
+ color, three colors, or a color for each row in the table.
+ Default=['#000000'] (black text for the entire table)
+ :param (int) height_constant: Constant multiplied by # of rows to
+ create table height. Default=30.
+ :param (bool) index: Create (header-colored) index column index from
+ Pandas dataframe or list[0] for each list in text. Default=False.
+ :param (string) index_title: Title for index column. Default=''.
+ :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
+ call help(plotly.graph_objs.Heatmap)
+
+ Example 1: Simple Plotly Table
+ ```
+ import plotly.plotly as py
+ from plotly.tools import FigureFactory as FF
+
+ text = [['Country', 'Year', 'Population'],
+ ['US', 2000, 282200000],
+ ['Canada', 2000, 27790000],
+ ['US', 2010, 309000000],
+ ['Canada', 2010, 34000000]]
+
+ table = FF.create_table(text)
+ py.iplot(table)
+ ```
+
+ Example 2: Table with Custom Coloring
+ ```
+ import plotly.plotly as py
+ from plotly.tools import FigureFactory as FF
+
+ text = [['Country', 'Year', 'Population'],
+ ['US', 2000, 282200000],
+ ['Canada', 2000, 27790000],
+ ['US', 2010, 309000000],
+ ['Canada', 2010, 34000000]]
+
+ table = FF.create_table(text,
+ colorscale=[[0, '#000000'],
+ [.5, '#80beff'],
+ [1, '#cce5ff']],
+ font_colors=['#ffffff', '#000000',
+ '#000000'])
+ py.iplot(table)
+ ```
+ Example 3: Simple Plotly Table with Pandas
+ ```
+ import plotly.plotly as py
+ from plotly.tools import FigureFactory as FF
+
+ import pandas as pd
+
+ df = pd.read_csv('http://www.stat.ubc.ca/~jenny/notOcto/STAT545A/examples/gapminder/data/gapminderDataFiveYear.txt', sep='\t')
+ df_p = df[0:25]
+
+ table_simple = FF.create_table(df_p)
+ py.iplot(table_simple)
+ ```
+ """
+ # TODO: protected until #282
+ from plotly.graph_objs import graph_objs
+
+ # Avoiding mutables in the call signature
+ colorscale = \
+ colorscale if colorscale is not None else [[0, '#00083e'],
+ [.5, '#ededee'],
+ [1, '#ffffff']]
+ font_colors = font_colors if font_colors is not None else ['#ffffff',
+ '#000000',
+ '#000000']
+
+ FigureFactory._validate_table(table_text, font_colors)
+ table_matrix = _Table(table_text, colorscale, font_colors, index,
+ index_title, annotation_offset,
+ **kwargs).get_table_matrix()
+ annotations = _Table(table_text, colorscale, font_colors, index,
+ index_title, annotation_offset,
+ **kwargs).make_table_annotations()
+
+ trace = dict(type='heatmap', z=table_matrix, opacity=.75,
+ colorscale=colorscale, showscale=False,
+ hoverinfo=hoverinfo, **kwargs)
+
+ data = [trace]
+ layout = dict(annotations=annotations,
+ height=len(table_matrix)*height_constant + 50,
+ margin=dict(t=0, b=0, r=0, l=0),
+ yaxis=dict(autorange='reversed', zeroline=False,
+ gridwidth=2, ticks='', dtick=1, tick0=.5,
+ showticklabels=False),
+ xaxis=dict(zeroline=False, gridwidth=2, ticks='',
+ dtick=1, tick0=-0.5, showticklabels=False))
+ return graph_objs.Figure(data=data, layout=layout)
+
class _Quiver(FigureFactory):
"""
@@ -2927,6 +3182,7 @@ def sum_streamlines(self):
streamline_y = sum(self.st_y, [])
return streamline_x, streamline_y
+
class _OHLC(FigureFactory):
"""
Refer to FigureFactory.create_ohlc_increase() for docstring.
@@ -3442,3 +3698,237 @@ def get_dendrogram_traces(self, X, colorscale):
trace_list.append(trace)
return trace_list, icoord, dcoord, ordered_labels, P['leaves']
+
+
+class _AnnotatedHeatmap(FigureFactory):
+ """
+ Refer to TraceFactory.create_annotated_heatmap() for docstring
+ """
+ def __init__(self, z, x, y, annotation_text, colorscale,
+ font_colors, reversescale, **kwargs):
+ from plotly.graph_objs import graph_objs
+
+ self.z = z
+ if x:
+ self.x = x
+ else:
+ self.x = range(len(z[0]))
+ if y:
+ self.y = y
+ else:
+ self.y = range(len(z))
+ if annotation_text is not None:
+ self.annotation_text = annotation_text
+ else:
+ self.annotation_text = self.z
+ self.colorscale = colorscale
+ self.reversescale = reversescale
+ self.font_colors = font_colors
+
+ def get_text_color(self):
+ """
+ Get font color for annotations.
+
+ The annotated heatmap can feature two text colors: min_text_color and
+ max_text_color. The min_text_color is applied to annotations for
+ heatmap values < (max_value - min_value)/2. The user can define these
+ two colors. Otherwise the colors are defined logically as black or
+ white depending on the heatmap's colorscale.
+
+ :rtype (string, string) min_text_color, max_text_color: text
+ color for annotations for heatmap values <
+ (max_value - min_value)/2 and text color for annotations for
+ heatmap values >= (max_value - min_value)/2
+ """
+ # Plotly colorscales ranging from a lighter shade to a darker shade
+ colorscales = ['Greys', 'Greens', 'Blues',
+ 'YIGnBu', 'YIOrRd', 'RdBu',
+ 'Picnic', 'Jet', 'Hot', 'Blackbody',
+ 'Earth', 'Electric', 'Viridis']
+ # Plotly colorscales ranging from a darker shade to a lighter shade
+ colorscales_reverse = ['Reds']
+ 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'
+ elif self.colorscale in colorscales:
+ min_text_color = '#FFFFFF'
+ max_text_color = '#000000'
+ elif self.colorscale in colorscales_reverse and self.reversescale:
+ min_text_color = '#FFFFFF'
+ max_text_color = '#000000'
+ elif self.colorscale in colorscales_reverse:
+ min_text_color = '#000000'
+ max_text_color = '#FFFFFF'
+ elif isinstance(self.colorscale, list):
+ if 'rgb' in self.colorscale[0][1]:
+ min_col = map(int,
+ self.colorscale[0][1].strip('rgb()').split(','))
+ max_col = map(int,
+ self.colorscale[-1][1].strip('rgb()').split(','))
+ elif '#' in self.colorscale[0][1]:
+ min_col = FigureFactory._hex_to_rgb(self.colorscale[0][1])
+ max_col = FigureFactory._hex_to_rgb(self.colorscale[-1][1])
+ else:
+ min_col = [255, 255, 255]
+ max_col = [255, 255, 255]
+
+ if (min_col[0]*0.299 + min_col[1]*0.587 + min_col[2]*0.114) > 186:
+ min_text_color = '#000000'
+ 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'
+ else:
+ max_text_color = '#FFFFFF'
+ else:
+ min_text_color = '#000000'
+ max_text_color = '#000000'
+ return min_text_color, max_text_color
+
+ def get_z_mid(self):
+ """
+ Get the mid value of z matrix
+
+ :rtype (float) z_avg: average val from z matrix
+ """
+ if _numpy_imported and isinstance(self.z, np.ndarray):
+ z_min = np.amin(self.z)
+ z_max = np.amax(self.z)
+ else:
+ z_min = min(min(self.z))
+ z_max = max(max(self.z))
+ z_mid = (z_max+z_min) / 2
+ return z_mid
+
+ def make_annotations(self):
+ """
+ Get annotations for each cell of the heatmap with graph_objs.Annotation
+
+ :rtype (list[dict]) annotations: list of annotations for each cell of
+ the heatmap
+ """
+ from plotly.graph_objs import graph_objs
+ min_text_color, max_text_color = _AnnotatedHeatmap.get_text_color(self)
+ z_mid = _AnnotatedHeatmap.get_z_mid(self)
+ annotations = []
+ for n, row in enumerate(self.z):
+ for m, val in enumerate(row):
+ font_color = min_text_color if val < z_mid else max_text_color
+ annotations.append(
+ graph_objs.Annotation(
+ text=str(self.annotation_text[n][m]),
+ x=self.x[m],
+ y=self.y[n],
+ xref='x1',
+ yref='y1',
+ font=dict(color=font_color),
+ showarrow=False))
+ return annotations
+
+
+class _Table(FigureFactory):
+ """
+ Refer to TraceFactory.create_table() for docstring
+ """
+ def __init__(self, table_text, colorscale, font_colors, index,
+ index_title, annotation_offset, **kwargs):
+ from plotly.graph_objs import graph_objs
+ if _pandas_imported and isinstance(table_text, pd.DataFrame):
+ headers = table_text.columns.tolist()
+ table_text_index = table_text.index.tolist()
+ table_text = table_text.values.tolist()
+ table_text.insert(0, headers)
+ if index:
+ table_text_index.insert(0, index_title)
+ for i in range(len(table_text)):
+ table_text[i].insert(0, table_text_index[i])
+ self.table_text = table_text
+ self.colorscale = colorscale
+ self.font_colors = font_colors
+ self.index = index
+ self.annotation_offset = annotation_offset
+ self.x = range(len(table_text[0]))
+ self.y = range(len(table_text))
+
+ def get_table_matrix(self):
+ """
+ Create z matrix to make heatmap with striped table coloring
+
+ :rtype (list[list]) table_matrix: z matrix to make heatmap with striped
+ table coloring.
+ """
+ header = [0] * len(self.table_text[0])
+ odd_row = [.5] * len(self.table_text[0])
+ even_row = [1] * len(self.table_text[0])
+ table_matrix = [None] * len(self.table_text)
+ table_matrix[0] = header
+ for i in range(1, len(self.table_text), 2):
+ table_matrix[i] = odd_row
+ for i in range(2, len(self.table_text), 2):
+ table_matrix[i] = even_row
+ if self.index:
+ for array in table_matrix:
+ array[0] = 0
+ return table_matrix
+
+ def get_table_font_color(self):
+ """
+ Fill font-color array.
+
+ Table text color can vary by row so this extends a single color or
+ creates an array to set a header color and two alternating colors to
+ create the striped table pattern.
+
+ :rtype (list[list]) all_font_colors: list of font colors for each row
+ in table.
+ """
+ if len(self.font_colors) == 1:
+ all_font_colors = self.font_colors*len(self.table_text)
+ elif len(self.font_colors) == 3:
+ all_font_colors = list(range(len(self.table_text)))
+ all_font_colors[0] = self.font_colors[0]
+ for i in range(1, len(self.table_text), 2):
+ all_font_colors[i] = self.font_colors[1]
+ for i in range(2, len(self.table_text), 2):
+ all_font_colors[i] = self.font_colors[2]
+ elif len(self.font_colors) == len(self.table_text):
+ all_font_colors = self.font_colors
+ else:
+ all_font_colors = ['#000000']*len(self.table_text)
+ return all_font_colors
+
+ def make_table_annotations(self):
+ """
+ Generate annotations to fill in table text
+
+ :rtype (list) annotations: list of annotations for each cell of the
+ table.
+ """
+ from plotly.graph_objs import graph_objs
+ table_matrix = _Table.get_table_matrix(self)
+ all_font_colors = _Table.get_table_font_color(self)
+ annotations = []
+ for n, row in enumerate(self.table_text):
+ for m, val in enumerate(row):
+ # Bold text in header and index
+ format_text = ('' + str(val) + '' if n == 0 or
+ self.index and m < 1 else str(val))
+ # Match font color of index to font color of header
+ font_color = (self.font_colors[0] if self.index and m == 0
+ else all_font_colors[n])
+ annotations.append(
+ graph_objs.Annotation(
+ text=format_text,
+ x=self.x[m] - self.annotation_offset,
+ y=self.y[n],
+ xref='x1',
+ yref='y1',
+ align="left",
+ xanchor="left",
+ font=dict(color=font_color),
+ showarrow=False))
+ return annotations
+
diff --git a/plotly/version.py b/plotly/version.py
index e5102d30158..35424e875ea 100644
--- a/plotly/version.py
+++ b/plotly/version.py
@@ -1 +1 @@
-__version__ = '1.9.0'
+__version__ = '1.9.1'