From c6744a0b14dd47df22b9890f5ed00c6a48392010 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Sat, 26 Jan 2019 23:00:47 +0100 Subject: [PATCH 01/12] poc of ternarycontour --- plotly/figure_factory/_ternarycontour.py | 301 +++++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 plotly/figure_factory/_ternarycontour.py diff --git a/plotly/figure_factory/_ternarycontour.py b/plotly/figure_factory/_ternarycontour.py new file mode 100644 index 0000000000..2696f91d8c --- /dev/null +++ b/plotly/figure_factory/_ternarycontour.py @@ -0,0 +1,301 @@ +from __future__ import absolute_import +import numpy as np +from scipy.interpolate import griddata +from plotly.graph_objs import graph_objs as go + + +def _pl_deep(): + return [[0.0, 'rgb(253, 253, 204)'], + [0.1, 'rgb(201, 235, 177)'], + [0.2, 'rgb(145, 216, 163)'], + [0.3, 'rgb(102, 194, 163)'], + [0.4, 'rgb(81, 168, 162)'], + [0.5, 'rgb(72, 141, 157)'], + [0.6, 'rgb(64, 117, 152)'], + [0.7, 'rgb(61, 90, 146)'], + [0.8, 'rgb(65, 64, 123)'], + [0.9, 'rgb(55, 44, 80)'], + [1.0, 'rgb(39, 26, 44)']] + + + +def tr_b2c2b(): + """ + Rreturns the transformation matrix from barycentric to cartesian + coordinates and conversely + """ + # reference triangle + tri_verts = np.array([[0.5, np.sqrt(3)/2], [0, 0], [1, 0]]) + M = np.array([tri_verts[:, 0], tri_verts[:, 1], np.ones(3)]) + return M, np.linalg.inv(M) + + +def contour_trace(x, y, z, tooltip, colorscale='Viridis', + reversescale=False, showscale=False, linewidth=0.5, + linecolor='rgb(150,150,150)', smoothing=False, + coloring=None, showlabels=False, fontcolor='blue', + fontsize=12): + + + c_dict = dict(type='contour', + x=x, + y=y, + z=z, + text=tooltip, + hoverinfo='text', + colorscale=colorscale, + reversescale=reversescale, + showscale=showscale, + line=dict(width=linewidth, color=linecolor, + smoothing=smoothing), + colorbar=dict(thickness=20, ticklen=4) + ) + if coloring == 'lines': + contours=dict(coloring =coloring, + showlabels = showlabels,) + if showlabels: + contours.update(labelfont = dict(size=fontsize, + color=fontcolor,)) + c_dict.update(contours=contours) + return go.Contour(c_dict) + + +def barycentric_ticks(side): + # side 0, 1 or 2; side j has 0 in the j^th position of barycentric coords of tick origin + # returns the list of tick origin barycentric coords + p = 10 + if side == 0: #where a=0 + return np.array([(0, j/p, 1-j/p) for j in range(p-2, 0, -2)]) + elif side == 1: # b=0 + return np.array([(i/p, 0, 1-i/p) for i in range( 2, p, 2) ]) + elif side == 2: #c=0 + return np.array([(i/p, j/p, 0) for i in range(p-2, 0, -2) for j in range(p-i, -1, -1) if i+j==p]) + else: + raise ValueError('The side can be only 0, 1, 2') + + + +def cart_coord_ticks(side, xt, yt, posx, posy, t=0.01): + + # side 0, 1 or 2 + # each tick segment is parameterized as (x(s), y(s)), s in [0, t] + # M is the transformation matrix from barycentric to cartesian coords + # xt, yt are the lists of x, resp y-coords of tick segments + # posx, posy are the lists of ticklabel positions for side 0, 1, 2 (concatenated) + + M, invM = tr_b2c2b() + baryc = barycentric_ticks(side) + xy1 = np.dot(M, baryc.T) + xs, ys = xy1[:2] + + if side == 0: + for i in range(4): + xt.extend([xs[i], xs[i]+t, None]) + yt.extend([ys[i], ys[i]-np.sqrt(3)*t, None]) + posx.extend([xs[i]+t for i in range(4)]) + posy.extend([ys[i]-np.sqrt(3)*t for i in range(4)]) + + elif side == 1: + for i in range(4): + xt.extend([xs[i], xs[i]+t, None]) + yt.extend([ys[i], ys[i]+np.sqrt(3)*t, None]) + posx.extend([xs[i]+t for i in range(4)]) + posy.extend([ys[i]+np.sqrt(3)*t for i in range(4)]) + + elif side == 2: + for i in range(4): + xt.extend([xs[i], xs[i]-2*t, None]) + yt.extend([ys[i], ys[i], None]) + posx.extend([xs[i]-2*t for i in range(4)]) + posy.extend([ys[i] for i in range(4)]) + else: + raise ValueError('side can be only 0,1,2') + return xt, yt, posx, posy + + +def set_ticklabels(annotations, posx, posy, proportion=True): + """ + annotations: list of annotations previously defined in layout definition as a dict, + not as an instance of go.Layout + posx, posy: lists containing ticklabel position coordinates + proportion - boolean; True when ticklabels are 0.2, 0.4, ... False when they are 20%, 40%... + """ + if not isinstance(annotations, list): + raise ValueError('annotations should be a list') + + ticklabel = [0.8, 0.6, 0.4, 0.2] if proportion else ['80%', '60%', '40%', '20%'] + + annotations.extend([dict(showarrow=False, # annotations for ticklabels on side 0 + text=f'{ticklabel[j]}', + x=posx[j], + y=posy[j], + align='center', + xanchor='center', + yanchor='top', + font=dict(size=12)) for j in range(4)]) + + annotations.extend([dict(showarrow=False, # annotations for ticklabels on side 1 + text=f'{ticklabel[j]}', + x=posx[j+4], + y=posy[j+4], + align='center', + xanchor='left', + yanchor='middle', + font=dict(size=12)) for j in range(4)]) + + annotations.extend([dict(showarrow=False, # annotations for ticklabels on side 2 + text=f'{ticklabel[j]}', + x=posx[j+8], + y=posy[j+8], + align='center', + xanchor='right', + yanchor='middle', + font=dict(size=12)) for j in range(4)]) + return annotations + + + +def styling_traces(xt, yt): + side_trace = dict(type='scatter', + x=[0.5, 0, 1, 0.5], + y=[np.sqrt(3)/2, 0, 0, np.sqrt(3)/2], + mode='lines', + line=dict(width=2, color='#444444'), + hoverinfo='none') + + tick_trace = dict(type='scatter', + x=xt, + y=yt, + mode='lines', + line=dict(width=1, color='#444444'), + hoverinfo='none') + + return side_trace, tick_trace + + + +def ternary_layout(title='Ternary contour plot', width=550, height=525, + fontfamily= 'Balto, sans-serif' , lfontsize=14, + plot_bgcolor='rgb(240,240,240)', + vertex_text=['a', 'b', 'c'], v_fontsize=14): + + return dict(title=title, + font=dict(family=fontfamily, size=lfontsize), + width=width, height=height, + xaxis=dict(visible=False), + yaxis=dict(visible=False), + plot_bgcolor=plot_bgcolor, + showlegend=False, + #annotations for strings placed at the triangle vertices + annotations=[dict(showarrow=False, + text=vertex_text[0], + x=0.5, + y=np.sqrt(3)/2, + align='center', + xanchor='center', + yanchor='bottom', + font=dict(size=v_fontsize)), + dict(showarrow=False, + text=vertex_text[1], + x=0, + y=0, + align='left', + xanchor='right', + yanchor='top', + font=dict(size=v_fontsize)), + dict(showarrow=False, + text=vertex_text[2], + x=1, + y=0, + align='right', + xanchor='left', + yanchor='top', + font=dict(size=v_fontsize)) + ]) + + +def _tooltip(N, bar_coords, grid_z, xy1, mode='proportions'): + if mode == 'proportions': + tooltip = [ + [f'a: {round(bar_coords[0][i,j], 2)}
b: {round(bar_coords[1][i,j], 2)}'+ + f'
c: {round(1-round(bar_coords[0][i,j], 2) - round(bar_coords[1][i,j], 2), 2)}'+ + f'
z: {round(grid_z[i,j],2)}' + if ~np.isnan(xy1[0][i, j]) else '' for j in range(N)] + for i in range(N)] + else: + tooltip = [ + [f'a: {int(100*bar_coords[0][i,j]+0.5)}
b: {int(100*bar_coords[1][i,j]+0.5)}'+ + f'
c: {100-int(100*bar_coords[0][i,j]+0.5) -int(100*bar_coords[1][i,j]+0.5)}'+ + f'
z: {round(grid_z[i,j],2)}' + if ~np.isnan(xy1[0][i, j]) else '' for j in range(N)] + for i in range(N)] + return tooltip + + +def _compute_grid(data, M, N=150): + A, B, C, z = data + M, invM = tr_b2c2b() + cartes_coord_points = np.einsum('ik, kj -> ij', M, np.stack((A, B, C))) + xx, yy = cartes_coord_points[:2] + x_min, x_max = xx.min(), xx.max() + y_min, y_max = yy.min(), yy.max() + gr_x = np.linspace(x_min, x_max, N) + gr_y = np.linspace(y_min, y_max, N) + grid_x, grid_y = np.meshgrid(gr_x, gr_y) + grid_z = griddata(cartes_coord_points[:2].T, z, (grid_x, grid_y), method='cubic') + bar_coords = np.einsum('ik, kmn -> imn', invM, + np.stack((grid_x, grid_y, np.ones(grid_x.shape)))) + # invalidate the points outside of the reference triangle + bar_coords[np.where(bar_coords < 0)] = None + # recompute back the cartesian coordinates of bar_coords with + # invalid positions + xy1 = np.einsum('ik, kmn -> imn', M, bar_coords) + is_nan = np.where(np.isnan(xy1[0])) + grid_z[is_nan] = None + return grid_z + + + + +def create_ternarycontour(data, N=150, tooltip_mode='proportion', + showscale=False, coloring='lines', **kwargs): + A, B, C, z = data + M, invM = tr_b2c2b() + cartes_coord_points = np.einsum('ik, kj -> ij', M, np.stack((A, B, C))) + xx, yy = cartes_coord_points[:2] + x_min, x_max = xx.min(), xx.max() + y_min, y_max = yy.min(), yy.max() + gr_x = np.linspace(x_min, x_max, N) + gr_y = np.linspace(y_min, y_max, N) + grid_x, grid_y = np.meshgrid(gr_x, gr_y) + grid_z = griddata(cartes_coord_points[:2].T, z, (grid_x, grid_y), method='cubic') + bar_coords = np.einsum('ik, kmn -> imn', invM, + np.stack((grid_x, grid_y, np.ones(grid_x.shape)))) + # invalidate the points outside of the reference triangle + bar_coords[np.where(bar_coords < 0)] = None + # recompute back the cartesian coordinates of bar_coords with + # invalid positions + xy1 = np.einsum('ik, kmn -> imn', M, bar_coords) + is_nan = np.where(np.isnan(xy1[0])) + grid_z[is_nan] = None + + xt = [] + yt = [] + posx = [] + posy = [] + for side in [0, 1, 2]: + xt, yt, posx, posy = cart_coord_ticks(side, xt, yt, posx, posy, t=0.01) + + layout = ternary_layout() + annotations = set_ticklabels(layout['annotations'], posx, posy, + proportion=True) + pl_deep = _pl_deep() + tooltip = _tooltip(N, bar_coords, grid_z, xy1, tooltip_mode) + c_trace = contour_trace(gr_x, gr_y, grid_z, tooltip, showscale=showscale, + colorscale=pl_deep, reversescale=True, + coloring=coloring) + side_trace, tick_trace = styling_traces(xt, yt) + fig = go.FigureWidget(data=[c_trace, tick_trace, side_trace], + layout=layout) + fig.layout.annotations = annotations + return fig From 82b600d5a88192a58c5c3678e46b529cc2e92206 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Sun, 27 Jan 2019 23:09:25 +0100 Subject: [PATCH 02/12] ternary contour figure factory --- plotly/figure_factory/README.md | 4 + plotly/figure_factory/__init__.py | 1 + plotly/figure_factory/_ternarycontour.py | 488 +++++++++++------- .../test_figure_factory.py | 14 + 4 files changed, 309 insertions(+), 198 deletions(-) diff --git a/plotly/figure_factory/README.md b/plotly/figure_factory/README.md index c88385ad45..53e01b5c79 100644 --- a/plotly/figure_factory/README.md +++ b/plotly/figure_factory/README.md @@ -142,6 +142,10 @@ It is often not a good idea to put all your code into your `create_foo()` functi It is best to make all other functions besides `create_foo()` secret so a user cannot access them. This is done by placing a `_` before the name of the function, so `_aux_func()` for example. +6. Tests + +Add unit tests in +`plotly/tests/test_optional/test_figure_factory/test_figure_factory.py`. ## Create a Pull Request diff --git a/plotly/figure_factory/__init__.py b/plotly/figure_factory/__init__.py index a8be19872e..910ffa62f1 100644 --- a/plotly/figure_factory/__init__.py +++ b/plotly/figure_factory/__init__.py @@ -18,6 +18,7 @@ from plotly.figure_factory._scatterplot import create_scatterplotmatrix from plotly.figure_factory._streamline import create_streamline from plotly.figure_factory._table import create_table +from plotly.figure_factory._ternarycontour import create_ternarycontour from plotly.figure_factory._trisurf import create_trisurf from plotly.figure_factory._violin import create_violin if optional_imports.get_module('pandas') is not None: diff --git a/plotly/figure_factory/_ternarycontour.py b/plotly/figure_factory/_ternarycontour.py index 2696f91d8c..2fd0b8a72d 100644 --- a/plotly/figure_factory/_ternarycontour.py +++ b/plotly/figure_factory/_ternarycontour.py @@ -2,26 +2,26 @@ import numpy as np from scipy.interpolate import griddata from plotly.graph_objs import graph_objs as go +import warnings def _pl_deep(): return [[0.0, 'rgb(253, 253, 204)'], - [0.1, 'rgb(201, 235, 177)'], - [0.2, 'rgb(145, 216, 163)'], - [0.3, 'rgb(102, 194, 163)'], - [0.4, 'rgb(81, 168, 162)'], - [0.5, 'rgb(72, 141, 157)'], - [0.6, 'rgb(64, 117, 152)'], - [0.7, 'rgb(61, 90, 146)'], - [0.8, 'rgb(65, 64, 123)'], - [0.9, 'rgb(55, 44, 80)'], - [1.0, 'rgb(39, 26, 44)']] - - - -def tr_b2c2b(): + [0.1, 'rgb(201, 235, 177)'], + [0.2, 'rgb(145, 216, 163)'], + [0.3, 'rgb(102, 194, 163)'], + [0.4, 'rgb(81, 168, 162)'], + [0.5, 'rgb(72, 141, 157)'], + [0.6, 'rgb(64, 117, 152)'], + [0.7, 'rgb(61, 90, 146)'], + [0.8, 'rgb(65, 64, 123)'], + [0.9, 'rgb(55, 44, 80)'], + [1.0, 'rgb(39, 26, 44)']] + + +def _transform_barycentric_cartesian(): """ - Rreturns the transformation matrix from barycentric to cartesian + Returns the transformation matrix from barycentric to cartesian coordinates and conversely """ # reference triangle @@ -30,132 +30,148 @@ def tr_b2c2b(): return M, np.linalg.inv(M) -def contour_trace(x, y, z, tooltip, colorscale='Viridis', - reversescale=False, showscale=False, linewidth=0.5, - linecolor='rgb(150,150,150)', smoothing=False, - coloring=None, showlabels=False, fontcolor='blue', - fontsize=12): - +def _contour_trace(x, y, z, tooltip, colorscale='Viridis', + reversescale=False, showscale=False, linewidth=0.5, + linecolor='rgb(150,150,150)', smoothing=False, + coloring=None, showlabels=False, fontcolor='blue', + fontsize=12): + """ + Contour trace in Cartesian coordinates. + """ + if showlabels and coloring is not 'lines': + msg = """`showlabels` was set to True, but labels can only be + displayed for `coloring='lines'`""" + warnings.warn(msg) c_dict = dict(type='contour', - x=x, - y=y, - z=z, - text=tooltip, - hoverinfo='text', - colorscale=colorscale, - reversescale=reversescale, - showscale=showscale, - line=dict(width=linewidth, color=linecolor, - smoothing=smoothing), - colorbar=dict(thickness=20, ticklen=4) - ) + x=x, y=y, z=z, + text=tooltip, + hoverinfo='text', + colorscale=colorscale, + reversescale=reversescale, + showscale=showscale, + line=dict(width=linewidth, color=linecolor, + smoothing=smoothing), + colorbar=dict(thickness=20, ticklen=4) + ) if coloring == 'lines': - contours=dict(coloring =coloring, - showlabels = showlabels,) + contours = dict(coloring=coloring, + showlabels=showlabels) if showlabels: - contours.update(labelfont = dict(size=fontsize, + contours.update(labelfont=dict(size=fontsize, color=fontcolor,)) - c_dict.update(contours=contours) - return go.Contour(c_dict) + c_dict.update(contours=contours) + return go.Contour(c_dict) def barycentric_ticks(side): - # side 0, 1 or 2; side j has 0 in the j^th position of barycentric coords of tick origin - # returns the list of tick origin barycentric coords + """ + side 0, 1 or 2; side j has 0 in the j^th position of barycentric + coords of tick origin + returns the list of tick origin barycentric coords + """ p = 10 - if side == 0: #where a=0 - return np.array([(0, j/p, 1-j/p) for j in range(p-2, 0, -2)]) - elif side == 1: # b=0 - return np.array([(i/p, 0, 1-i/p) for i in range( 2, p, 2) ]) - elif side == 2: #c=0 - return np.array([(i/p, j/p, 0) for i in range(p-2, 0, -2) for j in range(p-i, -1, -1) if i+j==p]) + if side == 0: # where a=0 + return np.array([(0, j/p, 1-j/p) for j in range(p - 2, 0, -2)]) + elif side == 1: # b=0 + return np.array([(i/p, 0, 1-i/p) for i in range(2, p, 2)]) + elif side == 2: # c=0 + return (np.array([(i/p, j/p, 0) + for i in range(p-2, 0, -2) + for j in range(p-i, -1, -1) if i+j == p])) else: raise ValueError('The side can be only 0, 1, 2') - -def cart_coord_ticks(side, xt, yt, posx, posy, t=0.01): - - # side 0, 1 or 2 - # each tick segment is parameterized as (x(s), y(s)), s in [0, t] - # M is the transformation matrix from barycentric to cartesian coords - # xt, yt are the lists of x, resp y-coords of tick segments - # posx, posy are the lists of ticklabel positions for side 0, 1, 2 (concatenated) - - M, invM = tr_b2c2b() - baryc = barycentric_ticks(side) +def _cart_coord_ticks(t=0.01): + """ + side 0, 1 or 2 + each tick segment is parameterized as (x(s), y(s)), s in [0, t] + M is the transformation matrix from barycentric to cartesian coords + xt, yt are the lists of x, resp y-coords of tick segments + posx, posy are the lists of ticklabel positions for side 0, 1, 2 + (concatenated) + """ + M, invM = _transform_barycentric_cartesian() + x_ticks, y_ticks, posx, posy = [], [], [], [] + # Side 0 + baryc = barycentric_ticks(0) xy1 = np.dot(M, baryc.T) xs, ys = xy1[:2] - - if side == 0: - for i in range(4): - xt.extend([xs[i], xs[i]+t, None]) - yt.extend([ys[i], ys[i]-np.sqrt(3)*t, None]) - posx.extend([xs[i]+t for i in range(4)]) - posy.extend([ys[i]-np.sqrt(3)*t for i in range(4)]) - - elif side == 1: - for i in range(4): - xt.extend([xs[i], xs[i]+t, None]) - yt.extend([ys[i], ys[i]+np.sqrt(3)*t, None]) - posx.extend([xs[i]+t for i in range(4)]) - posy.extend([ys[i]+np.sqrt(3)*t for i in range(4)]) - - elif side == 2: - for i in range(4): - xt.extend([xs[i], xs[i]-2*t, None]) - yt.extend([ys[i], ys[i], None]) - posx.extend([xs[i]-2*t for i in range(4)]) - posy.extend([ys[i] for i in range(4)]) - else: - raise ValueError('side can be only 0,1,2') - return xt, yt, posx, posy + for i in range(4): + x_ticks.extend([xs[i], xs[i]+t, None]) + y_ticks.extend([ys[i], ys[i]-np.sqrt(3)*t, None]) + posx.extend([xs[i]+t for i in range(4)]) + posy.extend([ys[i]-np.sqrt(3)*t for i in range(4)]) + # Side 1 + baryc = barycentric_ticks(1) + xy1 = np.dot(M, baryc.T) + xs, ys = xy1[:2] + for i in range(4): + x_ticks.extend([xs[i], xs[i]+t, None]) + y_ticks.extend([ys[i], ys[i]+np.sqrt(3)*t, None]) + posx.extend([xs[i]+t for i in range(4)]) + posy.extend([ys[i]+np.sqrt(3)*t for i in range(4)]) + # Side 2 + baryc = barycentric_ticks(2) + xy1 = np.dot(M, baryc.T) + xs, ys = xy1[:2] + for i in range(4): + x_ticks.extend([xs[i], xs[i]-2*t, None]) + y_ticks.extend([ys[i], ys[i], None]) + posx.extend([xs[i]-2*t for i in range(4)]) + posy.extend([ys[i] for i in range(4)]) + return x_ticks, y_ticks, posx, posy -def set_ticklabels(annotations, posx, posy, proportion=True): +def _set_ticklabels(annotations, posx, posy, proportion=True): """ - annotations: list of annotations previously defined in layout definition as a dict, - not as an instance of go.Layout + annotations: list of annotations previously defined in layout definition + as a dict, not as an instance of go.Layout posx, posy: lists containing ticklabel position coordinates - proportion - boolean; True when ticklabels are 0.2, 0.4, ... False when they are 20%, 40%... + proportion - boolean; True when ticklabels are 0.2, 0.4, ... + False when they are 20%, 40%... """ if not isinstance(annotations, list): raise ValueError('annotations should be a list') - - ticklabel = [0.8, 0.6, 0.4, 0.2] if proportion else ['80%', '60%', '40%', '20%'] - - annotations.extend([dict(showarrow=False, # annotations for ticklabels on side 0 + + ticklabel = [0.8, 0.6, 0.4, 0.2] if proportion \ + else ['80%', '60%', '40%', '20%'] + + # Annotations for ticklabels on side 0 + annotations.extend([dict(showarrow=False, text=f'{ticklabel[j]}', x=posx[j], y=posy[j], align='center', - xanchor='center', + xanchor='center', yanchor='top', font=dict(size=12)) for j in range(4)]) - - annotations.extend([dict(showarrow=False, # annotations for ticklabels on side 1 + + # Annotations for ticklabels on side 1 + annotations.extend([dict(showarrow=False, text=f'{ticklabel[j]}', x=posx[j+4], y=posy[j+4], align='center', - xanchor='left', + xanchor='left', yanchor='middle', font=dict(size=12)) for j in range(4)]) - annotations.extend([dict(showarrow=False, # annotations for ticklabels on side 2 + # Annotations for ticklabels on side 2 + annotations.extend([dict(showarrow=False, text=f'{ticklabel[j]}', x=posx[j+8], y=posy[j+8], align='center', - xanchor='right', + xanchor='right', yanchor='middle', font=dict(size=12)) for j in range(4)]) return annotations - -def styling_traces(xt, yt): +def _styling_traces_ternary(x_ticks, y_ticks): + # Outer triangle side_trace = dict(type='scatter', x=[0.5, 0, 1, 0.5], y=[np.sqrt(3)/2, 0, 0, np.sqrt(3)/2], @@ -164,8 +180,8 @@ def styling_traces(xt, yt): hoverinfo='none') tick_trace = dict(type='scatter', - x=xt, - y=yt, + x=x_ticks, + y=y_ticks, mode='lines', line=dict(width=1, color='#444444'), hoverinfo='none') @@ -173,129 +189,205 @@ def styling_traces(xt, yt): return side_trace, tick_trace - -def ternary_layout(title='Ternary contour plot', width=550, height=525, - fontfamily= 'Balto, sans-serif' , lfontsize=14, +def _ternary_layout(title='Ternary contour plot', width=550, height=525, + fontfamily='Balto, sans-serif', lfontsize=14, plot_bgcolor='rgb(240,240,240)', - vertex_text=['a', 'b', 'c'], v_fontsize=14): - - return dict(title=title, - font=dict(family=fontfamily, size=lfontsize), - width=width, height=height, - xaxis=dict(visible=False), - yaxis=dict(visible=False), - plot_bgcolor=plot_bgcolor, - showlegend=False, - #annotations for strings placed at the triangle vertices - annotations=[dict(showarrow=False, - text=vertex_text[0], - x=0.5, - y=np.sqrt(3)/2, - align='center', - xanchor='center', - yanchor='bottom', - font=dict(size=v_fontsize)), - dict(showarrow=False, - text=vertex_text[1], - x=0, - y=0, - align='left', - xanchor='right', - yanchor='top', - font=dict(size=v_fontsize)), - dict(showarrow=False, - text=vertex_text[2], - x=1, - y=0, - align='right', - xanchor='left', - yanchor='top', - font=dict(size=v_fontsize)) - ]) + pole_labels=['a', 'b', 'c'], v_fontsize=14): + return dict(title=title, + font=dict(family=fontfamily, size=lfontsize), + width=width, height=height, + xaxis=dict(visible=False), + yaxis=dict(visible=False), + plot_bgcolor=plot_bgcolor, + showlegend=False, + # annotations for strings placed at the triangle vertices + annotations=[dict(showarrow=False, + text=pole_labels[0], + x=0.5, + y=np.sqrt(3)/2, + align='center', + xanchor='center', + yanchor='bottom', + font=dict(size=v_fontsize)), + dict(showarrow=False, + text=pole_labels[1], + x=0, + y=0, + align='left', + xanchor='right', + yanchor='top', + font=dict(size=v_fontsize)), + dict(showarrow=False, + text=pole_labels[2], + x=1, + y=0, + align='right', + xanchor='left', + yanchor='top', + font=dict(size=v_fontsize)) + ]) def _tooltip(N, bar_coords, grid_z, xy1, mode='proportions'): if mode == 'proportions': tooltip = [ - [f'a: {round(bar_coords[0][i,j], 2)}
b: {round(bar_coords[1][i,j], 2)}'+ - f'
c: {round(1-round(bar_coords[0][i,j], 2) - round(bar_coords[1][i,j], 2), 2)}'+ - f'
z: {round(grid_z[i,j],2)}' - if ~np.isnan(xy1[0][i, j]) else '' for j in range(N)] - for i in range(N)] - else: + [f'a: {round(bar_coords[0][i,j], 2)}
b: {round(bar_coords[1][i,j], 2)}' + + f'
c: {round(1-round(bar_coords[0][i,j], 2) - round(bar_coords[1][i,j], 2), 2)}' + + f'
z: {round(grid_z[i,j],2)}' + if ~np.isnan(xy1[0][i, j]) else '' for j in range(N)] + for i in range(N)] + elif mode == 'percents': tooltip = [ - [f'a: {int(100*bar_coords[0][i,j]+0.5)}
b: {int(100*bar_coords[1][i,j]+0.5)}'+ - f'
c: {100-int(100*bar_coords[0][i,j]+0.5) -int(100*bar_coords[1][i,j]+0.5)}'+ - f'
z: {round(grid_z[i,j],2)}' - if ~np.isnan(xy1[0][i, j]) else '' for j in range(N)] - for i in range(N)] + [f'a: {int(100*bar_coords[0][i,j]+0.5)}
b: {int(100*bar_coords[1][i,j]+0.5)}' + + f'
c: {100-int(100*bar_coords[0][i,j]+0.5) -int(100*bar_coords[1][i,j]+0.5)}' + + f'
z: {round(grid_z[i,j],2)}' + if ~np.isnan(xy1[0][i, j]) else '' for j in range(N)] + for i in range(N)] + else: + raise ValueError("""tooltip mode must be either "proportions" or + "percents".""") return tooltip -def _compute_grid(data, M, N=150): - A, B, C, z = data - M, invM = tr_b2c2b() +def _prepare_barycentric_coord(b_coords): + """ + check ternary data and return the right barycentric coordinates + """ + if not isinstance(b_coords, (list, np.ndarray)): + raise ValueError('Data should be either an array of shape (n,m), or a list of n m-lists, m=2 or 3') + b_coords = np.asarray(b_coords) + if b_coords.shape[0] not in (2, 3): + raise ValueError('A point should have 2 (a, b) or 3 (a, b, c) barycentric coordinates') + if ((len(b_coords) == 3) and + not np.allclose(b_coords.sum(axis=0), 1, rtol=0.01)): + msg = "The sum of coordinates is not one for all data points" + warnings.warn(msg) + A, B = b_coords[:2] + C = 1 - (A + B) + return A, B, C + + +def _compute_grid(coordinates, values, tooltip_mode): + A, B, C = _prepare_barycentric_coord(coordinates) + M, invM = _transform_barycentric_cartesian() cartes_coord_points = np.einsum('ik, kj -> ij', M, np.stack((A, B, C))) xx, yy = cartes_coord_points[:2] x_min, x_max = xx.min(), xx.max() y_min, y_max = yy.min(), yy.max() - gr_x = np.linspace(x_min, x_max, N) - gr_y = np.linspace(y_min, y_max, N) + n_interp = max(100, int(np.sqrt(len(values)))) + gr_x = np.linspace(x_min, x_max, n_interp) + gr_y = np.linspace(y_min, y_max, n_interp) grid_x, grid_y = np.meshgrid(gr_x, gr_y) - grid_z = griddata(cartes_coord_points[:2].T, z, (grid_x, grid_y), method='cubic') + grid_z = griddata(cartes_coord_points[:2].T, values, (grid_x, grid_y), + method='cubic') bar_coords = np.einsum('ik, kmn -> imn', invM, - np.stack((grid_x, grid_y, np.ones(grid_x.shape)))) + np.stack((grid_x, grid_y, np.ones(grid_x.shape)))) # invalidate the points outside of the reference triangle - bar_coords[np.where(bar_coords < 0)] = None + bar_coords[np.where(bar_coords < 0)] = None # recompute back the cartesian coordinates of bar_coords with # invalid positions xy1 = np.einsum('ik, kmn -> imn', M, bar_coords) is_nan = np.where(np.isnan(xy1[0])) grid_z[is_nan] = None - return grid_z - + tooltip = _tooltip(n_interp, bar_coords, grid_z, xy1, tooltip_mode) + return grid_z, gr_x, gr_y, tooltip +def create_ternarycontour(coordinates, values, pole_labels=['a', 'b', 'c'], + tooltip_mode='proportion', width=500, height=500, + showscale=False, coloring=None, + showlabels=False, colorscale=None, + plot_bgcolor='rgb(240,240,240)', + title=None, + smoothing=False): + """ + Ternary contour plot. + + Parameters + ---------- + + coordinates : list or ndarray + Barycentric coordinates of shape (2, N) or (3, N) where N is the + number of points. + values : array-like + Field to be represented as contours. + pole_labels : str, default ['a', 'b', 'c'] + Names of the three poles of the triangle. + tooltip_mode : str, 'proportions' or 'percents' + Coordinates inside the ternary plot can be displayed either as + proportions (adding up to 1) or as percents (adding up to 100). + width : int + Figure width. + height : int + Figure height. + showscale : bool, default False + If True, a colorbar showing the color scale is displayed. + coloring : None or 'lines' + How to display contour. Filled contours if None, lines if ``lines``. + showlabels : bool, default False + For line contours (coloring='lines'), the value of the contour is + displayed if showlabels is True. + colorscale : None or array-like + colorscale of the contours. + plot_bgcolor : + color of figure background + title : str or None + Title of ternary plot + smoothing : bool + If True, contours are smoothed. + + Examples + ======== + + Example 1: ternary contour plot with filled contours + + # Define coordinates + a, b = np.mgrid[0:1:20j, 0:1:20j] + a = a.ravel() + b = b.ravel() + c = 1 - a - b + # Values to be displayed as contours + z = a * b * c + fig = ff.create_ternarycontour(np.stack((a, b, c)), z) + + It is also possible to give only two barycentric coordinates for each + point, since the sum of the three coordinates is one: + + fig = ff.create_ternarycontour(np.stack((a, b)), z) + + Example 2: ternary contour plot with line contours + + fig = ff.create_ternarycontour(np.stack((a, b)), z, coloring='lines') + + Labels of contour plots can be displayed on the contours: + + fig = ff.create_ternarycontour(np.stack((a, b)), z, coloring='lines', + showlabels=True) -def create_ternarycontour(data, N=150, tooltip_mode='proportion', - showscale=False, coloring='lines', **kwargs): - A, B, C, z = data - M, invM = tr_b2c2b() - cartes_coord_points = np.einsum('ik, kj -> ij', M, np.stack((A, B, C))) - xx, yy = cartes_coord_points[:2] - x_min, x_max = xx.min(), xx.max() - y_min, y_max = yy.min(), yy.max() - gr_x = np.linspace(x_min, x_max, N) - gr_y = np.linspace(y_min, y_max, N) - grid_x, grid_y = np.meshgrid(gr_x, gr_y) - grid_z = griddata(cartes_coord_points[:2].T, z, (grid_x, grid_y), method='cubic') - bar_coords = np.einsum('ik, kmn -> imn', invM, - np.stack((grid_x, grid_y, np.ones(grid_x.shape)))) - # invalidate the points outside of the reference triangle - bar_coords[np.where(bar_coords < 0)] = None - # recompute back the cartesian coordinates of bar_coords with - # invalid positions - xy1 = np.einsum('ik, kmn -> imn', M, bar_coords) - is_nan = np.where(np.isnan(xy1[0])) - grid_z[is_nan] = None - - xt = [] - yt = [] - posx = [] - posy = [] - for side in [0, 1, 2]: - xt, yt, posx, posy = cart_coord_ticks(side, xt, yt, posx, posy, t=0.01) - - layout = ternary_layout() - annotations = set_ticklabels(layout['annotations'], posx, posy, - proportion=True) - pl_deep = _pl_deep() - tooltip = _tooltip(N, bar_coords, grid_z, xy1, tooltip_mode) - c_trace = contour_trace(gr_x, gr_y, grid_z, tooltip, showscale=showscale, - colorscale=pl_deep, reversescale=True, - coloring=coloring) - side_trace, tick_trace = styling_traces(xt, yt) - fig = go.FigureWidget(data=[c_trace, tick_trace, side_trace], - layout=layout) + """ + M, invM = _transform_barycentric_cartesian() + grid_z, gr_x, gr_y, tooltip = _compute_grid(coordinates, values, + tooltip_mode) + + x_ticks, y_ticks, posx, posy = _cart_coord_ticks(t=0.01) + + layout = _ternary_layout(pole_labels=pole_labels, + width=width, height=height, title=title, + plot_bgcolor=plot_bgcolor) + + annotations = _set_ticklabels(layout['annotations'], posx, posy, + proportion=True) + if colorscale is None: + colorscale = _pl_deep() + + contour_trace = _contour_trace(gr_x, gr_y, grid_z, tooltip, + showscale=showscale, + showlabels=showlabels, + colorscale=colorscale, reversescale=True, + coloring=coloring, + smoothing=smoothing) + side_trace, tick_trace = _styling_traces_ternary(x_ticks, y_ticks) + fig = go.FigureWidget(data=[contour_trace, tick_trace, side_trace], + layout=layout) fig.layout.annotations = annotations return fig diff --git a/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py index 37834ec395..382becd4b6 100644 --- a/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py @@ -2858,6 +2858,7 @@ def test_full_choropleth(self): self.assertEqual(fig['data'][2]['x'][:50], exp_fig_head) + class TestQuiver(TestCase): def test_scaleratio_param(self): @@ -2897,3 +2898,16 @@ def test_scaleratio_param(self): self.assertEqual(fig_head, exp_fig_head) +class TestTernarycontour(NumpyTestUtilsMixin, TestCase): + + + def test_simple_ternary_contour(self): + a, b = np.mgrid[0:1:20j, 0:1:20j] + a = a.ravel() + b = b.ravel() + c = 1 - a - b + z = a * b * c + fig = ff.create_ternarycontour(np.stack((a, b, c)), z) + fig2 = ff.create_ternarycontour(np.stack((a, b)), z) + np.testing.assert_array_equal(fig2['data'][0]['z'], + fig['data'][0]['z']) From 0c02bebf36be34d87611b53f5db76eb650d1e94f Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 28 Jan 2019 08:29:18 +0100 Subject: [PATCH 03/12] Changed string formatting to be compatible with Python <3.6 --- plotly/figure_factory/_ternarycontour.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plotly/figure_factory/_ternarycontour.py b/plotly/figure_factory/_ternarycontour.py index 2fd0b8a72d..d9b28361f0 100644 --- a/plotly/figure_factory/_ternarycontour.py +++ b/plotly/figure_factory/_ternarycontour.py @@ -140,7 +140,7 @@ def _set_ticklabels(annotations, posx, posy, proportion=True): # Annotations for ticklabels on side 0 annotations.extend([dict(showarrow=False, - text=f'{ticklabel[j]}', + text=str(ticklabel[j]), x=posx[j], y=posy[j], align='center', @@ -150,7 +150,7 @@ def _set_ticklabels(annotations, posx, posy, proportion=True): # Annotations for ticklabels on side 1 annotations.extend([dict(showarrow=False, - text=f'{ticklabel[j]}', + text=str(ticklabel[j]), x=posx[j+4], y=posy[j+4], align='center', @@ -160,7 +160,7 @@ def _set_ticklabels(annotations, posx, posy, proportion=True): # Annotations for ticklabels on side 2 annotations.extend([dict(showarrow=False, - text=f'{ticklabel[j]}', + text=str(ticklabel[j]), x=posx[j+8], y=posy[j+8], align='center', From 358b3769be02a98ef120147ba028cc3b38c65697 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 28 Jan 2019 17:35:12 +0100 Subject: [PATCH 04/12] Changed string format to be compatible with Python < 3.6 --- plotly/figure_factory/_ternarycontour.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/plotly/figure_factory/_ternarycontour.py b/plotly/figure_factory/_ternarycontour.py index d9b28361f0..60ef8166c8 100644 --- a/plotly/figure_factory/_ternarycontour.py +++ b/plotly/figure_factory/_ternarycontour.py @@ -231,18 +231,22 @@ def _ternary_layout(title='Ternary contour plot', width=550, height=525, def _tooltip(N, bar_coords, grid_z, xy1, mode='proportions'): if mode == 'proportions': tooltip = [ - [f'a: {round(bar_coords[0][i,j], 2)}
b: {round(bar_coords[1][i,j], 2)}' + - f'
c: {round(1-round(bar_coords[0][i,j], 2) - round(bar_coords[1][i,j], 2), 2)}' + - f'
z: {round(grid_z[i,j],2)}' + ['a: %.2f' % round(bar_coords[0][i, j], 2) + + '
b: %.2f' % round(bar_coords[1][i, j], 2) + + '
c: %.2f' % (round(1-round(bar_coords[0][i, j], 2) - + round(bar_coords[1][i, j], 2), 2)) + + '
z: %.2f' % round(grid_z[i, j], 2) if ~np.isnan(xy1[0][i, j]) else '' for j in range(N)] for i in range(N)] elif mode == 'percents': tooltip = [ - [f'a: {int(100*bar_coords[0][i,j]+0.5)}
b: {int(100*bar_coords[1][i,j]+0.5)}' + - f'
c: {100-int(100*bar_coords[0][i,j]+0.5) -int(100*bar_coords[1][i,j]+0.5)}' + - f'
z: {round(grid_z[i,j],2)}' - if ~np.isnan(xy1[0][i, j]) else '' for j in range(N)] - for i in range(N)] + ['a: %d' % int(100*bar_coords[0][i, j] + 0.5) + + '
b: %d' % int(100*bar_coords[1][i, j] + 0.5) + + '
c: %d' % (100-int(100*bar_coords[0][i, j] + 0.5) - + int(100*bar_coords[1][i, j] + 0.5)) + + '
z: %.2f' % round(grid_z[i, j], 2) + if ~np.isnan(xy1[0][i, j]) else '' for j in range(N)] + for i in range(N)] else: raise ValueError("""tooltip mode must be either "proportions" or "percents".""") From 424542316bd94e356b6947d831e13b3a26f80421 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 28 Jan 2019 22:34:48 +0100 Subject: [PATCH 05/12] Improved documentation --- plotly/figure_factory/_ternarycontour.py | 267 +++++++++++++++++------ 1 file changed, 206 insertions(+), 61 deletions(-) diff --git a/plotly/figure_factory/_ternarycontour.py b/plotly/figure_factory/_ternarycontour.py index 60ef8166c8..1861b58b0a 100644 --- a/plotly/figure_factory/_ternarycontour.py +++ b/plotly/figure_factory/_ternarycontour.py @@ -22,10 +22,10 @@ def _pl_deep(): def _transform_barycentric_cartesian(): """ Returns the transformation matrix from barycentric to cartesian - coordinates and conversely + coordinates and conversely. """ # reference triangle - tri_verts = np.array([[0.5, np.sqrt(3)/2], [0, 0], [1, 0]]) + tri_verts = np.array([[0.5, np.sqrt(3) / 2], [0, 0], [1, 0]]) M = np.array([tri_verts[:, 0], tri_verts[:, 1], np.ones(3)]) return M, np.linalg.inv(M) @@ -37,8 +37,43 @@ def _contour_trace(x, y, z, tooltip, colorscale='Viridis', fontsize=12): """ Contour trace in Cartesian coordinates. + + Parameters + ========== + + x, y : array-like + Cartesian coordinates + z : array-like + Field to be represented as contours. + tooltip : list of str + Annotations to show on hover. + colorscale : str o array, optional + Colorscale to use for contours + reversescale : bool + Reverses the color mapping if true. If true, `zmin` + will correspond to the last color in the array and + `zmax` will correspond to the first color. + showscale : bool + If True, a colorbar showing the color scale is displayed. + linewidth : int + Line width of contours + linecolor : color string + Color on contours + smoothing : bool + If True, contours are smoothed. + coloring : None or 'lines' + How to display contour. Filled contours if None, lines if ``lines``. + showlabels : bool, default False + For line contours (coloring='lines'), the value of the contour is + displayed if showlabels is True. + colorscale : None or array-like + Colorscale of the contours. + fontcolor : color str + Color of contour labels. + fontsize : int + Font size of contour labels. """ - if showlabels and coloring is not 'lines': + if showlabels and (coloring is not 'lines'): msg = """`showlabels` was set to True, but labels can only be displayed for `coloring='lines'`""" warnings.warn(msg) @@ -66,9 +101,13 @@ def _contour_trace(x, y, z, tooltip, colorscale='Viridis', def barycentric_ticks(side): """ - side 0, 1 or 2; side j has 0 in the j^th position of barycentric - coords of tick origin - returns the list of tick origin barycentric coords + Barycentric coordinates of ticks locations. + + Parameters + ========== + side : 0, 1 or 2 + side j has 0 in the j^th position of barycentric coords of tick + origin. """ p = 10 if side == 0: # where a=0 @@ -77,65 +116,106 @@ def barycentric_ticks(side): return np.array([(i/p, 0, 1-i/p) for i in range(2, p, 2)]) elif side == 2: # c=0 return (np.array([(i/p, j/p, 0) - for i in range(p-2, 0, -2) - for j in range(p-i, -1, -1) if i+j == p])) + for i in range(p - 2, 0, -2) + for j in range(p - i, -1, -1) if i + j == p])) else: raise ValueError('The side can be only 0, 1, 2') -def _cart_coord_ticks(t=0.01): +def _side_coord_ticks(side, t=0.01): """ - side 0, 1 or 2 - each tick segment is parameterized as (x(s), y(s)), s in [0, t] - M is the transformation matrix from barycentric to cartesian coords - xt, yt are the lists of x, resp y-coords of tick segments - posx, posy are the lists of ticklabel positions for side 0, 1, 2 - (concatenated) + Cartesian coordinates of ticks loactions for one side (0, 1, 2) + of ternary diagram. + + Parameters + ========== + + side : int, 0, 1 or 2 + Index of side + t : float, default 0.01 + Length of tick + + Returns + ======= + xt, yt : lists + Lists of x, resp y-coords of tick segments + posx, posy : lists + Lists of ticklabel positions """ M, invM = _transform_barycentric_cartesian() - x_ticks, y_ticks, posx, posy = [], [], [], [] - # Side 0 - baryc = barycentric_ticks(0) + baryc = barycentric_ticks(side) xy1 = np.dot(M, baryc.T) xs, ys = xy1[:2] - for i in range(4): - x_ticks.extend([xs[i], xs[i]+t, None]) - y_ticks.extend([ys[i], ys[i]-np.sqrt(3)*t, None]) - posx.extend([xs[i]+t for i in range(4)]) - posy.extend([ys[i]-np.sqrt(3)*t for i in range(4)]) - # Side 1 - baryc = barycentric_ticks(1) - xy1 = np.dot(M, baryc.T) - xs, ys = xy1[:2] - for i in range(4): - x_ticks.extend([xs[i], xs[i]+t, None]) - y_ticks.extend([ys[i], ys[i]+np.sqrt(3)*t, None]) - posx.extend([xs[i]+t for i in range(4)]) - posy.extend([ys[i]+np.sqrt(3)*t for i in range(4)]) - # Side 2 - baryc = barycentric_ticks(2) - xy1 = np.dot(M, baryc.T) - xs, ys = xy1[:2] - for i in range(4): - x_ticks.extend([xs[i], xs[i]-2*t, None]) - y_ticks.extend([ys[i], ys[i], None]) - posx.extend([xs[i]-2*t for i in range(4)]) - posy.extend([ys[i] for i in range(4)]) + x_ticks, y_ticks, posx, posy = [], [], [], [] + if side == 0: + for i in range(4): + x_ticks.extend([xs[i], xs[i]+t, None]) + y_ticks.extend([ys[i], ys[i]-np.sqrt(3)*t, None]) + posx.extend([xs[i]+t for i in range(4)]) + posy.extend([ys[i]-np.sqrt(3)*t for i in range(4)]) + elif side == 1: + for i in range(4): + x_ticks.extend([xs[i], xs[i]+t, None]) + y_ticks.extend([ys[i], ys[i]+np.sqrt(3)*t, None]) + posx.extend([xs[i]+t for i in range(4)]) + posy.extend([ys[i]+np.sqrt(3)*t for i in range(4)]) + elif side == 2: + for i in range(4): + x_ticks.extend([xs[i], xs[i]-2*t, None]) + y_ticks.extend([ys[i], ys[i], None]) + posx.extend([xs[i]-2*t for i in range(4)]) + posy.extend([ys[i] for i in range(4)]) + else: + raise ValueError('Side can be only 0, 1, 2') return x_ticks, y_ticks, posx, posy -def _set_ticklabels(annotations, posx, posy, proportion=True): +def _cart_coord_ticks(t=0.01): + """ + Cartesian coordinates of ticks loactions. + + Parameters + ========== + + t : float, default 0.01 + Length of tick + + Returns + ======= + xt, yt : lists + Lists of x, resp y-coords of tick segments (all sides concatenated). + posx, posy : lists + Lists of ticklabel positions (all sides concatenated). """ - annotations: list of annotations previously defined in layout definition - as a dict, not as an instance of go.Layout - posx, posy: lists containing ticklabel position coordinates - proportion - boolean; True when ticklabels are 0.2, 0.4, ... - False when they are 20%, 40%... + x_ticks, y_ticks, posx, posy = [], [], [], [] + for side in range(3): + xt, yt, px, py = _side_coord_ticks(side, t) + x_ticks.extend(xt) + y_ticks.extend(yt) + posx.extend(px) + posy.extend(py) + return x_ticks, y_ticks, posx, posy + + +def _set_ticklabels(annotations, posx, posy, proportions=True): + """ + + Parameters + ========== + + annotations : list + List of annotations previously defined in layout definition + as a dict, not as an instance of go.Layout. + posx, posy: lists + Lists containing ticklabel position coordinates + proportions : bool + True when ticklabels are 0.2, 0.4, ... False when they are + 20%, 40%... """ if not isinstance(annotations, list): raise ValueError('annotations should be a list') - ticklabel = [0.8, 0.6, 0.4, 0.2] if proportion \ + ticklabel = [0.8, 0.6, 0.4, 0.2] if proportions \ else ['80%', '60%', '40%', '20%'] # Annotations for ticklabels on side 0 @@ -171,7 +251,17 @@ def _set_ticklabels(annotations, posx, posy, proportion=True): def _styling_traces_ternary(x_ticks, y_ticks): - # Outer triangle + """ + Traces for outer triangle of ternary plot, and corresponding ticks. + + Parameters + ========== + + x_ticks : array_like, 1D + x Cartesian coordinate of ticks + y_ticks : array_like, 1D + y Cartesian coordinate of ticks + """ side_trace = dict(type='scatter', x=[0.5, 0, 1, 0.5], y=[np.sqrt(3)/2, 0, 0, np.sqrt(3)/2], @@ -190,11 +280,34 @@ def _styling_traces_ternary(x_ticks, y_ticks): def _ternary_layout(title='Ternary contour plot', width=550, height=525, - fontfamily='Balto, sans-serif', lfontsize=14, - plot_bgcolor='rgb(240,240,240)', - pole_labels=['a', 'b', 'c'], v_fontsize=14): + fontfamily='Balto, sans-serif', colorbar_fontsize=14, + plot_bgcolor='rgb(240,240,240)', + pole_labels=['a', 'b', 'c'], label_fontsize=16): + """ + Layout of ternary contour plot, to be passed to ``go.FigureWidget`` + object. + + Parameters + ========== + title : str or None + Title of ternary plot + width : int + Figure width. + height : int + Figure height. + fontfamily : str + Family of fonts + colorbar_fontsize : int + Font size of colorbar. + plot_bgcolor : + color of figure background + pole_labels : str, default ['a', 'b', 'c'] + Names of the three poles of the triangle. + label_fontsize : int + Font size of pole labels. + """ return dict(title=title, - font=dict(family=fontfamily, size=lfontsize), + font=dict(family=fontfamily, size=colorbar_fontsize), width=width, height=height, xaxis=dict(visible=False), yaxis=dict(visible=False), @@ -208,7 +321,7 @@ def _ternary_layout(title='Ternary contour plot', width=550, height=525, align='center', xanchor='center', yanchor='bottom', - font=dict(size=v_fontsize)), + font=dict(size=label_fontsize)), dict(showarrow=False, text=pole_labels[1], x=0, @@ -216,7 +329,7 @@ def _ternary_layout(title='Ternary contour plot', width=550, height=525, align='left', xanchor='right', yanchor='top', - font=dict(size=v_fontsize)), + font=dict(size=label_fontsize)), dict(showarrow=False, text=pole_labels[2], x=1, @@ -224,11 +337,29 @@ def _ternary_layout(title='Ternary contour plot', width=550, height=525, align='right', xanchor='left', yanchor='top', - font=dict(size=v_fontsize)) + font=dict(size=label_fontsize)) ]) def _tooltip(N, bar_coords, grid_z, xy1, mode='proportions'): + """ + Tooltip annotations to be displayed on hover. + + Parameters + ========== + + N : int + Number of annotations along each axis. + bar_coords : array-like + Barycentric coordinates. + grid_z : array + Values (e.g. elevation values) at barycentric coordinates. + xy1 : array-like + Cartesian coordinates. + mode : str, 'proportions' or 'percents' + Coordinates inside the ternary plot can be displayed either as + proportions (adding up to 1) or as percents (adding up to 100). + """ if mode == 'proportions': tooltip = [ ['a: %.2f' % round(bar_coords[0][i, j], 2) + @@ -255,7 +386,7 @@ def _tooltip(N, bar_coords, grid_z, xy1, mode='proportions'): def _prepare_barycentric_coord(b_coords): """ - check ternary data and return the right barycentric coordinates + Check ternary coordinates and return the right barycentric coordinates. """ if not isinstance(b_coords, (list, np.ndarray)): raise ValueError('Data should be either an array of shape (n,m), or a list of n m-lists, m=2 or 3') @@ -272,6 +403,21 @@ def _prepare_barycentric_coord(b_coords): def _compute_grid(coordinates, values, tooltip_mode): + """ + Compute interpolation of data points on regular grid in Cartesian + coordinates. + + Parameters + ========== + + coordinates : array-like + Barycentric coordinates of data points. + values : 1-d array-like + Data points, field to be represented as contours. + tooltip_mode : str, 'proportions' or 'percents' + Coordinates inside the ternary plot can be displayed either as + proportions (adding up to 1) or as percents (adding up to 100). + """ A, B, C = _prepare_barycentric_coord(coordinates) M, invM = _transform_barycentric_cartesian() cartes_coord_points = np.einsum('ik, kj -> ij', M, np.stack((A, B, C))) @@ -312,9 +458,9 @@ def create_ternarycontour(coordinates, values, pole_labels=['a', 'b', 'c'], coordinates : list or ndarray Barycentric coordinates of shape (2, N) or (3, N) where N is the - number of points. + number of data points. values : array-like - Field to be represented as contours. + Data points of field to be represented as contours. pole_labels : str, default ['a', 'b', 'c'] Names of the three poles of the triangle. tooltip_mode : str, 'proportions' or 'percents' @@ -369,7 +515,6 @@ def create_ternarycontour(coordinates, values, pole_labels=['a', 'b', 'c'], showlabels=True) """ - M, invM = _transform_barycentric_cartesian() grid_z, gr_x, gr_y, tooltip = _compute_grid(coordinates, values, tooltip_mode) @@ -380,7 +525,7 @@ def create_ternarycontour(coordinates, values, pole_labels=['a', 'b', 'c'], plot_bgcolor=plot_bgcolor) annotations = _set_ticklabels(layout['annotations'], posx, posy, - proportion=True) + proportions=True) if colorscale is None: colorscale = _pl_deep() From bcd35e8081299cc990b80bd881afbcef775b8e82 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 28 Jan 2019 22:38:01 +0100 Subject: [PATCH 06/12] corrected bug, typo in kw argument --- plotly/figure_factory/_ternarycontour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plotly/figure_factory/_ternarycontour.py b/plotly/figure_factory/_ternarycontour.py index 1861b58b0a..7276e886e1 100644 --- a/plotly/figure_factory/_ternarycontour.py +++ b/plotly/figure_factory/_ternarycontour.py @@ -444,7 +444,7 @@ def _compute_grid(coordinates, values, tooltip_mode): def create_ternarycontour(coordinates, values, pole_labels=['a', 'b', 'c'], - tooltip_mode='proportion', width=500, height=500, + tooltip_mode='proportions', width=500, height=500, showscale=False, coloring=None, showlabels=False, colorscale=None, plot_bgcolor='rgb(240,240,240)', From a32f58f4efadee0252dd263794093cf8a8ded614 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Mon, 28 Jan 2019 22:51:56 +0100 Subject: [PATCH 07/12] fixed encoding error --- plotly/figure_factory/_ternarycontour.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plotly/figure_factory/_ternarycontour.py b/plotly/figure_factory/_ternarycontour.py index 7276e886e1..60766f1b76 100644 --- a/plotly/figure_factory/_ternarycontour.py +++ b/plotly/figure_factory/_ternarycontour.py @@ -434,8 +434,7 @@ def _compute_grid(coordinates, values, tooltip_mode): np.stack((grid_x, grid_y, np.ones(grid_x.shape)))) # invalidate the points outside of the reference triangle bar_coords[np.where(bar_coords < 0)] = None - # recompute back the cartesian coordinates of bar_coords with - # invalid positions + # recompute back cartesian coordinates with invalid positions xy1 = np.einsum('ik, kmn -> imn', M, bar_coords) is_nan = np.where(np.isnan(xy1[0])) grid_z[is_nan] = None From 3d8f9bff3301abb5ffd19e6282f3cdd8595dc591 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 29 Jan 2019 15:36:07 +0100 Subject: [PATCH 08/12] added tests for ternary contour plot --- plotly/figure_factory/_ternarycontour.py | 30 +++++++-- .../test_figure_factory.py | 67 ++++++++++++++++++- 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/plotly/figure_factory/_ternarycontour.py b/plotly/figure_factory/_ternarycontour.py index 60766f1b76..4963c5c1b8 100644 --- a/plotly/figure_factory/_ternarycontour.py +++ b/plotly/figure_factory/_ternarycontour.py @@ -30,7 +30,7 @@ def _transform_barycentric_cartesian(): return M, np.linalg.inv(M) -def _contour_trace(x, y, z, tooltip, colorscale='Viridis', +def _contour_trace(x, y, z, tooltip, ncontours=None, colorscale='Viridis', reversescale=False, showscale=False, linewidth=0.5, linecolor='rgb(150,150,150)', smoothing=False, coloring=None, showlabels=False, fontcolor='blue', @@ -47,6 +47,8 @@ def _contour_trace(x, y, z, tooltip, colorscale='Viridis', Field to be represented as contours. tooltip : list of str Annotations to show on hover. + ncontours : int or None + Number of contours to display (determined automatically if None). colorscale : str o array, optional Colorscale to use for contours reversescale : bool @@ -82,6 +84,7 @@ def _contour_trace(x, y, z, tooltip, colorscale='Viridis', x=x, y=y, z=z, text=tooltip, hoverinfo='text', + ncontours=ncontours, colorscale=colorscale, reversescale=reversescale, showscale=showscale, @@ -360,7 +363,7 @@ def _tooltip(N, bar_coords, grid_z, xy1, mode='proportions'): Coordinates inside the ternary plot can be displayed either as proportions (adding up to 1) or as percents (adding up to 100). """ - if mode == 'proportions': + if mode == 'proportions' or mode == 'proportion': tooltip = [ ['a: %.2f' % round(bar_coords[0][i, j], 2) + '
b: %.2f' % round(bar_coords[1][i, j], 2) + @@ -369,7 +372,7 @@ def _tooltip(N, bar_coords, grid_z, xy1, mode='proportions'): '
z: %.2f' % round(grid_z[i, j], 2) if ~np.isnan(xy1[0][i, j]) else '' for j in range(N)] for i in range(N)] - elif mode == 'percents': + elif mode == 'percents' or mode == 'percent': tooltip = [ ['a: %d' % int(100*bar_coords[0][i, j] + 0.5) + '
b: %d' % int(100*bar_coords[1][i, j] + 0.5) + @@ -395,10 +398,12 @@ def _prepare_barycentric_coord(b_coords): raise ValueError('A point should have 2 (a, b) or 3 (a, b, c) barycentric coordinates') if ((len(b_coords) == 3) and not np.allclose(b_coords.sum(axis=0), 1, rtol=0.01)): - msg = "The sum of coordinates is not one for all data points" - warnings.warn(msg) + msg = "The sum of coordinates should be one for all data points" + raise ValueError(msg) A, B = b_coords[:2] C = 1 - (A + B) + if np.any(np.stack((A, B, C)) < 0): + raise ValueError('Barycentric coordinates should be positive.') return A, B, C @@ -444,8 +449,10 @@ def _compute_grid(coordinates, values, tooltip_mode): def create_ternarycontour(coordinates, values, pole_labels=['a', 'b', 'c'], tooltip_mode='proportions', width=500, height=500, + ncontours=None, showscale=False, coloring=None, showlabels=False, colorscale=None, + reversescale=False, plot_bgcolor='rgb(240,240,240)', title=None, smoothing=False): @@ -457,7 +464,8 @@ def create_ternarycontour(coordinates, values, pole_labels=['a', 'b', 'c'], coordinates : list or ndarray Barycentric coordinates of shape (2, N) or (3, N) where N is the - number of data points. + number of data points. The sum of the 3 coordinates is expected + to be 1 for all data points. values : array-like Data points of field to be represented as contours. pole_labels : str, default ['a', 'b', 'c'] @@ -469,6 +477,8 @@ def create_ternarycontour(coordinates, values, pole_labels=['a', 'b', 'c'], Figure width. height : int Figure height. + ncontours : int or None + Number of contours to display (determined automatically if None). showscale : bool, default False If True, a colorbar showing the color scale is displayed. coloring : None or 'lines' @@ -478,6 +488,10 @@ def create_ternarycontour(coordinates, values, pole_labels=['a', 'b', 'c'], displayed if showlabels is True. colorscale : None or array-like colorscale of the contours. + reversescale : bool + Reverses the color mapping if true. If true, `zmin` + will correspond to the last color in the array and + `zmax` will correspond to the first color. plot_bgcolor : color of figure background title : str or None @@ -529,9 +543,11 @@ def create_ternarycontour(coordinates, values, pole_labels=['a', 'b', 'c'], colorscale = _pl_deep() contour_trace = _contour_trace(gr_x, gr_y, grid_z, tooltip, + ncontours=ncontours, showscale=showscale, showlabels=showlabels, - colorscale=colorscale, reversescale=True, + colorscale=colorscale, + reversescale=reversescale, coloring=coloring, smoothing=smoothing) side_trace, tick_trace = _styling_traces_ternary(x_ticks, y_ticks) diff --git a/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py index 382becd4b6..5289fc7057 100644 --- a/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py @@ -2900,14 +2900,77 @@ def test_scaleratio_param(self): class TestTernarycontour(NumpyTestUtilsMixin, TestCase): - - def test_simple_ternary_contour(self): + def test_wrong_coordinates(self): a, b = np.mgrid[0:1:20j, 0:1:20j] a = a.ravel() b = b.ravel() + z = a * b + with self.assertRaises(ValueError, + msg='Barycentric coordinates should be positive.'): + _ = ff.create_ternarycontour(np.stack((a, b)), z) + mask = a + b < 1. + a = a[mask] + b = b[mask] + with self.assertRaises(ValueError): + _ = ff.create_ternarycontour(np.stack((a, b, a, b)), z) + with self.assertRaises(ValueError, + msg='different number of values and points'): + _ = ff.create_ternarycontour(np.stack((a, b, 1 - a - b)), + np.concatenate((z, [1]))) + # Different sums for different points + c = a + with self.assertRaises(ValueError): + _ = ff.create_ternarycontour(np.stack((a, b, c)), z) + # Sum of coordinates is different from one but is equal + # for all points. + with self.assertRaises(ValueError): + _ = ff.create_ternarycontour(np.stack((a, b, 2 - a - b)), z) + + + def test_tooltip(self): + a, b = np.mgrid[0:1:20j, 0:1:20j] + mask = a + b < 1. + a = a[mask].ravel() + b = b[mask].ravel() + c = 1 - a - b + z = a * b * c + fig = ff.create_ternarycontour(np.stack((a, b, c)), z, + tooltip_mode='percents') + fig = ff.create_ternarycontour(np.stack((a, b, c)), z, + tooltip_mode='percent') + + with self.assertRaises(ValueError): + fig = ff.create_ternarycontour(np.stack((a, b, c)), z, + tooltip_mode='wrong_mode') + + + def test_simple_ternary_contour(self): + a, b = np.mgrid[0:1:20j, 0:1:20j] + mask = a + b < 1. + a = a[mask].ravel() + b = b[mask].ravel() c = 1 - a - b z = a * b * c fig = ff.create_ternarycontour(np.stack((a, b, c)), z) fig2 = ff.create_ternarycontour(np.stack((a, b)), z) np.testing.assert_array_equal(fig2['data'][0]['z'], fig['data'][0]['z']) + + + def test_contour_attributes(self): + a, b = np.mgrid[0:1:20j, 0:1:20j] + mask = a + b < 1. + a = a[mask].ravel() + b = b[mask].ravel() + c = 1 - a - b + z = a * b * c + contour_dict = {'ncontours': 10, + 'showscale': True, + 'reversescale': False} + + + fig = ff.create_ternarycontour(np.stack((a, b, c)), z, **contour_dict) + for key, value in contour_dict.items(): + assert fig['data'][0][key] == value + + From 581b93cf9b6a4bd03664b42e2d8c03e873a94fb9 Mon Sep 17 00:00:00 2001 From: Emmanuelle Gouillart Date: Tue, 29 Jan 2019 23:18:01 +0100 Subject: [PATCH 09/12] updated docstring --- plotly/figure_factory/_ternarycontour.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plotly/figure_factory/_ternarycontour.py b/plotly/figure_factory/_ternarycontour.py index 4963c5c1b8..49bea67da5 100644 --- a/plotly/figure_factory/_ternarycontour.py +++ b/plotly/figure_factory/_ternarycontour.py @@ -506,8 +506,9 @@ def create_ternarycontour(coordinates, values, pole_labels=['a', 'b', 'c'], # Define coordinates a, b = np.mgrid[0:1:20j, 0:1:20j] - a = a.ravel() - b = b.ravel() + mask = a + b <= 1 + a = a[mask].ravel() + b = b[mask].ravel() c = 1 - a - b # Values to be displayed as contours z = a * b * c From d80e0702b2840d3240047f1f6666b0d35988be5d Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Thu, 31 Jan 2019 06:50:33 -0500 Subject: [PATCH 10/12] FigureWidget -> Figure for consistency with other figure factories --- plotly/figure_factory/_ternarycontour.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plotly/figure_factory/_ternarycontour.py b/plotly/figure_factory/_ternarycontour.py index 49bea67da5..5f2805141c 100644 --- a/plotly/figure_factory/_ternarycontour.py +++ b/plotly/figure_factory/_ternarycontour.py @@ -429,7 +429,8 @@ def _compute_grid(coordinates, values, tooltip_mode): xx, yy = cartes_coord_points[:2] x_min, x_max = xx.min(), xx.max() y_min, y_max = yy.min(), yy.max() - n_interp = max(100, int(np.sqrt(len(values)))) + # n_interp = max(100, int(np.sqrt(len(values)))) + n_interp = 20 gr_x = np.linspace(x_min, x_max, n_interp) gr_y = np.linspace(y_min, y_max, n_interp) grid_x, grid_y = np.meshgrid(gr_x, gr_y) @@ -438,11 +439,11 @@ def _compute_grid(coordinates, values, tooltip_mode): bar_coords = np.einsum('ik, kmn -> imn', invM, np.stack((grid_x, grid_y, np.ones(grid_x.shape)))) # invalidate the points outside of the reference triangle - bar_coords[np.where(bar_coords < 0)] = None + bar_coords[np.where(bar_coords < 0)] = 0 # None # recompute back cartesian coordinates with invalid positions xy1 = np.einsum('ik, kmn -> imn', M, bar_coords) is_nan = np.where(np.isnan(xy1[0])) - grid_z[is_nan] = None + grid_z[is_nan] = 0 # None tooltip = _tooltip(n_interp, bar_coords, grid_z, xy1, tooltip_mode) return grid_z, gr_x, gr_y, tooltip @@ -552,7 +553,7 @@ def create_ternarycontour(coordinates, values, pole_labels=['a', 'b', 'c'], coloring=coloring, smoothing=smoothing) side_trace, tick_trace = _styling_traces_ternary(x_ticks, y_ticks) - fig = go.FigureWidget(data=[contour_trace, tick_trace, side_trace], - layout=layout) + fig = go.Figure(data=[contour_trace, tick_trace, side_trace], + layout=layout) fig.layout.annotations = annotations return fig From 9f6aee110b32f1ee3f81e676130f50f8c233f017 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Fri, 1 Feb 2019 06:03:20 -0500 Subject: [PATCH 11/12] ternarycontour -> ternary_contour --- plotly/figure_factory/__init__.py | 2 +- ..._ternarycontour.py => _ternary_contour.py} | 26 ++++++++-------- .../test_figure_factory.py | 30 +++++++++---------- 3 files changed, 29 insertions(+), 29 deletions(-) rename plotly/figure_factory/{_ternarycontour.py => _ternary_contour.py} (96%) diff --git a/plotly/figure_factory/__init__.py b/plotly/figure_factory/__init__.py index 910ffa62f1..b7dd72c21b 100644 --- a/plotly/figure_factory/__init__.py +++ b/plotly/figure_factory/__init__.py @@ -18,7 +18,7 @@ from plotly.figure_factory._scatterplot import create_scatterplotmatrix from plotly.figure_factory._streamline import create_streamline from plotly.figure_factory._table import create_table -from plotly.figure_factory._ternarycontour import create_ternarycontour +from plotly.figure_factory._ternary_contour import create_ternary_contour from plotly.figure_factory._trisurf import create_trisurf from plotly.figure_factory._violin import create_violin if optional_imports.get_module('pandas') is not None: diff --git a/plotly/figure_factory/_ternarycontour.py b/plotly/figure_factory/_ternary_contour.py similarity index 96% rename from plotly/figure_factory/_ternarycontour.py rename to plotly/figure_factory/_ternary_contour.py index 5f2805141c..f1bb53ddf5 100644 --- a/plotly/figure_factory/_ternarycontour.py +++ b/plotly/figure_factory/_ternary_contour.py @@ -448,15 +448,15 @@ def _compute_grid(coordinates, values, tooltip_mode): return grid_z, gr_x, gr_y, tooltip -def create_ternarycontour(coordinates, values, pole_labels=['a', 'b', 'c'], - tooltip_mode='proportions', width=500, height=500, - ncontours=None, - showscale=False, coloring=None, - showlabels=False, colorscale=None, - reversescale=False, - plot_bgcolor='rgb(240,240,240)', - title=None, - smoothing=False): +def create_ternary_contour(coordinates, values, pole_labels=['a', 'b', 'c'], + tooltip_mode='proportions', width=500, height=500, + ncontours=None, + showscale=False, coloring=None, + showlabels=False, colorscale=None, + reversescale=False, + plot_bgcolor='rgb(240,240,240)', + title=None, + smoothing=False): """ Ternary contour plot. @@ -513,20 +513,20 @@ def create_ternarycontour(coordinates, values, pole_labels=['a', 'b', 'c'], c = 1 - a - b # Values to be displayed as contours z = a * b * c - fig = ff.create_ternarycontour(np.stack((a, b, c)), z) + fig = ff.create_ternary_contour(np.stack((a, b, c)), z) It is also possible to give only two barycentric coordinates for each point, since the sum of the three coordinates is one: - fig = ff.create_ternarycontour(np.stack((a, b)), z) + fig = ff.create_ternary_contour(np.stack((a, b)), z) Example 2: ternary contour plot with line contours - fig = ff.create_ternarycontour(np.stack((a, b)), z, coloring='lines') + fig = ff.create_ternary_contour(np.stack((a, b)), z, coloring='lines') Labels of contour plots can be displayed on the contours: - fig = ff.create_ternarycontour(np.stack((a, b)), z, coloring='lines', + fig = ff.create_ternary_contour(np.stack((a, b)), z, coloring='lines', showlabels=True) """ diff --git a/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py index 5289fc7057..c731015a46 100644 --- a/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py @@ -2907,24 +2907,24 @@ def test_wrong_coordinates(self): z = a * b with self.assertRaises(ValueError, msg='Barycentric coordinates should be positive.'): - _ = ff.create_ternarycontour(np.stack((a, b)), z) + _ = ff.create_ternary_contour(np.stack((a, b)), z) mask = a + b < 1. a = a[mask] b = b[mask] with self.assertRaises(ValueError): - _ = ff.create_ternarycontour(np.stack((a, b, a, b)), z) + _ = ff.create_ternary_contour(np.stack((a, b, a, b)), z) with self.assertRaises(ValueError, msg='different number of values and points'): - _ = ff.create_ternarycontour(np.stack((a, b, 1 - a - b)), - np.concatenate((z, [1]))) + _ = ff.create_ternary_contour(np.stack((a, b, 1 - a - b)), + np.concatenate((z, [1]))) # Different sums for different points c = a with self.assertRaises(ValueError): - _ = ff.create_ternarycontour(np.stack((a, b, c)), z) + _ = ff.create_ternary_contour(np.stack((a, b, c)), z) # Sum of coordinates is different from one but is equal # for all points. with self.assertRaises(ValueError): - _ = ff.create_ternarycontour(np.stack((a, b, 2 - a - b)), z) + _ = ff.create_ternary_contour(np.stack((a, b, 2 - a - b)), z) def test_tooltip(self): @@ -2934,14 +2934,14 @@ def test_tooltip(self): b = b[mask].ravel() c = 1 - a - b z = a * b * c - fig = ff.create_ternarycontour(np.stack((a, b, c)), z, - tooltip_mode='percents') - fig = ff.create_ternarycontour(np.stack((a, b, c)), z, - tooltip_mode='percent') + fig = ff.create_ternary_contour(np.stack((a, b, c)), z, + tooltip_mode='percents') + fig = ff.create_ternary_contour(np.stack((a, b, c)), z, + tooltip_mode='percent') with self.assertRaises(ValueError): - fig = ff.create_ternarycontour(np.stack((a, b, c)), z, - tooltip_mode='wrong_mode') + fig = ff.create_ternary_contour(np.stack((a, b, c)), z, + tooltip_mode='wrong_mode') def test_simple_ternary_contour(self): @@ -2951,8 +2951,8 @@ def test_simple_ternary_contour(self): b = b[mask].ravel() c = 1 - a - b z = a * b * c - fig = ff.create_ternarycontour(np.stack((a, b, c)), z) - fig2 = ff.create_ternarycontour(np.stack((a, b)), z) + fig = ff.create_ternary_contour(np.stack((a, b, c)), z) + fig2 = ff.create_ternary_contour(np.stack((a, b)), z) np.testing.assert_array_equal(fig2['data'][0]['z'], fig['data'][0]['z']) @@ -2969,7 +2969,7 @@ def test_contour_attributes(self): 'reversescale': False} - fig = ff.create_ternarycontour(np.stack((a, b, c)), z, **contour_dict) + fig = ff.create_ternary_contour(np.stack((a, b, c)), z, **contour_dict) for key, value in contour_dict.items(): assert fig['data'][0][key] == value From bc26b99f2e05e71a321f063d26c5f30c768917a9 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Fri, 1 Feb 2019 06:10:56 -0500 Subject: [PATCH 12/12] Remove the reversescale, smoothing, and showlabels options so that we don't have to support them initially when we convert this figure factory to represent contours using scatterternary traces. --- plotly/figure_factory/_ternary_contour.py | 60 ++++--------------- .../test_figure_factory.py | 6 +- 2 files changed, 12 insertions(+), 54 deletions(-) diff --git a/plotly/figure_factory/_ternary_contour.py b/plotly/figure_factory/_ternary_contour.py index f1bb53ddf5..6a8079c922 100644 --- a/plotly/figure_factory/_ternary_contour.py +++ b/plotly/figure_factory/_ternary_contour.py @@ -31,9 +31,9 @@ def _transform_barycentric_cartesian(): def _contour_trace(x, y, z, tooltip, ncontours=None, colorscale='Viridis', - reversescale=False, showscale=False, linewidth=0.5, - linecolor='rgb(150,150,150)', smoothing=False, - coloring=None, showlabels=False, fontcolor='blue', + showscale=False, linewidth=0.5, + linecolor='rgb(150,150,150)', + coloring=None, fontcolor='blue', fontsize=12): """ Contour trace in Cartesian coordinates. @@ -51,23 +51,14 @@ def _contour_trace(x, y, z, tooltip, ncontours=None, colorscale='Viridis', Number of contours to display (determined automatically if None). colorscale : str o array, optional Colorscale to use for contours - reversescale : bool - Reverses the color mapping if true. If true, `zmin` - will correspond to the last color in the array and - `zmax` will correspond to the first color. showscale : bool If True, a colorbar showing the color scale is displayed. linewidth : int Line width of contours linecolor : color string Color on contours - smoothing : bool - If True, contours are smoothed. coloring : None or 'lines' How to display contour. Filled contours if None, lines if ``lines``. - showlabels : bool, default False - For line contours (coloring='lines'), the value of the contour is - displayed if showlabels is True. colorscale : None or array-like Colorscale of the contours. fontcolor : color str @@ -75,10 +66,6 @@ def _contour_trace(x, y, z, tooltip, ncontours=None, colorscale='Viridis', fontsize : int Font size of contour labels. """ - if showlabels and (coloring is not 'lines'): - msg = """`showlabels` was set to True, but labels can only be - displayed for `coloring='lines'`""" - warnings.warn(msg) c_dict = dict(type='contour', x=x, y=y, z=z, @@ -86,18 +73,12 @@ def _contour_trace(x, y, z, tooltip, ncontours=None, colorscale='Viridis', hoverinfo='text', ncontours=ncontours, colorscale=colorscale, - reversescale=reversescale, showscale=showscale, - line=dict(width=linewidth, color=linecolor, - smoothing=smoothing), + line=dict(width=linewidth, color=linecolor), colorbar=dict(thickness=20, ticklen=4) ) if coloring == 'lines': - contours = dict(coloring=coloring, - showlabels=showlabels) - if showlabels: - contours.update(labelfont=dict(size=fontsize, - color=fontcolor,)) + contours = dict(coloring=coloring) c_dict.update(contours=contours) return go.Contour(c_dict) @@ -451,12 +432,11 @@ def _compute_grid(coordinates, values, tooltip_mode): def create_ternary_contour(coordinates, values, pole_labels=['a', 'b', 'c'], tooltip_mode='proportions', width=500, height=500, ncontours=None, - showscale=False, coloring=None, - showlabels=False, colorscale=None, - reversescale=False, + showscale=False, + coloring=None, + colorscale=None, plot_bgcolor='rgb(240,240,240)', - title=None, - smoothing=False): + title=None): """ Ternary contour plot. @@ -484,21 +464,12 @@ def create_ternary_contour(coordinates, values, pole_labels=['a', 'b', 'c'], If True, a colorbar showing the color scale is displayed. coloring : None or 'lines' How to display contour. Filled contours if None, lines if ``lines``. - showlabels : bool, default False - For line contours (coloring='lines'), the value of the contour is - displayed if showlabels is True. colorscale : None or array-like colorscale of the contours. - reversescale : bool - Reverses the color mapping if true. If true, `zmin` - will correspond to the last color in the array and - `zmax` will correspond to the first color. plot_bgcolor : color of figure background title : str or None Title of ternary plot - smoothing : bool - If True, contours are smoothed. Examples ======== @@ -523,12 +494,6 @@ def create_ternary_contour(coordinates, values, pole_labels=['a', 'b', 'c'], Example 2: ternary contour plot with line contours fig = ff.create_ternary_contour(np.stack((a, b)), z, coloring='lines') - - Labels of contour plots can be displayed on the contours: - - fig = ff.create_ternary_contour(np.stack((a, b)), z, coloring='lines', - showlabels=True) - """ grid_z, gr_x, gr_y, tooltip = _compute_grid(coordinates, values, tooltip_mode) @@ -547,11 +512,8 @@ def create_ternary_contour(coordinates, values, pole_labels=['a', 'b', 'c'], contour_trace = _contour_trace(gr_x, gr_y, grid_z, tooltip, ncontours=ncontours, showscale=showscale, - showlabels=showlabels, - colorscale=colorscale, - reversescale=reversescale, - coloring=coloring, - smoothing=smoothing) + colorscale=colorscale, + coloring=coloring) side_trace, tick_trace = _styling_traces_ternary(x_ticks, y_ticks) fig = go.Figure(data=[contour_trace, tick_trace, side_trace], layout=layout) diff --git a/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py b/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py index c731015a46..b360fbc72e 100644 --- a/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py +++ b/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py @@ -2965,12 +2965,8 @@ def test_contour_attributes(self): c = 1 - a - b z = a * b * c contour_dict = {'ncontours': 10, - 'showscale': True, - 'reversescale': False} - + 'showscale': True} fig = ff.create_ternary_contour(np.stack((a, b, c)), z, **contour_dict) for key, value in contour_dict.items(): assert fig['data'][0][key] == value - -