Networkx: connecting nodes using ports - python-3.x

I have this network:
r1 = dict( name = 'R1', ports = dict(p1 = 'p1', p2 = 'p2') )
r2 = dict( name = 'R2', ports = dict(p1 = 'p1', p2 = 'p2') )
r3 = dict( name = 'R3', ports = dict(p1 = 'p1', p2 = 'p2') )
routers = [r1,r2,r3]
G = nx.Graph()
[G.add_node(r['name'], name=r['name']) for r in routers]
G.add_edges_from([('R1','R2'),('R2','R3')]
The previous produces the next topology.
As you can see, each of the nodes have their ports p1 and p2. I know how to create these edges or connections in the graph:
In [53]: G.edges()
Out[53]: EdgeView([('R1', 'R2'), ('R2', 'R3')])
However I'm mostly interested in using the ports of each node as point of connection. Meaning:
In [53]: G.edges()
Out[53]: EdgeView([('R1'.'p1', 'R2'.'p2'), ('R2'.'p1', 'R3'.'p2')])
How can I accomplish that? Or, in other words, how could I model that, in the sense of having nodes+ports where the anchor points are ultimately these ports?
thanks!

Generic model for any port connection
First you need to add the ports as an attribute to your nodes:
import networkx as nx
r1 = dict( name = 'R1', ports = dict(p1 = 'p1', p2 = 'p2') )
r2 = dict( name = 'R2', ports = dict(p1 = 'p1', p2 = 'p2') )
r3 = dict( name = 'R3', ports = dict(p1 = 'p1', p2 = 'p2') )
routers = [r1,r2,r3]
G = nx.Graph()
for r in routers:
# Add ports as attributes
G.add_node(r['name'], name=r['name'], ports=r['ports'])
So, now if I do the following:
G.nodes().get('R3', None)
I get the following:
{'name': 'R3', 'ports': {'p1': 'p1', 'p2': 'p2'}}
Then, you can basically add a wrapper function for creating edges in your graph. I have assumed that you can use any port from one node to any other port of another node :
def add_edge_port(G, node1, port1, node2, port2):
node_list = [node1, node2]
port_list = [port1, port2]
edge_ports = []
for idx in range(0, 2):
node_idx = node_list[idx]
port_idx = port_list[idx]
# Sanity check to see if the nodes and ports are present in Graph
if G.nodes().get(node_idx, None) is None:
print("Node : {} is not present in Graph".format(node_idx))
return
if G.nodes(data=True)[node_idx]['ports'].get(port_idx, None) is None:
print("Port ID :{} is incorrect for Node ID : {}!".
format(node_idx, port_idx))
return
edge_ports.append(node_idx + '.' + port_idx)
# Add the anchor points as edge attributes
G.add_edge(node1, node2, anchors=edge_ports)
Now add the edges like this:
add_edge_port(G, 'R1', 'p1', 'R2', 'p2')
print(G.edges(data=True))
# Output : EdgeDataView([('R1', 'R2', {'anchors': ['R1.p1', 'R2.p2']})])
To get the anchors list, simply use:
print(nx.get_edge_attributes(G, 'anchors'))
# Output: {('R1', 'R2'): ['R1.p1', 'R2.p2']}
Now if you are sure that port p1 will always connect to port p2
def add_edge_port_modified(G, node1, node2):
# No need to check the nodes in this case
edge_ports = [node1 + '.p1', node2 + '.p2']
G.add_edge(node1, node2, anchors=edge_ports)
Then call:
add_edge_port_modified(G, 'R2', 'R3')
and the edges will be
print(nx.get_edge_attributes(G, 'anchors'))
# Output: {('R2', 'R3'): ['R2.p1', 'R3.p2']}
References:
https://networkx.github.io/documentation/networkx-2.2/reference/generated/networkx.classes.function.get_edge_attributes.html

Related

PySpark GraphFrame and networkx for graphs with hierarchy

I need to create a graph like this which have two relationships, continent-country, country-city. I have 3 columns: city, country, continent, but not sure how to get it into this graph.
Below is an example of another graph with only two columns, country & city. metro_name is the city.
metro = spark.read.csv("metro.csv", header='true').withColumnRenamed("name","metro_name")
country = spark.read.csv("country.csv", header='true').withColumnRenamed("name","country_name")
continent = spark.read.csv("continent.csv", header='true').withColumnRenamed("name","continent_name")
metro_country = spark.read.csv("metro_country.csv", header='true')
country_continent = spark.read.csv("country_continent.csv", header='true')
mc_vertices = country.select(col("country_name").alias("id"))
mc_edges = country.join(metro_country, country.country_id == metro_country.country_id).join(metro, metro_country.metro_id == metro.metro_id).select(col("country_name").alias("src"),col("metro_name").alias("dst"))
mc = GraphFrame(mc_vertices, mc_edges)
# display graph
import networkx as nx
mc_gp = nx.from_pandas_edgelist(mc.edges.toPandas(),'src','dst')
nx.draw(mc_gp, with_labels = True, node_size = 12, font_size = 12, edge_color = "red")
I have tried:
# gets graph that has country, city, continent
mcc_vertices = mc_vertices
mcc_edges = mc_edges.join(cc_edges, mc_edges.src == cc_edges.src).select(mc_edges["src"],mc_edges["dst"],cc_edges["dst"].alias("continent_name"))
mcc = GraphFrame(mcc_vertices, mcc_edges)
# display the graph
mcc_gp = nx.from_pandas_edgelist(mcc.edges.toPandas(),'continent_name','src','dst')
nx.draw(mcc_gp, with_labels = True, node_size = 12, font_size = 12, edge_color = "red")
# gets graph that only has "North America"
northamerica_vertices = mcc_edges.filter(mcc_edges.continent_name == "North America").select(col("src").alias("id")).distinct()
northamerica_edges = mcc_edges.filter(mcc_edges.continent_name == "North America")
northamerica = GraphFrame(northamerica_vertices, northamerica_edges)
northamerica_gp = nx.from_pandas_edgelist(northamerica.edges.toPandas(),'src','dst')
nx.draw(northamerica_gp, with_labels = True, node_size = 40, font_size = 10, edge_color = "red")

Problem with adding smiles on photos with convolutional autoencoder

I have a dataset with images and another dataset as it's description:
There are a lot of pictures: people with and without sunglasses, smiles and other attributes. What I want to do is be able to add smiles to photos where people are not smiling.
I've started like this:
smile_ids = attrs['Smiling'].sort_values(ascending=False).iloc[100:125].index.values
smile_data = data[smile_ids]
no_smile_ids = attrs['Smiling'].sort_values(ascending=True).head(5).index.values
no_smile_data = data[no_smile_ids]
eyeglasses_ids = attrs['Eyeglasses'].sort_values(ascending=False).head(25).index.values
eyeglasses_data = data[eyeglasses_ids]
sunglasses_ids = attrs['Sunglasses'].sort_values(ascending=False).head(5).index.values
sunglasses_data = data[sunglasses_ids]
When I print them their are fine:
plot_gallery(smile_data, IMAGE_H, IMAGE_W, n_row=5, n_col=5, with_title=True, titles=smile_ids)
Plot gallery looks like this:
def plot_gallery(images, h, w, n_row=3, n_col=6, with_title=False, titles=[]):
plt.figure(figsize=(1.5 * n_col, 1.7 * n_row))
plt.subplots_adjust(bottom=0, left=.01, right=.99, top=.90, hspace=.35)
for i in range(n_row * n_col):
plt.subplot(n_row, n_col, i + 1)
try:
plt.imshow(images[i].reshape((h, w, 3)), cmap=plt.cm.gray, vmin=-1, vmax=1, interpolation='nearest')
if with_title:
plt.title(titles[i])
plt.xticks(())
plt.yticks(())
except:
pass
Then I do:
def to_latent(pic):
with torch.no_grad():
inputs = torch.FloatTensor(pic.reshape(-1, 45*45*3))
inputs = inputs.to('cpu')
autoencoder.eval()
output = autoencoder.encode(inputs)
return output
def from_latent(vec):
with torch.no_grad():
inputs = vec.to('cpu')
autoencoder.eval()
output = autoencoder.decode(inputs)
return output
After that:
smile_latent = to_latent(smile_data).mean(axis=0)
no_smile_latent = to_latent(no_smile_data).mean(axis=0)
sunglasses_latent = to_latent(sunglasses_data).mean(axis=0)
smile_vec = smile_latent-no_smile_latent
sunglasses_vec = sunglasses_latent - smile_latent
And finally:
def add_smile(ids):
for id in ids:
pic = data[id:id+1]
latent_vec = to_latent(pic)
latent_vec[0] += smile_vec
pic_output = from_latent(latent_vec)
pic_output = pic_output.view(-1,45,45,3).cpu()
plot_gallery([pic,pic_output], IMAGE_H, IMAGE_W, n_row=1, n_col=2)
def add_sunglasses(ids):
for id in ids:
pic = data[id:id+1]
latent_vec = to_latent(pic)
latent_vec[0] += sunglasses_vec
pic_output = from_latent(latent_vec)
pic_output = pic_output.view(-1,45,45,3).cpu()
plot_gallery([pic,pic_output], IMAGE_H, IMAGE_W, n_row=1, n_col=2)
But when I execute this line I don't get any faces:
add_smile(no_smile_ids)
The output:
Could someone please explain where is my mistake or why it can happen? Thanks for any help.
Added: checking the shape of pic_output:
Wild guess, but it seems you are broadcasting your images instead of permuting the axes. The former will have the undesired effect of mixing information across the batches/channels.
pic_output = pic_output.view(-1, 45, 45, 3).cpu()
should be replaced with
pic_output = pic_output.permute(0, 2, 3, 1).cpu()
Assuming tensor pic_output is already shaped like (-1, 3, 45, 45).

Create Dictionary with uneven lens

This may sound a bit strange or silly but I am trying to create a dictionary or lists that can be referenced. Maybe if you look at the attached pic of my Excel would give you a better understanding.
I want the values of each row to be into a dictionary with say the key as 0 and different values as under Hostname, IP, GroupName and Port. the dictionary works with just the Hostname and IP as their length is same, but when i try to add the GroupName to the dict by using a lot of methods i found on Stackoverflow, it does not work as the length is not the same
enter image description here
Any help would be appreciated
Here is my code.
df = pd.read_excel("object.xlsx")
HostList =[]
IPList = []
for x in ExcelHostList:
for hostname in x:
if hostname not in HostList:
HostList.append(hostname)
for ips in ExcelIPList:
for ipadd in ips:
if ipadd not in IPList:
IPList.append(ipadd)
dict1 = dict(zip(HostList, IPList))
dict1
{'test1': '1.1.1.1', 'test2': '2.2.2.2', 'test3': '3.3.3.3', 'test4': '4.4.4.4', 'test5': '5.5.5.5', 'test6': '6.6.6.6'}
I have tried with making it a dict and then trying to combine them
ExcelHostList = (df["Hostname"].str.split("\n").to_dict())
ExcelIPList = (df["IP"].str.split("\n").to_dict())
ExcelGroupName = (df["GroupName"].to_dict())
dict2 = {z[0]: list(z[1:]) for z in zip(HostList, IPList, ExcelGroupName)}
dict2
{'test1': ['1.1.1.1', 'test-group-1'], 'test2': ['2.2.2.2', 'test-group-2'], 'test3': ['3.3.3.3', 'test-group-3']}
It's going to be very difficult to provide you with a good answer without more context about what exactly your end goal is for this data, but here are two ways you can consider structuring your data:
A list of dicts:
list_of_dicts = [
dict(
Hostname = ['test1', 'test2', 'test3',],
IP = ['1.1.1.1', '2.2.2.2', '3.3.3.3',],
GroupName = 'test-group-1',
Port = [443, 22, 808, 80, 161],
),
dict(
Hostname = ['test4',],
IP = ['4.4.4.4',],
GroupName = 'test-group-2',
Port = [443, 8080],
),
dict(
Hostname = ['test5', 'test6',],
IP = ['5.5.5.5', '6.6.6.6',],
GroupName = 'test-group-3',
Port = [443],
),
]
print(list_of_dicts)
print(list_of_dicts[0])
print(list_of_dicts[0]["Hostname"])
print(list_of_dicts[0]["Hostname"][0])
A dict of dicts using the GroupNames as keys:
dict_of_dicts = {
'test-group-1' : dict(
Hostname = ['test1', 'test2', 'test3',],
IP = ['1.1.1.1', '2.2.2.2', '3.3.3.3',],
Port = [443, 22, 808, 80, 161],
),
'test-group-2' : dict(
Hostname = ['test4',],
IP = ['4.4.4.4',],
Port = [443, 8080],
),
'test-group-3' : dict(
Hostname = ['test5', 'test6',],
IP = ['5.5.5.5', '6.6.6.6',],
Port = [443],
),
}
print(dict_of_dicts)
print(dict_of_dicts['test-group-1'])
print(dict_of_dicts['test-group-1']["Hostname"])
print(dict_of_dicts['test-group-1']["Hostname"][0])
examples in python tutor
Also if you already have this data in excel then I would highly recommend looking into using pandas to read this data into a DataFrame.

How can I use the plotly dropdown menu feature to update the z value in my choropleth map?

I just want to create a menu on the plot where I'm able to change the z-value in data only. I tried looking at other examples on here: https://plot.ly/python/dropdowns/#restyle-dropdown but it was hard since the examples were not exactly similar to my plot.
import plotly
import plotly.plotly as py
import plotly.graph_objs as go
import pandas as pd
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/2014_world_gdp_with_codes.csv')
data = [go.Choropleth(
locations = df['CODE'],
z = df['GDP (BILLIONS)'],
text = df['COUNTRY'],
colorscale = [
[0, "rgb(5, 10, 172)"],
[0.35, "rgb(40, 60, 190)"],
[0.5, "rgb(70, 100, 245)"],
[0.6, "rgb(90, 120, 245)"],
[0.7, "rgb(106, 137, 247)"],
[1, "rgb(220, 220, 220)"]
],
autocolorscale = False,
reversescale = True,
marker = go.choropleth.Marker(
line = go.choropleth.marker.Line(
color = 'rgb(180,180,180)',
width = 0.5
)),
colorbar = go.choropleth.ColorBar(
tickprefix = '$',
title = 'GDP<br>Billions US$'),
)]
layout = go.Layout(
title = go.layout.Title(
text = '2014 Global GDP'
),
geo = go.layout.Geo(
showframe = False,
showcoastlines = False,
projection = go.layout.geo.Projection(
type = 'equirectangular'
)
),
annotations = [go.layout.Annotation(
x = 0.55,
y = 0.1,
xref = 'paper',
yref = 'paper',
text = 'Source: <a href="https://www.cia.gov/library/publications/the-world-factbook/fields/2195.html">\
CIA World Factbook</a>',
showarrow = False
)]
)
fig = go.Figure(data = data, layout = layout)
py.iplot(fig, filename = 'd3-world-map')
It's been a while since this was asked, but I figured it was still worth answering. I can't speak to how this might have changed since it was asked in 2019, but this works today.
First, I'll provide the code I used to create the new z values and the dropdown menu, then I'll provide all of the code I used to create these graphs in one chunk (easier to cut and paste...and all that).
This is the data I used for the alternate data in the z field.
import plotly.graph_objects as go
import pandas as pd
import random
z2 = df['GDP (BILLIONS)'] * .667 + 12
random.seed(21)
random.shuffle(z2)
df['z2'] = z2 # example as another column in df
print(df.head()) # validate as expected
z3 = df['GDP (BILLIONS)'] * .2 + 1000
random.seed(231)
random.shuffle(z3) # example as a series outside of df
z4 = df['GDP (BILLIONS)']**(1/3) * df['GDP (BILLIONS)']**(1/2)
random.seed(23)
random.shuffle(z4)
z4 = z4.tolist() # example as a basic Python list
To add buttons to change z, you'll add updatemenus to your layout. Each dict() is a separate dropdown option. At a minimum, each button requires a method, a label, and args. These represent what is changing (method for data, layout, or both), what it's called in the dropdown (label), and the new information (the new z in this example).
args for changes to data (where the method is either restyle or update) can also include the trace the change applies to. So if you had a bar chart and a line graph together, you may have a button that only changes the bar graph.
Using the same structure you have:
updatemenus = [go.layout.Updatemenu(
x = 1, xanchor = 'right', y = 1.15, type = "dropdown",
pad = {'t': 5, 'r': 20, 'b': 5, 'l': 30}, # around all buttons (not indiv buttons)
buttons = list([
dict(
args = [{'z': [df['GDP (BILLIONS)']]}], # original data; nest data in []
label = 'Return to the Original z',
method = 'restyle' # restyle is for trace updates
),
dict(
args = [{'z': [df['z2']]}], # nest data in []
label = 'A different z',
method = 'restyle'
),
dict(
args = [{'z': [z3]}], # nest data in []
label = 'How about this z?',
method = 'restyle'
),
dict(
args = [{'z': [z4]}], # nest data in []
label = 'Last option for z',
method = 'restyle'
)])
)]
All code used to create this graph in one chunk (includes code shown above).
import plotly.graph_objs as go
import pandas as pd
import ssl
import random
# to collect data without an error
ssl._create_default_https_context = ssl._create_unverified_context
# data used in plot
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/2014_world_gdp_with_codes.csv')
# z values used in buttons
z2 = df['GDP (BILLIONS)'] * .667 + 12
random.seed(21)
random.shuffle(z2)
df['z2'] = z2 # example as another column in the data frame
print(df.head()) # validate as expected
z3 = df['GDP (BILLIONS)'] * .2 + 1000
random.seed(231)
random.shuffle(z3) # example as a series outside of the data frame
z4 = df['GDP (BILLIONS)']**(1/3) * df['GDP (BILLIONS)']**(1/2)
random.seed(23)
random.shuffle(z4)
z4 = z4.tolist() # example as a basic Python list
data = [go.Choropleth(
locations = df['CODE'], z = df['GDP (BILLIONS)'], text = df['COUNTRY'],
colorscale = [
[0, "rgb(5, 10, 172)"],
[0.35, "rgb(40, 60, 190)"],
[0.5, "rgb(70, 100, 245)"],
[0.6, "rgb(90, 120, 245)"],
[0.7, "rgb(106, 137, 247)"],
[1, "rgb(220, 220, 220)"]],
reversescale = True,
marker = go.choropleth.Marker(
line = go.choropleth.marker.Line(
color = 'rgb(180,180,180)', width = 0.5)),
colorbar = go.choropleth.ColorBar(
tickprefix = '$',
title = 'GDP<br>Billions US$',
len = .6) # I added this for aesthetics
)]
layout = go.Layout(
title = go.layout.Title(text = '2014 Global GDP'),
geo = go.layout.Geo(
showframe = False, showcoastlines = False,
projection = go.layout.geo.Projection(
type = 'equirectangular')
),
annotations = [go.layout.Annotation(
x = 0.55, y = 0.1, xref = 'paper', yref = 'paper',
text = 'Source: <a href="https://www.cia.gov/library/publications/the-world-factbook/fields/2195.html">\
CIA World Factbook</a>',
showarrow = False
)],
updatemenus = [go.layout.Updatemenu(
x = 1, xanchor = 'right', y = 1.15, type = "dropdown",
pad = {'t': 5, 'r': 20, 'b': 5, 'l': 30},
buttons = list([
dict(
args = [{'z': [df['GDP (BILLIONS)']]}], # original data; nest data in []
label = 'Return to the Original z',
method = 'restyle' # restyle is for trace updates only
),
dict(
args = [{'z': [df['z2']]}], # nest data in []
label = 'A different z',
method = 'restyle'
),
dict(
args = [{'z': [z3]}], # nest data in []
label = 'How about this z?',
method = 'restyle'
),
dict(
args = [{'z': [z4]}], # nest data in []
label = 'Last option for z',
method = 'restyle'
)])
)]
)
fig = go.Figure(data = data, layout = layout)
fig.show()

Bokeh – ColumnDataSource not updating whiskered-plot

I’m having issues with the following code (I’ve cut out large pieces but I can add them back in – these seemed like the important parts). In my main code, I set up a plot (“sectionizePlot”) which is a simple variation on another whiskered-plot
I’m looking to update them on the fly. In the same script, I’m using a heatmap (“ModifiedGenericHeatMap”) which updates fine.
Any ideas how I might update my whiskered-plot? Updating the ColumnDataSource doesn’t seem to work (which makes sense). I’m guessing that I am running into issues with adding each circle/point individually onto the plot.
One idea would be to clear the plot each time and manually add the points onto the plot, but it would need to be cleared each time, which I’m unsure of how to do.
Any help would be appreciated. I’m just a lowly Scientist trying to utilize Bokeh in Pharma research.
def ModifiedgenericHeatMap(source, maxPct):
colors = ["#75968f", "#a5bab7", "#c9d9d3", "#e2e2e2", "#dfccce", "#ddb7b1", "#cc7878", "#933b41", "#550b1d"]
#mapper = LinearColorMapper(palette=colors, low=0, high=data['count'].max())
mapper = LinearColorMapper(palette=colors, low=0, high=maxPct)
TOOLS = "hover,save,pan,box_zoom,reset,wheel_zoom"
globalDist = figure(title="derp",
x_range=cols, y_range=list(reversed(rows)),
x_axis_location="above", plot_width=1000, plot_height=400,
tools=TOOLS, toolbar_location='below')
globalDist.grid.grid_line_color = None
globalDist.axis.axis_line_color = None
globalDist.axis.major_tick_line_color = None
globalDist.axis.major_label_text_font_size = "5pt"
globalDist.axis.major_label_standoff = 0
globalDist.xaxis.major_label_orientation = pi / 3
globalDist.rect(x="cols", y="rows", width=1, height=1,
source=source,
fill_color={'field': 'count', 'transform': mapper},
line_color=None)
color_bar = ColorBar(color_mapper=mapper, major_label_text_font_size="5pt",
ticker=BasicTicker(desired_num_ticks=len(colors)),
# fix this via using a formatter with accounts for
formatter=PrintfTickFormatter(format="%d%%"),
label_standoff=6, border_line_color=None, location=(0, 0))
text_props = {"source": source, "text_align": "left", "text_baseline": "middle"}
x = dodge("cols", -0.4, range=globalDist.x_range)
r = globalDist.text(x=x, y=dodge("rows", 0.3, range=globalDist.y_range), text="count", **text_props)
r.glyph.text_font_size = "8pt"
globalDist.add_layout(color_bar, 'right')
globalDist.select_one(HoverTool).tooltips = [
('Well:', '#rows #cols'),
('Count:', '#count'),
]
return globalDist
def sectionizePlot(source, source_error, type, base):
print("sectionize plot created with typ: " + type)
colors = []
for x in range(0, len(base)):
colors.append(getRandomColor())
title = type + "-wise Intensity Distribution"
p = figure(plot_width=600, plot_height=300, title=title)
p.add_layout(
Whisker(source=source_error, base="base", upper="upper", lower="lower"))
for i, sec in enumerate(source.data['base']):
p.circle(x=source_error.data["base"][i], y=sec, color=colors[i])
p.xaxis.axis_label = type
p.yaxis.axis_label = "Intensity"
if (type.split()[-1] == "Row"):
print("hit a row")
conv = dict(enumerate(list("nABCDEFGHIJKLMNOP")))
conv.pop(0)
p.xaxis.major_label_overrides = conv
p.xaxis.ticker = SingleIntervalTicker(interval=1)
return p
famData = dict()
e1FractSource = ColumnDataSource(dict(count=[], cols=[], rows=[], index=[]))
e1Fract = ModifiedgenericHeatMap(e1FractSource, 100)
rowSectTotSource = ColumnDataSource(data=dict(base=[]))
rowSectTotSource_error = ColumnDataSource(data=dict(base=[], lower=[], upper=[]))
rowSectPlot_tot = sectionizePlot(rowSectTotSource,rowSectTotSource_error, "eSum Row", rowBase)
def update(selected=None):
global famData
famData = getFAMData(file_source_dt1, True)
global e1Stack
e1Fract = (famData['e1Sub'] / famData['eSum']) * 100
e1Stack = e1Fract.stack(dropna=False).reset_index()
e1Stack.columns = ["rows", "cols", "count"]
e1Stack['count'] = e1Stack['count'].apply(lambda x: round(x, 1))
e1FractSource.data = dict(cols=e1Stack["cols"], count=(e1Stack["count"]),
rows=e1Stack["rows"], index=e1Stack.index.values, codon=wells, )
rowData, colData = sectionize(famData['eSum'], rows, cols)
rowData_lower, rowData_upper = getLowerUpper(rowData)
rowBase = list(range(1, 17))
rowSectTotSource_error.data = dict(base=rowBase, lower=rowData_lower, upper=rowData_upper, )
rowSectTotSource.data = dict(base=rowData)
rowSectPlot_tot.title.text = "plot changed in update"
layout = column(e1FractSource, rowSectPlot_tot)
update()
curdoc().add_root(layout)
curdoc().title = "Specs"
print("ok")

Resources