Related
I am creating a 3D scatter plot based off a pandas dataframe, and then I want to re-draw it with slightly updated data whenever the user presses a button in my program. I almost have this functionality working, except the updated figure is drawn via a new opened tab, when really I just want my origin existing figure to be updated.
Here is my code. First I initialize the plot with 'version 1' of the data, then I set up a simple while loop to wait for the user to request an update. Then ideally once they enter input to ask for the update, I just re-draw everything in the same tab that is open. But instead a new tab is opened (which redraws the data correctly at least).
fig = go.Figure(data=[go.Scatter3d(x=df['x'],y=df['y'],z=df['z'],mode='markers', marker=dict(
size=4,
color=df['y'], # set color to an array/list of desired values
colorscale='Viridis', # choose a colorscale
opacity=0.3
))])
# Column max and mins for plotting:
xmax = df_1.max(axis=0)['x']; xmin = df_1.min(axis=0)['x']
ymax = df_1.max(axis=0)['y']; ymin = df_1.min(axis=0)['y']
zmax = df_1.max(axis=0)['z']; zmin = df_1.min(axis=0)['z']
fig.update_layout(
scene = dict(xaxis = dict(nticks=4, range=[xmin,xmax],),
yaxis = dict(nticks=4, range=[ymin,ymax],),
zaxis = dict(nticks=4, range=[zmin,zmax],),))
f2 = go.FigureWidget(fig)
f2.show()
#fig.show()
while True:
choice = input("> ")
choice = choice.lower() #Convert input to "lowercase"
if choice == 'exit':
print("Good bye.")
break
if choice == 'w':
print("W, moving forward")
cube_origin = cube_origin + np.array([0.1,0,0])
df_cube = createCubeMesh(cube_size, cube_density, cube_origin)
new_df = df_scene_orig.copy()
new_df = new_df.append(df_cube)
fig = go.Figure(data=[go.Scatter3d(x=new_df['x'],y=new_df['y'],z=new_df['z'],mode='markers', marker=dict(
size=4,
color=new_df['y'], # set color to an array/list of desired values
colorscale='Viridis', # choose a colorscale
opacity=0.3
))])
f2 = go.FigureWidget(fig)
f2.show()
I based my code on another answer that said to use go.FigureWidget(fig), but it doesn't seem to work as intended.
Edit
Instead of me using f2.show() at the end, I just want a simple thing analogous to f2.update() that redraws.
This is the case you want.
Everywhere in this page that you see fig.show(), you can display the same figure in a Dash application by passing it to the figure argument of the Graph component from the built-in dash_core_components package like this:
import plotly.graph_objects as go
fig = go.Figure(
data=[go.Scatter(
mode="markers+text",
x=[10, 20],
y=[20, 25],
text=["Point A", "Point B"]
)],
layout=dict(height=400, width=400, template="none")
)
import dash
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.layout = html.Div([
dcc.Graph(figure=fig)
])
app.run_server(debug=True, use_reloader=False)
reference: https://plotly.com/python/figure-introspection/
Help you write a code that is closest to your needs:
import plotly as py
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
import plotly.graph_objects as go
from jupyter_dash import JupyterDash
import pandas as pd
import numpy as np
py.offline.init_notebook_mode(connected=True)
app = JupyterDash('SimpleExample')
app.layout = html.Div([
dcc.Dropdown(id='dropdown', options=[
{'label': 'W', 'value': 'W'},
{'label': 'exit', 'value': 'exit'}],
value='exit'),
dcc.Graph(id='graph-court')
])
def random_data():
# sample dataframe of a wide format
np.random.seed(4)
cols = list('xyz')
X = np.random.randint(50, size=(3, len(cols)))
df = pd.DataFrame(X, columns=cols)
df.iloc[0] = 0
return df
df = random_data()
def create_figure(df):
fig = go.Figure(data=[go.Scatter3d(x=df['x'], y=df['y'], z=df['z'], mode='markers', marker=dict(
size=10,
color=df['y'],
colorscale='Viridis',
opacity=0.3
))])
# Column max and mins for plotting:
xmax = df.max(axis=0)['x']
xmin = df.min(axis=0)['x']
ymax = df.max(axis=0)['y']
ymin = df.min(axis=0)['y']
zmax = df.max(axis=0)['z']
zmin = df.min(axis=0)['z']
fig.update_layout(
scene=dict(xaxis=dict(nticks=4, range=[xmin, xmax], ),
yaxis=dict(nticks=4, range=[ymin, ymax], ),
zaxis=dict(nticks=4, range=[zmin, zmax], ), ))
fig = go.FigureWidget(fig)
return fig
#app.callback(Output('graph-court', 'figure'),
[Input('dropdown', 'value')])
def update_figure(selected_value):
selected_value = selected_value.lower() # Convert input to "lowercase"
if selected_value == 'exit':
print("Good bye.")
new_x, new_y, new_z = [], [], []
else:
print("W, moving forward")
# new data
new_x, new_y, new_z = np.random.randint(10, size=(3, 1))
# ploy
fig = create_figure(df) # Set as global variable or local variable as required
fig.add_trace(go.Scatter3d(x=new_x, y=new_y, z=new_z, marker=dict(size=10, color='green'), mode='markers'))
return fig
app.run_server(debug=False, use_reloader=False)
Estimated that your "tab" is referring to "browser tab" it is basically not possible with the standard renderer.
With the renderer browser it serves a one-shot server on a random port, which is shutting down immediately after the rendering is done. You can check that by reloading the graph in browser.
You can:
generate a static image and serve that yourself in a webapp (e.g. with flask) with f2.write_image("test.svg")
generate a dynamic html content by f2.show(renderer = "iframe") and serve that with e.g. flask
simply use plotly dash, look here for impressions
Try using Plotly for plotting, it has a functionality (Visibility), using that you can update your plot on button click or drop down.
The below example is for dropdown.
import pandas as pd
import numpy as np
import plotly.offline as py_offline
import plotly.graph_objs as go
from plotly import tools
py_offline.init_notebook_mode()
trace = go.Scatter(
x=[1, 2, 3],
y=[4, 5, 6]
)
fig = tools.make_subplots(rows=10, cols=1)
for k in range(10):
fig.append_trace(trace, k+1, 1)
updatemenus=list([
dict(
buttons=[],
direction = 'down',
pad = {'r': 10, 't': 10},
showactive = True,
x = 0,
xanchor = 'left',
y = 1.2,
yanchor = 'top'
),
])
lister = []
for k in range(11):
lister.append(dict(
args=['visible', [True for k in range(10)] if k == 0 else [True if (i+1) == k else False for i in range(10)]],
label='Show Trace ' + str( 'All' if k == 0 else k),
method='restyle'
))
updatemenus[0]['buttons'] = lister
fig['layout']['updatemenus'] = updatemenus
fig['layout'].update(title='subplots')
py_offline.iplot(fig, filename='simple-subplot')
So I haven't found any clear solutions in the r documentation for the Sankey diagram, and hoping someone could help me! All I want to do is make the links the same color as the source node, and have the link darken when hovering above it. Here's my Sankey Diagram as it exists at the moment, unfortunately, I can't share the data as there are some confidentiality issues. At the bottom you'll find a link for the image of the plot that I have.
dt <- setDT(copy(dt_minors2016))
nodes <- dt[,unique(c(citizen,geo))]
sources <- match(dt[,citizen],nodes)-1
targets <- match(dt[,geo], nodes) -1
values <- dt[,V1]
fig <- plot_ly(
type = "sankey",
#default= 1000,
domain = list(
x = c(0,1),
y = c(0,1)
),
orientation = "h",
valueformat = ".0f",
valuesuffix = "Persons",
node = list(
label = nodes,
# color = colors,
pad = 15,
thickness = 15,
line = list(
color = "black",
width = 0.5
)
),
link = list(
source = sources,
target = targets,
value = values,
color = 'rgba(0,255,255,0.4)'
)
)
fig <- fig %>% layout(
title = "UAM asylum seekers from top 5 origin countries to EU countries - 2016",
font = list(
size = 10
),
xaxis = list(showgrid = F, zeroline = F),
yaxis = list(showgrid = F, zeroline = F),
hovermode = "x unified"
)
fig
https://i.stack.imgur.com/oAMh8.png
as per comments, solution provided in python not R
core to solution is setting color on both nodes and links using the imdex associated with name to select a color from a predefined color list
import pandas as pd
import numpy as np
import plotly.graph_objects as go
import plotly.express as px
import itertools
df = pd.DataFrame(
itertools.product(
["AF", "SY", "IQ", "SO", "ER"], ["DE", "AT", "BG", "SE", "UK", "CH"]
),
columns=["source", "target"],
).pipe(lambda d: d.assign(value=np.random.uniform(1, 10000, 1000)[:len(d)]))
nodes = np.unique(df[["source", "target"]], axis=None)
nodes = pd.Series(index=nodes, data=range(len(nodes)))
fig = go.Figure(
go.Sankey(
node={
"label": nodes.index,
"color": [
px.colors.qualitative.Plotly[i % len(px.colors.qualitative.Plotly)]
for i in nodes
],
},
link={
"source": nodes.loc[df["source"]],
"target": nodes.loc[df["target"]],
"value": df["value"],
"color": [
px.colors.qualitative.Plotly[i % len(px.colors.qualitative.Plotly)]
for i in nodes.loc[df["source"]]
],
},
)
)
fig
I want to merge two stacked bar plot in plotly.express.
The code of the first figure is:
import pandas as pd
import plotly.express as px
from plotly.subplots import make_subplots
rscu1 = pd.read_csv("JQ038231.1_RSCU_stack.csv")
rscu2 = pd.read_csv("MG970255.1_RSCU_stack.csv")
rscu = pd.concat([rscu1, rscu2], keys=["JQ038231", "MG970255"])
rscu["species"] = rscu.index.get_level_values(0)
rscu = rscu.astype({"Fill": "category"})
rscu = rscu.astype({"aaRatio": "object"})
fig = px.bar(rscu, x="AA", y="RSCU", color="Fill", barmode = 'stack', text="aaRatio",
hover_data=['AA', "RSCU"], facet_row="species",
color_discrete_map={1: "#6598c9", 2: "#cb4a28", 3: "#9ac664", 4: "#7f5499"})
fig.update_xaxes(tickangle=0, title=None, ticks="outside")
fig.update_layout(
autosize=False,
width=950,
height=450,
showlegend=False)
for data in fig.data:
data["width"] = 0.9
fig.update_traces(textposition='outside')
fig.update_layout(uniformtext_minsize=8, uniformtext_mode='show')
These codes generate this figure:
The code of the second figure is:
fig_bottom = px.bar(rscu1, x="AA", y="Equality", color="Fill", barmode = 'stack', text="AA",
hover_data=['AA'], height=220, width=950,
color_discrete_map={1: "#6598c9", 2: "#cb4a28", 3: "#9ac664", 4: "#7f5499"})
fig_bottom.update_traces(textposition='inside', textfont_size=14)
fig_bottom.update_layout(uniformtext_minsize=9, uniformtext_mode='show', showlegend=False,)
fig_bottom.update_layout({"plot_bgcolor": "rgba(0, 0, 0, 0)",
"paper_bgcolor": "rgba(0, 0, 0, 0)"})
fig_bottom.update_yaxes(title=None, showticklabels=False)
fig_bottom.update_xaxes(title=None, showticklabels=False)
for data in fig_bottom.data:
data["width"] = 0.9
These codes generate this figure:
Is there a way to merge them into one figure, the final figure (made by ggplot) is:
The data used here can be found in https://github.com/dongzhang0725/sample_data
Updated
As per previous answer you can make_subplots(), add_trace() and finally update_layout()
source from GitHub
import requests
import io
rscu1 = pd.read_csv(io.StringIO(requests.get("https://raw.githubusercontent.com/dongzhang0725/sample_data/main/JQ038231.1_RSCU_stack.csv").text))
rscu2 = pd.read_csv(io.StringIO(requests.get("https://raw.githubusercontent.com/dongzhang0725/sample_data/main/MG970255.1_RSCU_stack.csv").text))
solution
# want "bottom" figure to use it's own axis to add to subplots
fig_bottom.data[0]["xaxis"]="x3"
fig_bottom.data[0]["yaxis"]="y3"
# subplots figure...
figall = make_subplots(rows=3, row_heights=[.4,.4,.2])
# add all the traces to appropriate subplot
for f in fig.data:
if f["yaxis"] == "y":
figall.add_trace(f, row=1, col=1)
else:
figall.add_trace(f, row=2, col=1)
figall.add_trace(fig_bottom.data[0], row=3, col=1)
# copy / modify layout of subplots figure
figall.update_layout(fig.layout)
fh = .38 # height of first two charts
figall.update_layout(
yaxis={"domain":[(1-2*fh)-.03,(1-fh)-.03]},
yaxis2={"domain":[1-fh,1]},
xaxis3={"title": None, "showticklabels": False},
yaxis3={"title": None, "showticklabels": False},
height=fig.layout["height"] + fig_bottom.layout["height"],
)
figall.update_traces(marker_coloraxis=None)
# recenter annotations....
for i, a in enumerate(figall.layout["annotations"]):
a["y"] = (1-i*(fh+.03))-fh/2
figall
Thanks to Rob, according to his answer, I resolved my problem with the following codes:
import pandas as pd
import plotly.express as px
from plotly.subplots import make_subplots
# parameters
files = ["JQ038231.1_RSCU_stack.csv", "MG970255.1_RSCU_stack.csv"]
fig_n = len(files)
space_figs = 0.07
y_offset = 0.8
text_size = 12
width_ = 900
height_ = 700
bottom2fig_ratio = 0.4
row_heights_ = [1/(fig_n+bottom2fig_ratio)]*fig_n + [1/(fig_n+bottom2fig_ratio)*bottom2fig_ratio] # [1/2.5]*2 + [1/2.5*0.5] = [0.4, 0.4, 0.2]
# subplots figure...
figall = make_subplots(rows=fig_n+1, row_heights=row_heights_, vertical_spacing=space_figs, subplot_titles=files)
for num, file in enumerate(files):
row_ = num + 1
rscu = pd.read_csv(file)
rscu = rscu.astype({"Fill": "category"})
rscu = rscu.astype({"aaRatio": "object"})
max_rscu = rscu.groupby(by=["AA"]).sum().max()["RSCU"]
fig = px.bar(rscu, x="AA", y="RSCU", color="Fill", barmode = 'stack', text="aaRatio",
hover_data=['AA', "RSCU"], color_discrete_map={1: "#6598c9", 2: "#cb4a28", 3: "#9ac664", 4: "#7f5499"})
fig.update_traces(textposition='outside') # show text to outside
# set xaxis style
if row_ != fig_n:
fig.update_xaxes(showline=True, linewidth=1, linecolor="black", ticks="outside",
tickangle=0, title=None, showticklabels=False)
else:
fig.update_xaxes(showline=True, linewidth=1, linecolor="black", ticks="outside",
title=None)
# set y range to show annotation text
fig.update_yaxes(showline=True, linewidth=1, linecolor="black", ticks="outside", range=[0, max_rscu + y_offset])
# add all the traces to appropriate subplot
for f in fig.data:
figall.add_trace(f, row=row_, col=1)
# to make fig's layout works in figall
fig.for_each_trace(lambda trace_: trace_.update(xaxis=f"x{row_}", yaxis=f"y{row_}"))
fig.layout[f"xaxis{row_}"] = fig.layout.pop("xaxis")
fig.layout[f"yaxis{row_}"] = fig.layout.pop("yaxis")
fig.layout[f"xaxis{row_}"]["anchor"] = f"y{row_}"
fig.layout[f"yaxis{row_}"]["anchor"] = f"x{row_}"
fig.layout[f"yaxis{row_}"].pop("domain") # otherwise it will affect figall's domain
figall.update_layout(fig.layout)
fig_bottom = px.bar(rscu, x="AA", y="Equality", color="Fill", barmode = 'stack', text="Codon",
hover_data=['AA'], color_discrete_map={1: "#6598c9", 2: "#cb4a28", 3: "#9ac664", 4: "#7f5499"})
fig_bottom.update_traces(textposition='inside') # show text to inside
bottom_row = fig_n+1
fig_bottom.for_each_trace(lambda trace_: trace_.update(xaxis=f"x{bottom_row}", yaxis=f"y{bottom_row}"))
# add all the traces of bottom figure to appropriate subplot
for f in fig_bottom.data:
figall.add_trace(f, row=bottom_row, col=1)
dict_layout = {"barmode": "stack",
"autosize": False,
"showlegend": False,
"plot_bgcolor": "rgba(0, 0, 0, 0)",
"paper_bgcolor": "rgba(0, 0, 0, 0)",
"uniformtext_minsize": text_size,
"uniformtext_mode": "show",
"width": width_,
"height": height_}
# for bottom figure
dict_layout[f"yaxis{fig_n+1}"] = {"title": None, "showticklabels": False}
dict_layout[f"xaxis{fig_n+1}"] = {"title": None, "showticklabels": False}
figall.update_layout(dict_layout)
figall.for_each_annotation(lambda x: x.update(x=0.12, font={"size": 13, "family": "Arial", "color": "black"})) # adjust title of each sub-figure
figall.for_each_trace(lambda trace_: trace_.update(width=0.9)) # set bar width
figall.show()
I have written the following code to heat heatmap of US-States. But I am unable to get the output image in Google Colab.
State codes are two alphabet codes for a particular state of the US.
temp = pd.DataFrame(project_data.groupby("school_state")["project_is_approved"].apply(np.mean)).reset_index()
temp.columns = ['state_code', 'num_proposals']
scl = [[0.0, 'rgb(242,240,247)'],[0.2, 'rgb(218,218,235)'],[0.4, 'rgb(188,189,220)'],\
[0.6, 'rgb(158,154,200)'],[0.8, 'rgb(117,107,177)'],[1.0, 'rgb(84,39,143)']]
data = [ dict(
type='choropleth',
colorscale = scl,
autocolorscale = False,
locations = temp['state_code'],
z = temp['num_proposals'].astype(float),
locationmode = 'USA-states',
text = temp['state_code'],
marker = dict(line = dict (color = 'rgb(255,255,255)',width = 2)),
colorbar = dict(title = "% of pro")
) ]
layout = dict(
title = 'Project Proposals % of Acceptance Rate by US States',
geo = dict(
scope='usa',
projection=dict( type='albers usa' ),
showlakes = True,
lakecolor = 'rgb(255, 255, 255)',
),
)
fig = dict(data=data, layout=layout)
offline.iplot(fig, filename='us-map-heat-map')
I have imported following libraries:
from chart_studio import plotly
import plotly.offline as offline
import plotly.graph_objs as go
offline.init_notebook_mode()
from collections import Counter
import chart_studio.plotly as py
Try the following code with your data:
(I tried putting your variables in the correct spots)
choropleth = go.Choropleth(
locations=temp['state_code'],
locationmode='USA-states',
z = temp['num_proposals'].astype(float),
zmin = 0,
zmax = max(temp['num_proposals'].astype(float)),
colorscale=scl,
autocolorscale=False,
text='Proposals',
marker_line_color='white',
colorbar_title="% Acceptance Rate"
)
fig = go.Figure(data=choropleth)
fig.update_layout(
title_text='Project Proposals % of Acceptance Rate by US States',
geo = dict(
scope='usa',
projection=go.layout.geo.Projection(type = 'albers usa'),
showlakes=True,
lakecolor='rgb(255, 255, 255)'),
)
fig.show()
This code works by creating the Plotly Choropleth Graph Object with your data, then loading that object into a Plotly Figure Graph Object, then updating the layout (for proper titles and zooms), and finally displaying the figure.
I'm creating a sankey diagram using plotly and there is the built in method to use 'group' to combine nodes. However, when I use this the color of this node will be black and no label is showing. This is expected as the colors of the grouped nodes could vary. However, I don't see how I can set the color of the group. Same goes for the label.
Is there a way to define this?
example code:
import plotly.graph_objs as go
from plotly.offline import plot
value = [3,5,2,4,6]
source = [0,0,1,0,3]
target = [1,4,2,3,4]
color = ["blue","yellow","orange","orange","purple"]
label = ["A","B","C1","C2","D"]
data = dict(
type='sankey',
arrangement = 'freeform',
node = dict(
pad = 15,
thickness = 20,
line = dict(
color = "black",
width = 0.1
),
groups = [[2,3]],
label = label,
color = color,
),
link = dict(
source = source,
target = target,
value = value,
)
)
layout = dict(
title = "Sankey test",
font = dict(
size = 10
)
)
f = go.FigureWidget(data=[data], layout=layout)
plot(f)
Which renders:
Since I'm getting the following error with your snippet:
ValueError: Invalid property specified for object of type plotly.graph_objs.sankey.Node: 'groups'
And since I don't know what versions you are running of plotly, python (and Jupyter Notebook?), I would simply suggest that you restructure your source data and do the C1 and C2 grouping into simply C before you build your plot. And keep in mind that Links are assigned in the order they appear in dataset and that node colors are assigned in the order that the plot is built.
Plot:
Code:
# imports
import pandas as pd
import numpy as np
import plotly.graph_objs as go
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
# settings
init_notebook_mode(connected=True)
# Nodes & links
nodes = [['ID', 'Label', 'Color'],
[0,'A','blue'],
[1,'B','yellow'],
[2,'C','orange'],
[3,'D','purple'],
]
# links with your data
links = [['Source','Target','Value','Link Color'],
[0,1,3,'rgba(200, 205, 206, 0.6)'],
[0,2,5,'rgba(200, 205, 206, 0.6)'],
[0,3,5,'rgba(200, 205, 206, 0.6)'],
[1,2,6,'rgba(200, 205, 206, 0.6)'],
[2,3,6,'rgba(200, 205, 206, 0.6)'],
]
# Retrieve headers and build dataframes
nodes_headers = nodes.pop(0)
links_headers = links.pop(0)
df_nodes = pd.DataFrame(nodes, columns = nodes_headers)
df_links = pd.DataFrame(links, columns = links_headers)
# Sankey plot setup
data_trace = dict(
type='sankey',
domain = dict(
x = [0,1],
y = [0,1]
),
orientation = "h",
valueformat = ".0f",
node = dict(
pad = 10,
# thickness = 30,
line = dict(
color = "black",
width = 0
),
label = df_nodes['Label'].dropna(axis=0, how='any'),
color = df_nodes['Color']
),
link = dict(
source = df_links['Source'].dropna(axis=0, how='any'),
target = df_links['Target'].dropna(axis=0, how='any'),
value = df_links['Value'].dropna(axis=0, how='any'),
color = df_links['Link Color'].dropna(axis=0, how='any'),
)
)
layout = dict(
title = "Sankey Test",
height = 772,
font = dict(
size = 10),)
fig = dict(data=[data_trace], layout=layout)
iplot(fig, validate=False)
My system info:
The version of the notebook server is: 5.6.0
The server is running on this version of Python:
Python 3.7.0 (default, Jun 28 2018, 08:04:48) [MSC v.1912 64 bit (AMD64)]