I am new to netwrokx and pyvis and am making a small network to display the different shapes possible for each node. I managed to use all the shapes except for icons. I searched a lot but I couldn't find anything useful and the examples available did not work with my code I would appreciate it if anyone could help me figure this out.
here is my code:
import networkx as nx
import xlrd #used to access the external excel file
import pyvis
from pyvis.network import Network
import pandas as pd
import textwrap
df = pd.read_csv("Visualizer\Data\EECS2311\shapes.csv",encoding='cp1252')
G=nx.Graph()
nodes = []
p1 = df['person1']
p2 = df['person2']
p3 = df['person3']
p4 = df['person4']
p5 = df['person5']
p6 = df['person6']
p7 = df['person7']
p8 = df['person8']
p9 = df['person9']
p10 = df['person10']
p11 = df['person11']
p12 = df['person12']
p13 = df['person13']
p14 = df['person14']
data = zip(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14)
for e in data:
person1 = e[0]
G.add_node(person1, shape="ellipse")
person2 = e[1]
G.add_node(person2, shape="circle")
person3 = e[2]
G.add_node(person3, shape="database")
person4 = e[3]
G.add_node(person4, shape="box")
person5 = e[4]
G.add_node(person5, shape="text")
person6 = e[5]
G.add_node(person6, shape="image", image="https://image.shutterstock.com/image-vector/hello-funny-person-simple-cartoon-260nw-1311467669.jpg")
person7 = e[6]
G.add_node(person7, shape="circularImage", image="https://image.shutterstock.com/image-vector/hello-funny-person-simple-cartoon-260nw-1311467669.jpg")
person8 = e[7]
G.add_node(person8, shape="diamond")
person9 = e[8]
G.add_node(person9, shape="dot")
person10 = e[9]
G.add_node(person10, shape="star")
person11 = e[10]
G.add_node(person11, shape="triangle")
person12 = e[11]
G.add_node(person12, shape="triangleDown")
person13 = e[12]
G.add_node(person13, shape="square")
person14 = e[13]
G.add_node(person14, shape="icon", icon="https://image.shutterstock.com/image-vector/hello-funny-person-simple-cartoon-260nw-1311467669.jpg")
nodes.append((person1, person2))
nodes.append((person2, person3))
nodes.append((person3, person4))
nodes.append((person4, person5))
nodes.append((person5, person6))
nodes.append((person6, person7))
nodes.append((person7, person8))
nodes.append((person8, person9))
nodes.append((person9, person10))
nodes.append((person10, person11))
nodes.append((person11, person12))
nodes.append((person12, person13))
nodes.append((person13, person14))
options = {
"layout": {
"hierarchical": {
"enabled": True,
"levelSeparation": 300,
"nodeSpacing": 165,
"treeSpacing": 305,
"direction": "LR"
}
},
"physics": {
"hierarchicalRepulsion": {
"centralGravity": 0,
"nodeDistance": 110,
},
"minVelocity": 0.75,
"solver": "hierarchicalRepulsion"
}
}
G.add_edges_from(nodes)
G2 = Network(height="800px", width="100%", bgcolor="#222222", font_color="white", select_menu=True, filter_menu=True, directed=True)
G2.from_nx(G)
G2.options = options
neighbor_map = G2.get_adj_list()
for node in G2.nodes:
node["value"] = len(neighbor_map[node["id"]])
#to wrap long labels:
id_string = node["label"]
width = 20
wrapped_strings = textwrap.wrap(id_string, width)
wrapped_id ="";
for line in wrapped_strings:
wrapped_id = textwrap.fill(id_string, width)
node["label"] = wrapped_id
#G2.show_buttons()
G2.show("shapes.html")
and here is my .csv file:
person1,person2,person3,person4,person5,person6,person7,person8,person9,person10,person11,person12,person13,person14
ellipse, circle, database,box,text,image, circularImage,diamond,dot,star,triangle,triangleDown,square,icon
"ellipse shape displays label inside the shape. To use this simply set shape =""ellipse""","circle shape displays label inside the shape. To use this simply set shape =""circle""","database shape displays label inside the shape. To use this simply set shape =""database""","box shape displays label inside the shape. To use this simply set shape =""box""","only displays text. To use this simply set shape =""text""","image displays a image with label outside. To use set shape=""image"", image=""url"". Note: requires link to image","circularImage displays a circular image with label outside. To use set shape="" circularImage"", image=""url"". Note: requires link to image","diamond shape displays label outside the shape. To use this simply set shape =""diamond""","dot shape displays label outside the shape. To use this simply set shape =""dot""","star shape displays label outside the shape. To use this simply set shape =""star""","triangle shape displays label outside the shape. To use this simply set shape =""triangle""","triangleDown shape displays label outside the shape. To use this simply set shape =""triangleDown""","square shape displays label outside the shape. To use this simply set shape =""square""","icon displays a circular image with label outside. To use set shape="" icon"", image=""url"". Note: requires link to image"
ps. forgive the heading for the csv file :)
This doesn't answer your question, I just want to help you shrink your code so you can debug it more easily.
Use the DataFrame directly
You're doing a ton of extra work to get at your data, assigning to temporary variables, then zipping them together. They are already together! To loop over the things in row 0 of the DataFrame try this:
for item in df.loc[0]:
print(item)
There's also a function in NetworkX, nx.from_pandas_dataframe(), that will create a network directly from a DataFrame... but you can only add edge attributes with that, not node attributes.
Then again...
Maybe dn't even use a DataFrame
Pandas is a convenient way to load CSVs, but your data isn't all that well-suited to this data structure. A dict would be better. It's a kind of mapping, in your case from node names to a node attribute.
Fortunately, there's a fairly easy way to get a dict from your DataFrame:
df.T.to_dict()[0]
This 'transposes' the DataFrame (turns the rows into columns) then turns the result into a dict. Then the [0] gives you the only column in the data.
This way you can avoid needing to repeat all your data (the mapping from person number to symbol) in your code.
Then again...
Maybe don't even use a dictionary
Any time you are mapping from a continuous set of numbers to some other objects (like person1, person2, etc) you might as well just use a list. Everything is indexed by position, which is basically what you have already. So you could just store your data like ['ellipse', 'circle', 'dot'] etc.
Then again...
Maybe don't even store the data
It turns out all these symbols are already defined in matplotlib. Have a look at:
from matplotlib.lines import Line2D
Line2D.markers
It's a dictionary of all the markers! If you want to try all of them, then you can just use these, no need to define anything.
Use zip to add your edges
zip is great for combining two or more lists, or combining a list with itself but with some offset. You can step over the nodes and make edges like so:
nodes = list(G.nodes)
for u, v in zip(nodes, nodes[1:]):
G.add_edge(u, v)
General advice
Try to avoid using tools like pandas just to load data. In my experience, it often introduces a bunch of complexity you don't need.
Get something small and simple working before making it more complex, e.g. with URLs of images.
You can store dictionaries easily as JSON text files. Check out the json module.
Again, sorry for not directly answering your question. But I feel like all this should help get your code down to something that is much easier to debug.
Related
I am trying to create a volume in Gmsh (using Python API) by cutting some small cylinders from a bigger one.
When I do that, I expect to have one surface for each cutted region, instead, I get the result in the figure. I have highlighted in red the surfaces that give me the problem (some cutted regions behave as expected), as you can see, instead of one surface I get two, that sometimes aren't even equal.
gmsh creates more surfaces than expected:
So, my questions are:
Why gmsh behaves like that?
How can I fix this as I need predictable behavior?
Below is the code I used to generate the geometry.
The code to work requires some parameters such as core_height, core_inner_radius and core_outer_radius, the number of small cylinders and their radius.
gmsh.initialize(sys.argv)
#gmsh.initialize()
gmsh.clear()
gmsh.model.add("circle_extrusion")
inner_cyl_tag = 1
outer_cyl_tag = 2
inner_cyl = gmsh.model.occ.addCylinder(0,0,0, 0, 0, core_height, core_inner_radius, tag = inner_cyl_tag)
outer_cyl = gmsh.model.occ.addCylinder(0,0,0, 0, 0, core_height, core_outer_radius, tag = outer_cyl_tag)
core_tag = 3
cut1 = gmsh.model.occ.cut([(3,outer_cyl)],[(3,inner_cyl)], tag = core_tag)
#create a set of filled cylinders
#set position
angle_vector = np.linspace(0,2*np.pi,number_of_hp+1)
pos_x = hp_radial_position*np.cos(angle_vector)
pos_y = hp_radial_position*np.sin(angle_vector)
pos_z = 0.0
#cut one cylinder at the time and assign the new core tag
for ii in range(0,len(angle_vector)):
old_core_tag = core_tag
heat_pipe = gmsh.model.occ.addCylinder(pos_x[ii], pos_y[ii], pos_z, 0, 0, core_height,hp_outer_radius, tag =-1)
core_tag = heat_pipe+1
core = gmsh.model.occ.cut([(3,old_core_tag)],[(3,heat_pipe)], tag = core_tag)
gmsh.model.occ.synchronize()
#get volume entities and assign physical groups
volumes = gmsh.model.getEntities(dim=3)
solid_marker = 1
gmsh.model.addPhysicalGroup(volumes[0][0], [volumes[0][1]],solid_marker)
gmsh.model.setPhysicalName(volumes[0][0],solid_marker, "solid_volume")
#get surfaces entities and apply physical groups
surfaces = gmsh.model.getEntities(dim=2)
surface_markers= np.arange(1,len(surfaces)+1,1)
for ii in range(0,len(surfaces)):
gmsh.model.addPhysicalGroup(2,[surfaces[ii][1]],tag = surface_markers[ii])
#We finally generate and save the mesh:
gmsh.model.mesh.generate(3)
gmsh.model.mesh.refine()
gmsh.model.mesh.refine()
gmsh.option.setNumber("Mesh.MshFileVersion", 2.2) #save in ASCII 2 format
gmsh.write(mesh_name+".msh")
# Launch the GUI to see the results:
#if '-nopopup' not in sys.argv:
# gmsh.fltk.run()
gmsh.finalize()
I do not think that you have additional surfaces in the sense of gmsh.model.occ surfaces. To me this looks like your volume mesh is sticking out of your surface mesh, i.e. volume and surface mesh do not fit together.
Here is what I did to check your case:
First I added the following lines at the beginning of our code to get a minimum working example:
import gmsh
import sys
import numpy as np
inner_cyl_tag = 1
outer_cyl_tag = 2
core_height = 1
core_inner_radius = 0.1
core_outer_radius = 0.2
number_of_hp = 5
hp_radial_position = 0.1
hp_outer_radius = 0.05
What I get with this code is the following:
To visualize it like this go to "Tools"-->"Options"-->"Mesh" and check "2D element faces", "3D element edges" and "3D element faces".
You can see that there are some purple triangles sticking out of the green/yellowish surfaces triangles of the inner surfaces.
You could try to visualize your case the same way and check <--> uncheck the "3D element faces" a few times.
So here is the solution for this behaviour, I did not know that gmsh behaves like this myself. It seems that when you create your mesh and refine it the refinement will be applied on the 2D surface mesh and the 3D volume mesh seperately, which means that those two meshes are not connected after the refinement anymore. What I did next was to try what happens if you create the 2D mesh only, refine it, and then create the 3D mesh, i.e.:
replace:
gmsh.model.mesh.generate(3)
gmsh.model.mesh.refine()
gmsh.model.mesh.refine()
by:
gmsh.model.mesh.generate(2)
gmsh.model.mesh.refine()
gmsh.model.mesh.refine()
gmsh.model.mesh.generate(3)
The result then looks like this:
I hope that this was actually your problem. But in future it would be good if you could provide us a minimum working example of code that we can copy-paste and get the same visualization you showed us in your image.
I am trying to animate a plot using visvis.
This is the example code they have:
import visvis as vv
# read image
ims = [vv.imread('astronaut.png')]
# make list of images: decrease red channel in subsequent images
for i in range(9):
im = ims[i].copy()
im[:,:,0] = im[:,:,0]*0.9
ims.append(im)
# create figure, axes, and data container object
a = vv.gca()
m = vv.MotionDataContainer(a)
# create textures, loading them into opengl memory, and insert into container.
for im in ims:
t = vv.imshow(im)
t.parent = m
and I added:
app = vv.use()
app.Run()
This worked. But I needed to animate a plot, not an image, so I tried doing this:
import visvis as vv
from visvis.functions import getframe
# create figure, axes, and data container object
a = vv.gca()
m = vv.MotionDataContainer(a, interval=100)
for i in range(3):
vv.plot([0, 2+i*10], [0, 2+i*10])
f = getframe(a)
t = vv.imshow(f)
t.parent = m
a.SetLimits(rangeX=[-2, 25], rangeY=[-2, 25])
app = vv.use()
app.Run()
The axes are being initialized very big, that is why I am using set limits, and the output is not animated. I am getting only the last frame so a line from (0,0) to (22, 22).
Does anyone know a way of doing this with visvis?
It turns out adding the frame as a child of MotionDataContainer was not the way to go. The function vv.plot returns an instance of the class Line, and one should add the line as a child. If anyone is having the same problem, I could write a more detailed answer.
EDIT Adding a more detailed answer as requested:
To animate a plot made of lines, one must simply add the lines as children of MotionDataContainer. Taking my example in the question above, one would write:
import visvis as vv
# create figure, axes, and data container object
a = vv.gca()
m = vv.MotionDataContainer(a, interval=100)
for i in range(3):
line = vv.plot([0, 2+i*10], [0, 2+i*10])
line.parent = m
app = vv.use()
app.Run()
In my special case, I even needed to animate multiple lines being drawn at the same time.
To do this, I ended up defining a new class that, like MotionDataContainer, also inherits from MotionMixin, and change the class attribute delta which specifies how many objects should be made visible at the same time. For that, one has to also rewrite the function _SetMotionIndex.
(See visvis official source code: https://github.com/almarklein/visvis/blob/master/wobjects/motion.py)
Disclaimer: Concerning the animation of multiple objects, I have no idea if this is the intended use or if this is the easiest solution, but this is what worked for me.
I need to make a GeoDataFrame of some nodes on a road network (which was extracted from OpenStreetMap using OSMnx). In the code below, graph_proj is the graph whose nodes I'm working with, the points are start_point and end_point:
import osmnx as ox
import geopandas as gpd
nodes_proj, edges_proj = ox.graph_to_gdfs(graph_proj, nodes=True, edges=True)
# Finding the nodes on the graph nearest to the points
start_node = ox.nearest_nodes(graph_proj, start_point.geometry.x, start_point.geometry.y, return_dist=False)
end_node = ox.nearest_nodes(graph_proj, end_point.geometry.x, end_point.geometry.y, return_dist=False)
start_closest = nodes_proj.loc[start_node]
end_closest = nodes_proj.loc[end_node]
# Create a GeoDataBase from the start and end nodes
od_nodes = gpd.GeoDataFrame([start_closest, end_closest], geometry='geometry', crs=nodes_proj.crs)
During the last step ("# Create a GeoDataBase...", etc.), an error is thrown. Apparently, it has something to do with a 3-dimensional array being passed to the GeoDataFrame function. Am I right that the way I pass in the locations([start_closest, end_closest]) results in a 3D array? (The error message reads, 'Must pass 2-d input. shape=(2, 1, 7)') I tried transposing this array, but then GeoPandas could not locate the 'geometry' column. How do I go about passing in this argument in a way that it will be accepted?
OK, so I was able to get around this by writing each node to its own GeoDataFrame and then merging the two GeoDataFrames, like this:
od_nodes1 = gpd.GeoDataFrame(start_closest, geometry='geometry', crs=nodes_proj.crs)
od_nodes2 = gpd.GeoDataFrame(end_closest, geometry='geometry', crs=nodes_proj.crs)
od_nodes = od_nodes1.append(od_nodes2)
Surely, though, there must be a more elegant way of writing more than one feature into a GeoDataFrame?
I'm trying to optimize the porosity distribution of a certain material. I would like to visualize the results. I can visualize the different materials using 'visualize->material' however he gives every material a random color. I would like the least dense materials to be blue and the densest to be red. So the same as for the stress plots.
Is there a way to do this in Abaqus?
If there's no simple way to do this in the GUI, I was wondering would it be possible by using scripting? I tried to change a single color which resulted in the following code:
session.viewports['Viewport: 1'].enableMultipleColors()
session.viewports['Viewport: 1'].setColor(initialColor='#BDBDBD')
cmap=session.viewports['Viewport: 1'].colorMappings['Material']
session.viewports['Viewport: 1'].setColor(colorMapping=cmap)
session.viewports['Viewport: 1'].disableMultipleColors()
session.viewports['Viewport: 1'].enableMultipleColors()
session.viewports['Viewport: 1'].setColor(initialColor='#BDBDBD')
cmap = session.viewports['Viewport: 1'].colorMappings['Material']
cmap.updateOverrides(overrides={'IMPLANT_MATERIAL0':(True, '#FF0000',
'Default', '#FF0000')})
session.viewports['Viewport: 1'].setColor(colorMapping=cmap)
session.viewports['Viewport: 1'].disableMultipleColors()
session.viewports['Viewport: 1'].enableMultipleColors()
session.viewports['Viewport: 1'].setColor(initialColor='#BDBDBD')
cmap = session.viewports['Viewport: 1'].colorMappings['Material']
session.viewports['Viewport: 1'].setColor(colorMapping=cmap)
session.viewports['Viewport: 1'].disableMultipleColors()
If you're looking for something like stress plot visualisation, you'll have to write your own FieldOutput data. It's generally easier to output the data directly to an external visualiser, but it's possible (if not a bit convoluted) to do this in Abaqus.
The general process is this:
Generate a FieldOutput object; syntax is FO = odbModel.steps.values()[-1].frames[-1].FieldOutput(name=data_name, description=data_description, type=SCALAR), where
odbModel is an opened Odb object,
steps.values()[-1] or a named step steps[...] is the step you want to output to,
frames[-1] is the last frame (or a frame of your choice) that you want to output to in this step,
data_name and data_description are strings (for stress contour plots, data_name would be equivalent to the label S in the odb output)
SCALAR is a parameter from the abaqusConstants module
Get the rootAssembly.instance objects, and their associated element elementSets and sectionAssignments which have clear links to a section with a material which has a density attribute.
Update the FieldOutput object with the addData command; syntax is addData(position=CENTROID, instance=instance, labels=labels, data=data)
CENTROID is a parameter from the abaqusConstants module (assuming you just want to have element densities at the element centroid; you can stick them at integration points too, if you really wanted)
instance is the instance associated with the element set (or more generally the region assigned with this material)
labels is an iterable (list, tuple) of integers specifying the element labels of the associated instance for which the data is to be written at
data is an iterable of iterables of floats, specifying the data. In your case, a single density value means that data is an iterable of length-1 iterables, each containing one value of density. The length of data must be equal to the length of labels, as each member of data exactly corresponds to the elementLabel in the same position in labels.
Example script below (WARNING: Heavily recommend backing up the .odb in case something didn't turn out right)
import odbAccess
from abaqusConstants import SCALAR, CENTROID # Import constants
odbModel = odbAccess.openOdb(odb_file_path) # Put the file path of the `odb` in odb_file_path
FO = odbModel.steps.values()[-1].frames[-1].FieldOutput(name='Density', description='', type=SCALAR)
# Loop through `rootAssembly.instances`
for instance in odbModel.rootAssembly.instances.values():
valid_materials = [] # Valid material names which have `density`
# Loop through `instance.sectionAssignments` to check if the associated `section` has a `material` with the attribute `density`
for i in range(len(instance.sectionAssignments)):
sectionName = instance.sectionAssignments[i].sectionName
matName = odbModel.sections[sectionName].material
if hasattr(odbModel.materials[matName], 'density'):
valid_materials.append(matName)
sectionNames = [] # Build list of valid section names which are associated with a material which has the attribute `density`
for s in odbModel.sections.values():
if s.material in valid_materials:
sectionNames.append(s.name)
if sectionNames:
# Loop through `sectionAssignments` and get all the `elementLabels` in the `region` of the `sectionAssignment`
for sa in instance.sectionAssignments:
sa_labels = []
if sa.sectionName in sectionNames:
# Get labels
if sa.region.elements is not None:
for e in sa.region.elements:
sa_labels.append(e.label)
# Get material
matName = odbModel.sections[sa.sectionName].material
sa_data = [(odbModel.materials[matName].density.table[0][0],)]*len(sa_labels)
# Update `fieldOutput` object
FO.addData(position=CENTROID, instance=instance, labels=sa_labels, data=sa_data)
# Save odb model. The FieldOutput object only exists as reference from `FO` unless the odb model is saved.
odbModel.save()
odbModel.close()
odbModel = odbAccess.openOdb(odb_file_path) # Reopen the model for visualisation. If you can't find the data_name in the list, expand the model to the step and frame for which the data is saved.
I don't work with density, but here's an example output for Young's modulus for a model with two materials assigned to various elements.
It's probably not the perfect method but this works.
Limitations:
-You need to manually put in the amount of materials.
-Your materials should be ranked according to density(mat1,mat2->density1
-You should put your material name in the script (in my case it was 'Implant')
Suggestions for improvement are always welcome, this was just quick and dirty.
from math import floor
diminishing_factor = 10 #This factor diminishes the amount of colors to:
amount of materials/diminishing factor. This is necessary
#because apparently abaqus can only handle a limited amount of colors (+-50)
def create_color_lst(amount_of_mat):
color_lst=[]
total_length = 256*4-1 #0 telt ook dus -1
interval = floor(total_length/(amount_of_mat-1)) #*10 because we'll give
10 consequent materials the same color, because abaqus can't handle it
for i in range(0,amount_of_mat):
pos = int(floor(i/diminishing_factor))*diminishing_factor*interval
if pos<256: #Green is rising
col_pos=pos
code = (0,col_pos,255)
elif pos<512: #Blue is diminishing
col_pos=pos-255
code = (0,255,255-col_pos)
elif pos<768:
col_pos = pos - 511
code = (col_pos,255,0)
elif pos<1024:
col_pos = pos - 767
code = (255,255-col_pos,0)
else:
raise ValueError('Color position is too high: '+str(pos))
hex_code='#%02x%02x%02x' % code
color_lst.append(hex_code.upper())
return color_lst
def update_colors(color_lst):
session.viewports['Viewport: 1'].enableMultipleColors()
session.viewports['Viewport: 1'].setColor(initialColor='#BDBDBD')
cmap = session.viewports['Viewport: 1'].colorMappings['Material']
for i in range(0,amount_of_mat):
material = 'IMPLANT_MATERIAL'+str(i)
cmap.updateOverrides(overrides={material:(True, color_lst[i],
'Default', color_lst[i])})
if i%10==0:
print(i)
session.viewports['Viewport: 1'].setColor(colorMapping=cmap)
session.viewports['Viewport: 1'].disableMultipleColors()
amount_of_mat=494 #We can't get this you should always check this! (you
probably could but I'm to lazy to search it)
color_lst = create_color_lst(amount_of_mat) #Creates a list with strings
that contain the color names
update_colors(color_lst) #Updates the display (it's possible that you still
need to go to the display color dialog and press apply)
I define the vertices of a polygon and their types as follows:
verts=[(x1,y1),(x2,y2),(x3,y3),(x4,y4),(0,0)]
codes=[Path.MOVETO,Path.LINETO,Path.LINETO,Path.LINETO,Path.CLOSEPOLY]
path=Path(verts,codes)
patch=patches.PathPatch(path)
Now I would like to extract the indices of the data points contained inside the vertices so that I can manipulate them. I tried the following:
datapts = np.column_stack((x_data,y_data))
inds, = Path(verts).contains_points(datapts)
but of course the verts themselves are not data and so this doesn't work. Help is appreciated.
3 minutes after I posted the above I found the answer:
inds = path.contains_points(datapts)
This can be used as follows:
ax.plot(x_data[inds], y_data[inds])