I would like to represent a tree (virus spreading) into the world map. I use dendropy for working with trees; geopandas & shapely for processing regions, nodes & edges; folium for rendering this into interactive web-maps. The current result of the code below:
def colour_map(country_name: str) -> str:
if country_name == "Russia":
return "green"
else:
return "blue"
world_map = folium.Map(
location=(30.328056, -0.352369),
zoom_start=3,
tiles="cartodbdark_matter",
max_bounds=True,
min_lon=-45,
max_lon=315,
)
exUSSR.explore(
column="Name",
m=world_map,
highlight=False,
tooltip=False,
cmap=colour_map,
legend=False,
)
gpd.GeoDataFrame(nodes).convert_dtypes().explore(m=world_map, color="red")
gpd.GeoDataFrame(edges).convert_dtypes().explore(m=world_map, color="red")
This has not yet been brought to the state that I would desire. One of the problems is that it is really hard to trace the virus dissipation. In my view, it may be solved with edges colour gradient depending onto the age, especially if using logarithmic scale approximated to bifurcation rate. But I do not know how to set it. For instance, I can set a colourmap via cmap attribute in explore method from geopandas but it sets a colour of the whole object (edge in the case) depending onto column attribute values. The same is able to achieve via direct PolyLine initialisation in folium.
So my question is either how to give colour gradient to folium object or achieve a similar effect? I am ready to heed any good practice advice!
Related
I have a little Bokeh plot with data points and associated text labels. What I want is for the text labels to only appear when the user selects points with the box select tool. This gets me close:
from bokeh.plotting import ColumnDataSource,figure,show
source = ColumnDataSource(
data=dict(
x=test[:,0],
y=test[:,1],
label=[unquote_plus(vocab_idx[i]) for i in range(len(test))]))
TOOLS="box_zoom,pan,reset,box_select"
p = figure(plot_width=400, plot_height=400,tools=TOOLS)
p.circle(x='x',y='y', size=10, color="red", alpha=0.25,source=source)
renderer = p.text(x='x',y='y',text='label',source=source)
renderer.nonselection_glyph.text_alpha=0.
show(p)
This is close, in that if I draw a box around some points, those text labels are shown and the rest are hidden, but the problem is that it renders all the text labels to start (which is not what I want). The initial plot should have all labels hidden, and they should only appear upon a box_select.
I thought I could start by rendering everything with alpha=0.0, and then setting a selection_glyph parameter, like this:
...
renderer = p.text(x='x',y='y',text='label',source=source,alpha=0.)
renderer.nonselection_glyph.text_alpha=0.
renderer.selection_glyph.text_alpha=1.
...
But this throws an error:
AttributeError: 'NoneType' object has no attribute 'text_alpha'
When trying to access the text_alpha attribute of selection_glyph.
I know I could use a hover effect here or similar, but need the labels to default to not being visible. An alternative, but not ideal, solution would be to have a toggle button that switches the labels on and off, but I'm not sure how to do that either.
What am I doing wrong here?
As of version 0.11.1, the value of selection_glyph is None by default. This is interpreted by BokehJS as "don't do anything different, just draw the glyph as normal". So you need to actually create a selection_glyph. There are two ways to do this, both demonstrated here:
http://docs.bokeh.org/en/latest/docs/user_guide/styling.html#selected-and-unselected-glyphs
Basically, they are
by hand
Create an actual Circle Bokeh model, something like:
selected_circle = Circle(fill_alpha=1, fill_color="firebrick", line_color=None)
renderer.selection_glyph = selected_circle
OR
using glyph method parameters
Alternatively, as a convenience Figure.circle accepts paramters like selection_fill_alpha or selection_color (basically any line or fill or text property, prefixed with selection_) :
p.circle(..., selection_color="firebrick")
Then a Circle will be created automatically and used for renderer.selection_glyph
I hope this is useful information. If so, it highlights that there are two possible ways that the project could be improved:
updating the docs to be explicit and highlight that renderer.selection_glyph is None by default
changing code so that renderer.selection_glyph is just a copy of renderer.glyph by default (then your original code would work)
Either would be small in scope and ideal for a new contributor. If you would be interested in working up a Pull Request to do either of these tasks, we (and other users) would certainly be grateful for the contribution. In which case, please just make an issue first at
https://github.com/bokeh/bokeh/issues
that references this discussion, and we can provide more details or answer any questions.
I encountered a problem when trying to plot a graph with many nodes using NetworkX and graphviz_layout. More specifically, the arguments that pass into nx.graphviz_layout do not help at all. Attached is the code I use:
G=some_graph()
import matplotlib.pyplot as plt
plt.figure(figsize=(32,32))
# use graphviz to find radial layout
pos=nx.graphviz_layout(G,prog="dot",
root=1000,
args='-splines=true -nodesep=0.6 -overlap=scalexy'
)
nx.draw(G,pos,
with_labels=True,
alpha=0.5,
node_size=600,
font_size=10
)
plt.savefig("imagenet_layout.png")
No matter how I change "args" in nx.graphviz_layout, the output image would be the same, and all nodes overlap with each other. Could anybody help me with this? Thanks!
For me it seems that in order to give args to the prog you need to use the format '-G' +'argsname=x'. I noticed in the example they give the docs the arg epsilon asG.draw(‘test.ps’,prog=’twopi’,args=’-Gepsilon=1’). So I tried out that pattern as shown below. I just added G in front of the arguments. Now, these arguments vary quite a bit depending on what prog you use, so you definitely want to use 'dot' for what you want to accomplish. You can see all the possible arguments and how they work with each prog here. For my porpoises, I needed to have the nodesep=0.01.
G=some_graph()
import matplotlib.pyplot as plt
plt.figure(figsize=(32,32))
# use graphviz to find radial layout
pos=nx.graphviz_layout(G,prog="dot",
root=1000,
args='-Gsplines=true -Gnodesep=0.6 -Goverlap=scalexy'
)
nx.draw(G,pos,
with_labels=True,
alpha=0.5,
node_size=600,
font_size=10
)
plt.savefig("imagenet_layout.png")
Here is a comparison of my graph with and without the args, with code. First without the args.
A = nx.nx_agraph.to_agraph(G) # convert to a graphviz graph
A.layout(prog='neato') # neato layout
#A.draw('test3.pdf')
A.draw('test3.png' )
With args
A = nx.nx_agraph.to_agraph(G) # convert to a graphviz graph
A.layout(prog='dot') # neato layout
#A.draw('test3.pdf')
A.draw('test3.png',args='-Gnodesep=0.01 -Gfont_size=1', prog='dot' )
SO you can see that the images are different once I got the args to work.
My reading of the documentation for pygraphviz suggests that overlap does not work with dot.
For nodesep :
In dot, this specifies the minimum space between two adjacent nodes in the same rank, in inches.
It's not clear if the overlaps you are observing are between nodes in the same rank or between the ranks. If it is just between ranks, you may want to modify ranksep.
I do see that you are setting the positions, and then later you set the nodesize, and you are making node_size quite a bit larger than the default (600 vs 300). Since it does not know what node_size you are going to use when it finds pos, using a large enough node_size will cause overlap.
So I would recommend setting node_size to be the default, and if overlap remains, setting node_size to be smaller. If you're having issues with the between or within rank separations being out of proportion, then play with ranksep and nodesep.
About “overlap”,do you mean there are nodes drawed last time in current output? If so, add "plt.clf()"after"plt.savefig(****)"!
About the node_size, the default is 300, but the unit is not given in the document. I am using networkx these days too, can you tell me the unit if you know that?
In igraph for python, can you add a legend and title to a plot? Neither is mentioned in the manual or the tutorial as far as I can see. It is possible in R however.
R provides a pretty advanced plotting system on its own and the R interface simply makes use of this so that's why you can simply create plot titles and legends in R. Python does not provide any plotting by default, so igraph uses the Cairo library to draw graph plots. However, Cairo is "just" a generic vector graphics library. That's why you don't get the same advanced plotting capabilities in Python.
The plot function of igraph creates a Plot object in the background, adds the graph being plotted to the plot itself, creates an appropriate Cairo surface for it, and then starts drawing the graph on the Cairo surface. All this happens behind the scenes if you simply call plot with a graph as an argument. However, you can create a Plot object manually and then add labels to it before it is being plotted, like this:
>>> plot = Plot("plot.png", bbox=(600, 600), background="white")
At this point, you have a plot variable, which is an instance of igraph.drawing.Plot. The plot is backed by a Cairo image surface which is 600 pixels wide and 600 pixels high, and which will eventually be saved into a filed named plot.png. (You can also supply a Cairo surface directly in the first argument of the Plot constructor). Calling plot.redraw() would draw the plot but not save it yet. Calling plot.save() would draw the plot if it has not been drawn yet and then save it to the given filename.
You can then do two things with a plot:
Add an arbitrary object to the plot that has a __draw__ method. Graph objects have such a method so you can add a graph to the plot as follows:
>>> g = Graph.GRG(100, 0.2)
>>> plot.add(g, bbox=(20, 20, 580, 580))
Grab its surface property to access the Cairo surface on which the drawing is done, construct a Cairo drawing context with this surface, and then draw on the plot directly with Cairo using the drawing context.
The second option is how we are going to add labels to the plot. Luckily igraph provides a class named TextDrawer in the igraph.drawing.text package that helps us a bit with wrapping and alignment issues. We simply have to create a TextDrawer and then call its draw_at method to add a label to the plot at a given location:
>>> import cairo
>>> context = cairo.Context(plot.surface)
>>> text_drawer = TextDrawer(context, text="Test label", halign=TextDrawer.LEFT)
>>> text_drawer.draw_at(x=100, y=100)
The TextDrawer will draw the label with the current font of the Cairo context, so you have to use the set_font_face, set_font_size and related methods of the Cairo context to adjust the font that is used for drawing.
Putting it all together, the example goes like this:
from igraph import Graph, Plot
from igraph.drawing.text import TextDrawer
import cairo
# Construct the plot
plot = Plot("plot.png", bbox=(600, 650), background="white")
# Create the graph and add it to the plot
g = Graph.GRG(100, 0.2)
plot.add(g, bbox=(20, 70, 580, 630))
# Make the plot draw itself on the Cairo surface
plot.redraw()
# Grab the surface, construct a drawing context and a TextDrawer
ctx = cairo.Context(plot.surface)
ctx.set_font_size(36)
drawer = TextDrawer(ctx, "Test title", halign=TextDrawer.CENTER)
drawer.draw_at(0, 40, width=600)
# Save the plot
plot.save()
The example will add a title to the plot. Constructing a legend is more involved but I hope you can proceed further based on this idea. The labels of the legend can be constructed with repeatedly calling the draw or draw_at method of a TextDrawer (after adjusting the text property of the TextDrawer between calls of course). You can draw a box around the legend using standard Cairo calls. You can also use the node drawer classes in igraph.drawing.shapes if you want to draw node shapes similar to those that igraph uses when it draws a graph.
I have an application in which I'd like to draw counties from a shapefile using Basemap. Drawing the county polygons is the bottleneck in the rendering, and since I'll be drawing the same region of the US (a bunch of times), I'd rather not have to draw all the polygons any more than I need to. So I had the idea to draw the counties to a figure with a transparent background, copy the axes to a pixel buffer using copy_from_bbox(), and restore the buffer using restore_region() when I need to draw the counties.
The basic code goes like this:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
map = Basemap(...) # Create Basemap object
map.readshapefile("countyp020", 'counties', linewidth=0.5) # Draws the county lines
plt.gcf().patch.set_alpha(0.0)
plt.gca().patch.set_alpha(0.0)
# Copy to the pixel buffer (county_buffer is of type BufferRegion)
county_buffer = plt.gcf().canvas.copy_from_bbox(plt.gca().bbox)
plt.clf() # This line is problematic (see below)
# Plot my data here ...
# Restore the pixel buffer
plt.gcf().canvas.restore_region(county_buffer)
plt.gcf().canvas.blit(plt.gca().bbox) # Not sure if this line is necessary
plt.gcf().canvas.draw()
It works like a charm ... except for the line where I clear the figure. Clearing the figure between renderings apparently clears the BufferRegion object as well, and since I update the title and colorbar, I'd also like to clear the figure between renderings.
So my question is does anybody know a way to clear the figure and keep the pixel buffer intact? I haven't been able to find much documentation on BufferRegion, copy_from_bbox(), or restore_region(), so it's been a bit difficult to debug this. If there's no easy way around it, then does anybody know another way to do basically what I'm trying to do?
Thanks in advance!
I would like to add a scale bar (showing how big a micron is for example) to a mayavi plot I create with mlab.
For example, referencing this question: How to display a volume with non-cubic voxels correctly in mayavi
I can set the voxel size of a plot by using
from enthought.mayavi import mlab
import numpy as np
s=64
x,y,z = np.ogrid[0:s,0:s,0:s/2]
volume = np.sqrt((x-s/2)**2 + (y-s/2)**2 + (2*z-s/2)**2)
grid = mlab.pipeline.scalar_field(data)
grid.spacing = [1.0, 1.0, 2.0]
contours = mlab.pipeline.contour_surface(grid,
contours=[5,15,25], transparent=True)
mlab.show()
I would like an automated way of adding a some indicator of what the scale of the object I am showing is. Right now I am adding scale bars by hand with inkscape to exported images, but there has to be a better way.
A straightforward mayavi way would be most helpful, but if there is anything in vtk that would do it, I can always use mayavi's wrapper.
Something like text3d will let me add text, and then I suppose I could figure out how to draw a line as well and compute the correct scaling by hand, but I am hoping there is an easier way.
Try the following:
mlab.axes()
mlab.outline()
mlab.colorbar()
This reference: http://github.enthought.com/mayavi/mayavi/auto/mlab_reference.html would help as would the several examples.