Recreate image in Plotly using coordinates - python

I'm attempting to recreate this image using coordinates. So far I haven't had much luck. Specifically I'm running into trouble being able to see all the colors. I think what I need is a way to determine which rectangles are at the forefront vs background. Would I be better off using matplotlib? Any help on this would be greatly appreciated.
CURRENT CODE
frame = tools.csv_read(file=['attack_zones'])
x = frame.groupby('identifier')
x = x.agg(Xmin=('X', np.min), Xmax=('X', np.max)).reset_index()
y = frame.groupby('identifier')
y = y.agg(Ymin=('Y', np.min), Ymax=('Y', np.max)).reset_index()
x = x.merge(y,on='identifier',how='left')
x = x.sort_values('identifier',ascending=True)
fig = go.Figure()
#Create scatter trace of text labels
fig.add_trace(go.Scatter(
x=[-48, 52],
y=[113, 242],
text=["Rectangle reference to the plot",
"Rectangle reference to the axes"],
mode="text",
))
#Set axes properties
fig.update_xaxes(range=[-134, 134])
fig.update_yaxes(range=[0, 345])
#Set identifier colors
def colors(identifier):
if identifier < 10:
return 'purple'
if identifier < 20:
return 'pink'
if identifier < 30:
return 'yellow'
else:
return 'white'
for iden,xmin,xmax,ymin,ymax in zip(x['identifier'],x['Xmin'],x['Xmax'],x['Ymin'],x['Ymax']):
fig.add_shape(type="rect",
xref="x", yref="y",
x0=xmin, y0=ymin,
x1=xmax, y1=ymax,
fillcolor=colors(iden),
)
fig.show()

There appears to be repeatable portions in your desired image – specifically each "square" can be thought of as a 3x3 array with text labels everywhere except for the center. Therefore, I would recommend writing a function with parameters for the two corners of your square, the text annotations, and fill color of the square, then calling this function repeatedly.
import plotly.graph_objects as go
fig = go.Figure()
def draw_square_with_labels(top_left, bottom_right, text_labels, fill_color, fig=fig):
""" Adds a 3x3 square trace with labels ordered clockwise on a Plotly graph_object
Args:
top_left: starting corner of a rectangle as a tuple or list of form (x0,y0)
bottom_right: ending corner of a rectangle as a tuple or list of form (x0,y0)
text_labels: a list of text labels starting from the location of the top_left and moving clockwise
fill_color: fill color for the square
Returns:
fig with a 3x3 colored square trace with labels added
"""
x0,y0 = top_left
x2,y2 = bottom_right
x1,y1 = (x0+x2)/2, (y0+y2)/2
xy_coordinates = [
[x0,y0],[x1,y0],[x2,y0],
[x2,y1],[x2,y2],[x1,y2],
[x0,y2],[x0,y1],[x0,y0]
]
x = [c[0] for c in xy_coordinates]
y = [c[1] for c in xy_coordinates]
text_positions = [
"bottom right",
"bottom center",
"bottom left",
"middle left",
"top left",
"top center",
"top right",
"middle right",
]
fig.add_trace(go.Scatter(
x=x,
y=y,
mode='lines+text',
line_color="rgba(200,200,200,0.7)",
text=text_labels,
textposition=text_positions,
fill='toself',
fillcolor = fill_color,
))
side_lengths = [10,7,4,1]
text_labels_array = [
[31,32,33,36,39,38,37,34,""],
[21,22,23,26,29,28,27,24,""],
[11,12,13,16,19,18,17,14,""],
[1,2,3,6,9,8,7,4,""],
]
fill_colors = [
"rgba(255,255,255,0.5)",
"rgba(255,255,167,0.5)",
"rgba(255,182,193,0.5)",
"rgba(219,112,147,0.5)",
]
for side_length, text_labels, fill_color in zip(side_lengths,text_labels_array,fill_colors):
draw_square_with_labels(
top_left=[-side_length,side_length],
bottom_right=[side_length,-side_length],
text_labels=text_labels,
fill_color=fill_color
)
## add '5' at the center
fig.add_annotation(x=0, y=0, text="5", showarrow=False)
## add guidelines
edge_x, edge_y = 1,10
fig.add_shape(type="line",
x0=-edge_x, y0=-edge_y, x1=-edge_x, y1=edge_y,
line=dict(color="rgba(200,200,200,0.7)")
)
fig.add_shape(type="line",
x0=edge_x, y0=-edge_y, x1=edge_x, y1=edge_y,
line=dict(color="rgba(200,200,200,0.7)")
)
fig.add_shape(type="line",
x0=-edge_y, y0=edge_x, x1=edge_y, y1=edge_x,
line=dict(color="rgba(200,200,200,0.7)")
)
fig.add_shape(type="line",
x0=-edge_y, y0=-edge_x, x1=edge_y, y1=-edge_x,
line=dict(color="rgba(200,200,200,0.7)")
)
## add green dashed rectangle
fig.add_shape(type="rect",
x0=-3.5, y0=-3.5, x1=3.5, y1=3.5,
line=dict(color="rgba(46,204,113,0.7)", dash='dash')
)
fig.update_xaxes(visible=False, showticklabels=False)
fig.update_yaxes(visible=False, showticklabels=False)
fig.update_layout(template='plotly_white', showlegend=False)
fig.show()

Related

How do I update the color of a marker on click in a Plotly graph object inside a Dash app?

I have a code that creates a Plotly graph made into a grid such that every (x,y) square on the grid is filled with the marker. I want the color of the marker to change when the marker is clicked. I tried using the code here: https://plotly.com/python/click-events/ as an example but when I run my Dash app, the graph isn't clickable (except for the standard interactive tools found in Plotly).
The code for my graph is this.
def create_interactive_grid(n):
x = []
y = []
for i in range(n):
for j in range(n):
xvalue, yvalue = i, j
x.append(xvalue + 0.5)
y.append(yvalue + 0.5)
fig = go.Figure()
scatter = go.Scatter(x=x, y=y, marker=dict(size=30, symbol=1, color="blue"), line=dict(width=0),
mode='markers')
fig.add_trace(scatter)
fig.update_layout(hovermode="closest", clickmode="event", plot_bgcolor="white", width=1000, height=1000,
showlegend=False)
fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor="black", tickvals=[x for x in range(n)],
range=[0, n], scaleanchor="x", constrain="domain", showticklabels = False, ticks = "")
fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor="black", tickvals=[x for x in range(n)],
range=[0, n], constrain="domain", scaleanchor="y", showticklabels = False, ticks= "")
def update_cell(trace, points, selector):
# take in click
# change color of marker on visible graph
c = list(scatter.marker.color)
for point in points.point_inds:
c[point] = "black"
with fig.batch_update():
scatter.marker.color = c
scatter.on_click(update_cell)
return fig
Some other tutorials indicate that I need to use a callback for this, but I'm not sure how I go about implementing that in a multi-page app, since this function isn't in the same file as my app/run scripts or my pages.
this is js code you can transfert it for python :
updatePlotColor() {
let update = {'marker.color': this.color};
PlotlyModule.plotlyjs.restyle("plotDiv", update, 1);
}
where "1" is the number of trace ( if you have only one trace so change 1 to 0 .

Edge's different coloring in a plotly graph

I have a graph and I'd like to have two different colors for the edges. From the docs it can be seen that this is done using the color and colorscale keys from the marker dictionary.
This works as excepted when giving colors to nodes in the graph. It is not working when I am trying to do the same thing with the edges of the graph.
I have something like this
edge_trace = go.Scatter( x=edge_x, y=edge_y, mode = 'lines',
line=dict(width=1), hoverinfo='none',
marker=dict(
color=edge_color,
colorscale=edge_colorscale,
# cmin=0,
# cmax=1
)
)
The result is that the edges of the graph have some kind of bluish color that is not defined anywhere.
Any subtle differences when giving different colors to edges that I am not aware from?
Apparently one cannot do this the same way as it would be done with the node_trace. Separate edge_trace need to be created for every group of edges of different colors that one desires. If you'd like to have 2 groups of colors in your edges you could do sth like this:
edge_trace1 = go.Scatter( x=[], y=[], mode='lines',
line=dict(width=1), hoverinfo='none',
)
edge_trace2 = go.Scatter( x=[], y=[], mode='lines',
line=dict(width=1), hoverinfo='none',
)
for i, edge in enumerate(G.edges().data()):
x0, y0 = pos[edge[0]]
x1, y1 = pos[edge[1]]
# Arbitrary distinction between the group.
# Normally you need to have the indices of the groups of edges
if i < len(G.edges().data())/2:
edge_trace1['x'] += tuple([x0,x1,None])
edge_trace1['y'] += tuple([y0,y1,None])
else:
edge_trace2['x'] += tuple([x0,x1,None])
edge_trace2['y'] += tuple([y0,y1,None])
edge_trace1['marker'] = dict(
color='rgb(0,255,0)',
)
edge_trace2['marker'] = dict(
color='rgb(0,0,255)',
)
Then combine into one for the data in the Figure. Like so:
edge_trace = [edge_trace1, edge_trace2]
data = edge_trace +[node_trace]

How to change axis titles when using sliders in plotly

I'm plotting scatter3d projections of the 4d iris data set using plotly. To display all 4 possible projections in the same figure I am using sliders. However when "sliding" from one projection to the next the axis titles do not change. Normally I would use fig.update_layout() but that isn't working. How can I get these to change with the slider?
Projection 1
Projection 2
Here's the code for reference:
import numpy as np
import plotly.graph_objects as go
from matplotlib import cm
from itertools import combinations
def nd2scatter3d(X, labels = None, features = None, plot_axes = None, hovertext = None):
"""
Parameters
----------
X : array-like, shape = (n_samples, n_features).
labels : 1d int array, shape = (n_samples), optional, default None.
Target or clustering labels for each sample.
Defaults to np.ones(n_samples).
features : list, len = n_features, optional, default None.
List of feature names.
Defaults to numeric labeling.
plot_axes : list of 3-tuples, optional, default None.
List of axes to include in 3d projections. i.e. [(0,1,2), (0,1,3)] displays
projections along the 4th axis and 3rd axis in that order.
Defaults to all possible axes combinations.
hovertext : list, len = n_samples, optional, default None.
List of text to display on mouse hover.
Defaults to no text on hover.
"""
if labels is None:
labels = np.ones(X.shape[0]).astype(int)
if features is None:
features = np.arange(X.shape[1]).astype(str)
if plot_axes is None:
plot_axes = list(combinations(np.arange(X.shape[1]), 3))
if hovertext is None:
hoverinfo = 'none'
else:
hoverinfo = 'text'
fig = go.Figure()
for i in range(len(plot_axes)):
fig.add_trace(
go.Scatter3d(
visible=False,
x=X[:, plot_axes[i][0]],
y=X[:, plot_axes[i][1]],
z=X[:, plot_axes[i][2]],
mode='markers',
marker=dict(
size=3,
color = [list(cm.tab10.colors[c]) for c in labels],
opacity=1
),
hovertemplate=None,
hoverinfo= hoverinfo,
hovertext = hovertext,
),)
fig.data[0].visible = True
steps = []
for i in range(len(fig.data)):
step = dict(
method="update",
args=[{"visible": [False] * len(fig.data)},
{"title": features[plot_axes[i][0]] + ' vs. ' + features[plot_axes[i][1]] + ' vs. ' + features[plot_axes[i][2]]}, # layout attribute
],
label = str(plot_axes[i]),
)
step["args"][0]["visible"][i] = True # Toggle i'th trace to "visible"
steps.append(step)
sliders = [dict(
active=10,
currentvalue={"prefix": "Projection: "},
pad={"t": 10},
steps=steps,
)]
fig.update_layout(
sliders=sliders
)
fig.update_layout(width=900, height = 500, margin=dict(r=45, l=45, b=10, t=50),
showlegend=False)
fig.update_layout(scene_aspectmode='cube',
scene2_aspectmode='cube',
scene3_aspectmode='cube',
scene4_aspectmode='cube',
scene = dict(
xaxis_title = features[plot_axes[0][0]],
yaxis_title = features[plot_axes[0][1]],
zaxis_title = features[plot_axes[0][2]],),
scene2 = dict(
xaxis_title = features[plot_axes[1][0]],
yaxis_title = features[plot_axes[1][1]],
zaxis_title = features[plot_axes[1][2]],),
scene3 = dict(
xaxis_title = features[plot_axes[2][0]],
yaxis_title = features[plot_axes[2][1]],
zaxis_title = features[plot_axes[2][2]],),
scene4 = dict(
xaxis_title = features[plot_axes[3][0]],
yaxis_title = features[plot_axes[3][1]],
zaxis_title = features[plot_axes[3][2]],)
)
fig.show()
Solution thanks to jayveesea, as well as some minor changes:
def nd2scatter3d(X, labels = None, features = None, plot_axes = None, hovertext = None, size = 3):
"""
Parameters
----------
X : array-like, shape = (n_samples, n_features).
labels : 1d int array, shape = (n_samples), optional, default None.
Target or clustering labels for each sample.
Defaults to np.ones(n_samples).
features : list, len = n_features, optional, default None.
List of feature names.
Defaults to numeric labeling.
plot_axes : list of 3-tuples, optional, default None.
List of axes to include in 3d projections. i.e. [(0,1,2), (0,1,3)] displays
projections along the 4th axis and 3rd axis in that order.
Defaults to all possible axes combinations.
hovertext : list, len = n_samples, optional, default None.
List of text to display on mouse hover.
Defaults to no text on hover.
size : int, default 3.
Sets marker size.
"""
if labels is None:
# Label all datapoints zero.
labels = np.zeros(X.shape[0]).astype(int)
if features is None:
# numerical features if no names are passed.
features = np.arange(X.shape[1]).astype(str)
if plot_axes is None:
# plot all possible axes if none are passed.
plot_axes = list(combinations(np.arange(X.shape[1]), 3))
if hovertext is None:
hoverinfo = 'none'
else:
hoverinfo = 'text'
# Determine colormap from number of labels.
if len(np.unique(labels)) <= 10:
color = [list(cm.tab10.colors[c]) if c >= 0 else [0,0,0,1] for c in labels]
elif len(np.unique(labels)) <= 20:
color = [list(cm.tab20.colors[c]) if c >= 0 else [0,0,0,1] for c in labels]
else:
norm_labels = labels/max(labels)
color = [cm.viridis(c) if c >= 0 else [0,0,0,1] for c in norm_labels]
# Genterate 3d scatter plot slider.
fig = go.Figure()
for i in range(len(plot_axes)):
fig.add_trace(
# Scatter plot params.
go.Scatter3d(
visible=False,
x=X[:, plot_axes[i][0]],
y=X[:, plot_axes[i][1]],
z=X[:, plot_axes[i][2]],
mode='markers',
marker=dict(
size=size,
color = color,
opacity=1
),
hovertemplate=None,
hoverinfo= hoverinfo,
hovertext = hovertext,
),)
fig.data[0].visible = True
steps = []
# Slider update params.
for i in range(len(fig.data)):
step = dict(
method="update",
args=[{"visible": [False] * len(fig.data)},
{"title": features[plot_axes[i][0]] + ' vs. '
+ features[plot_axes[i][1]] + ' vs. ' + features[plot_axes[i][2]],
"scene.xaxis.title": features[plot_axes[i][0]],
"scene.yaxis.title": features[plot_axes[i][1]],
"scene.zaxis.title": features[plot_axes[i][2]],
},
],
label = str(plot_axes[i]),
)
step["args"][0]["visible"][i] = True # Toggle i'th trace to "visible".
steps.append(step)
sliders = [dict(
active=10,
currentvalue={"prefix": "Projection: (x, y, z) = "},
pad={"t": 10},
steps=steps,
)]
fig.update_layout(sliders=sliders)
fig.update_layout(width=900, height = 500, margin=dict(r=45, l=45, b=10, t=50))
fig.update_layout(scene_aspectmode='cube')
fig.show()
To update the axis titles you need to include the axis names with your slider entry. It may help to reference plotly's js document on update.
So instead of this chunk:
for i in range(len(fig.data)):
step = dict(
method="update",
args=[{"visible": [False] * len(fig.data)},
{"title": features[plot_axes[i][0]] + ' vs. '
+ features[plot_axes[i][1]] + ' vs. ' + features[plot_axes[i][2]]},
],
label = str(plot_axes[i]),
)
Use something like:
for i in range(len(fig.data)):
step = dict(
method="update",
args=[{"visible": [False] * len(fig.data)},
{"title": features[plot_axes[i][0]] + ' vs. '
+ features[plot_axes[i][1]] + ' vs. ' + features[plot_axes[i][2]],
"scene.xaxis.title": features[plot_axes[i][0]],
"scene.yaxis.title": features[plot_axes[i][1]],
"scene.zaxis.title": features[plot_axes[i][2]],
},
],
label = str(plot_axes[i]),
)
This creates an entry that will update the data and title and the axes titles when the slider changes.

add label to legend for matplotlib annotation

I am "annotating" many arrows of a certain color to add data to the graph (where events occurred). (example code). Is there a way to add it to the legend? One answer might be to add them manually as I show in the code below, but I guess it is always the last resort. What is the "right" way to do it? (bonus for also having a small arrow mark in the legend)
Here is an example, but really, the example is for ease of use, the question is just how to add label for line.axes.annotate
Here is a code which is almost identical to the one in the link:
A function to add arrows to
def add_arrow(line, position=None, direction='right', size=15, color=None, length=None):
"""
add an arrow to a line.
line: Line2D object
position: x-position of the arrow. If None, mean of xdata is taken
direction: 'left' or 'right'
size: size of the arrow in fontsize points
color: if None, line color is taken.
length: the number of points in the graph the arrow will consider, leave None for automatic choice
"""
if color is None:
color = line.get_color()
xdata = line.get_xdata()
ydata = line.get_ydata()
if not length:
length = max(1, len(xdata) // 1500)
if position is None:
position = xdata.mean()
# find closest index
start_ind = np.argmin(np.absolute(xdata - position))
if direction == 'right':
end_ind = start_ind + length
else:
end_ind = start_ind - length
if end_ind == len(xdata):
print("skipped arrow, arrow should appear after the line")
else:
line.axes.annotate('',
xytext=(xdata[start_ind], ydata[start_ind]),
xy=(xdata[end_ind], ydata[end_ind]),
arrowprops=dict(
arrowstyle="Fancy,head_width=" + str(size / 150), color=color),
size=size
)
A function that uses it
def add_arrows(line, xs, direction='right', size=15, color=None, name=None):
if name:
if color is None:
color = line.get_color()
patch = mpatches.Patch(color=color, label=name, marker="->")
plt.legend(handles=[patch])
for x in xs:
add_arrow(line, x, color=color)
An example to what line is
x,y = [i for i in range(10000)], [i for i in range(10000)]
line = plt.plot(x, y, label="class days")[0]
add_arrows(line, (x,y))
plt.show()

Subplots with two y axes (each) - plotly and python/pandas

Are there guidelines on how to set up secondary Y-axes in python for plotly?
I am assinging axis style through an iterative loop, as follows:
all_plots = ['plot1','plot2'...'plot20']
fig = tools.make_subplots(rows=nrow, cols=ncol, shared_xaxes=False, shared_yaxes=False, subplot_titles=all_plots)
for i in all_plots:
fig['layout']['yaxis'+str(j)].update()
How does the assignment of y axes work?
If my subplot included, say, 4 rows and 5 columns for a total of 20 subplots, do I have to assume that plotly needs to receive odd and even numbers, meaning:
yaxis1 and yaxis2 for plot1
....
yaxis39 and yaxis40 for plot20
It is possible, to do this, but its not particularly intuitive. Take this example where I create a plot 2x2 subplots, and add a secondary y axis to the plot in position 2,2.
When you create a subplots, they are assigned y axes: "y1","y2","y3","y4" on the left side of each subplot. To a secondary y axes, you need to use fig['layout'].updateto create new axes "y5", "y6", "y7", "y8" which correspond to "y1","y2","y3","y4". So the bottom right subplot would have axes y4(right) and y8(left). In the example below, I only create a secondary y axis for the last plot, but expanding it to more/all the subplots is pretty straightforward.
It is important to note, that creating the secondary axes, and assigning it in trace5 doesn't automatically place it on the proper axes. You still have to manually assign it with fig['data'][4].update(yaxis='y'+str(8)) to plot it relative to the left axis.
fig = tools.make_subplots(rows=2, cols=2,subplot_titles=('Air Temperature', 'Photon Flux Density',
'Ground Temps','Water Table & Precip'))
fig['layout']['xaxis1'].update( range=[174, 256])
fig['layout']['xaxis3'].update(title='Day of Year', range=[174, 256])
fig['layout']['yaxis1'].update(title='Degrees C',range=[-5,30])
fig['layout']['yaxis2'].update(title='mmol m<sup>-2</sup> m<sup>-d</sup>', range=[0, 35])
fig['layout']['yaxis3'].update(title='Ground Temps', range=[0, 11])
fig['layout']['yaxis4'].update(title='depth cm', range=[-20, 0])
fig['layout']['yaxis8'].update(title='rainfall cm', range=[0, 1.6])
fig['layout'].update(showlegend=False, title='Climate Conditions')
# In this example, I am only doing it for the last subplot, but if you wanted to do if for all,
# Just change to range(1,5)
for k in range(4,5):
fig['layout'].update({'yaxis{}'.format(k+4): dict(anchor='x'+str(k),
overlaying='y'+str(k),
side='right',
)
})
trace1 = go.Scatter(
y=Daily['AirTC_Avg'],
x=Daily.index,
marker = dict(
size = 10,
color = 'rgba(160, 0, 0, .8)',),
error_y=dict(
type='data',
array=Daily_Max['AirTC_Avg']-Daily_Min['AirTC_Avg'],
visible=True,
color = 'rgba(100, 0, 0, .5)',
),
name = 'Air Temp'
)
trace2 = go.Bar(
y=Daily['PPFD']/1000,
x=Daily.index,
name='Photon Flux',
marker=dict(
color='rgb(180, 180, 0)'
),
yaxis='y2',
)
trace3 = go.Scatter(
y=Daily['Temp_2_5_1'],
x=Daily.index,
name='Soil Temp',
marker=dict(
color='rgb(180, 0, 0)'
),
yaxis='y3',
)
trace4 = go.Scatter(
y=Daily['Table_1']*100,
x=Daily.index,
name='Water Table',
marker=dict(
color='rgb(0, 0, 180)'
),
yaxis='y4',
)
trace5 = go.Bar(
y=Daily['Rain']/10,
x=Daily.index,
name='Rain',
marker=dict(
color='rgb(0, 100, 180)'
),
yaxis='y8',
)
fig.append_trace(trace1, 1, 1)
fig.append_trace(trace2, 1, 2)
fig.append_trace(trace3, 2, 1)
fig.append_trace(trace4, 2, 2)
fig.append_trace(trace5, 2, 2)
## This part is important!!! you have to manually assing the data to the axis even
# though you do it when defining trace 5
fig['data'][4].update(yaxis='y'+str(8))
plot(fig, filename='FI_Climate')
Not an exact answer but I thought it might help...
I like to use pandas and cufflinks. Here is an example of how to plot two sets of data from one dataframe (df) on a graph using a secondary y axis. The data from each axis is displayed in different formats in this example (scatter and bar). The data is arranged into columns beforehand.
import pandas as pd
import cufflinks as cf
from plotly.offline import download_plotlyjs, init_notebook_mode,plot,iplot
fig1 = df.iplot(kind='scatter', mode='lines+markers', x=['col1', 'col2'],
y=['col3', 'col4',],
asFigure=True)
fig2 = df.iplot(kind='bar', x=['col1', 'col2'],
y=['col3', 'col4', ],
secondary_y=['col5','col6'],asFigure=True)
fig2['data'].extend(fig1['data'])
The naming convention is y, y2, y3... y40, and you make the reference to the axis in the trace dict.
So your traces should be like...
trace0 = dict(
x = xvals,
y = yvals,
yaxis = 'y'
)
trace1 = dict(
x = x2vals,
y = y2vals,
yaxis = 'y2'
)
....
trace40 = dict(
x = x40vals,
y = y40vals,
yaxis = 'y40'
)

Categories

Resources