I'd like to draw path patches in python with a gradient fill to better visualize the start and end of a path connection. I know that I can use FanyArrowPatch to have an arrow to indicate the direction but I'd prefer to use gradient colors (e.g. Blue to red).
Is there a way to define the color of a patch using something simple like just idnicating color = "RdBu"?
For wedge patches I used a not so convient way (for loop) by plotting small segements of it seperatly with adapting the colors. However, in my end plot I will have all lot of connections using patches.PathPatch and I need a more efficient way to define a gradient color. Also, I would not know how the apply the for-loop trick on Paths patches...
Do you have any suggestions how to plot pathes with gradient colors? Are there any other packages I could try?
import numpy as np
import matplotlib.patches as mpatches
from matplotlib.collections import PatchCollection
import matplotlib.pyplot as plt
import seaborn as sns
#---------------------------------------------
# Drawing example
#---------------------------------------------
fig, ax = plt.subplots(figsize=(8,8))
plt.title('matplotlib mpatch / path does not accept gradient colors directly')
ring = mpatches.Wedge((0,0), 0.7, 190, 350, color='r',width=0.2, label = 'wedge with only one color, no direction')
ax.add_patch(ring)
# drawing desired output with easy fix using for-loop, whcih is not working for mpatches.FancyArrowPatch
total_deg = 170
n_steps = 100
d_deg = total_deg / n_steps
colors = sns.color_palette("vlag", n_steps)
for i in range(n_steps-1):
ring = mpatches.Wedge((0,0), 0.7, i*d_deg, (i+1)*d_deg, color=colors[i],width=0.2)
ax.add_patch(ring)
i = i+1
ring = mpatches.Wedge((0,0), 0.7, i*d_deg, (i+1)*d_deg, color=colors[i],width=0.2, label = 'wedge with gradient color using for-loop')
ax.add_patch(ring)
# drawing desired output with easy fix using for-loop, whcih is not working for mpatches.FancyArrowPatch
verts = [[0.8,0.2],[0,1.4],[-0.9, 0.2]]
codes = [Path.MOVETO, Path.CURVE3, Path.CURVE3]
lw = 5
path = Path(verts, codes)
patch = mpatches.FancyArrowPatch(path=path,arrowstyle="-|>,head_length=10,head_width=10", lw=lw, color='b', zorder=1, label = 'path that should have gradient color')
ax.add_patch(patch)
plt.xlim([-1.5,1.5])
plt.ylim([-1.5,1.5])
plt.legend()
plt.axis('off')
plt.show()
Related
I want to plot a few Rectangle outlines using matplotlib. The thing is, I need tons of them, so the "normal" way of drawing rectangles is pretty slow. I solved this using How to speed up plot.... The thing is, I am not able anymore to just plot the edge of the rectangles using fill=None or edgecolor=... and facecolor=None.
See a toy example of my code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.patches import Rectangle
def plot_rectangle_from_area(area, color):
"""Plot a rectangle for a given area with color"""
return Rectangle(xy=(area["min_x"], area["min_y"]), width=area["max_x"] - area["min_x"],
height=area["max_y"] - area["min_y"],
fill=None) # linewidth=0, edgecolor=color, facecolor=None, zorder=100, alpha=0,
sample_areas = [{"min_x": -1, "max_x": 0.4, "min_y": 0.7, "max_y": 1},
{"min_x": 0.5, "max_x": 1, "min_y": 0.1, "max_y": 0.5}]
rectangles = []
fig, ax = plt.subplots()
# ... print some contour via meshgrid
if sample_areas:
for area_i in sample_areas:
rectangles.append(plot_rectangle_from_area(area_i, color="r"))
# ... some more cases
# Append the rectangles all at once instead of on their own, see:
# https://stackoverflow.com/questions/33905305/how-to-speed-up-the-plot-of-a-large-number-of-rectangles-with-matplotlib
ax.add_collection(PatchCollection(rectangles))
# ... legend, save, grid, ...
plt.show()
At first I create a array for all the Rectangles, the append to it and use PatchCollection to plot it 1. The following example does exactly the same, just without the PatchCollection and is working fine.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.patches import Rectangle
def plot_rectangle_from_area(area, color):
"""Plot a rectangle from given area"""
return Rectangle(xy=(area["min_x"], area["min_y"]), width=area["max_x"] - area["min_x"],
height=area["max_y"] - area["min_y"],
fill=None) # linewidth=0, edgecolor=color, facecolor=None, zorder=100, alpha=0,
sample_areas = [{"min_x": -1, "max_x": 0.4, "min_y": 0.7, "max_y": 1},
{"min_x": 0.5, "max_x": 1, "min_y": 0.1, "max_y": 0.5}]
fig, ax = plt.subplots()
if sample_areas:
for area_i in sample_areas:
ax.add_patch(plot_rectangle_from_area(area_i, color="r"))
plt.show()
Here are some plots I created with both of these codes. On the left the wished result using the slow method, on the right the result I get with PatchCollection:
I tried multiple combinations of fill, edgecolor, facecolor and even the suggestion with zorder from here.
Is it possible to use the "fast" way of creating rectangles and have just the borders shown?
Yes - looking at the documentation for PatchCollection, there is a parameter called match_original, which - when True - will set the properties of the patches to match those of the original rectangles.
So simply change
ax.add_collection(PatchCollection(rectangles))
to
ax.add_collection(PatchCollection(rectangles, match_original=True))
I'm trying to produce a similar version of this image using Python:
I'm close but can't quite figure out how to modify a matplotlib colormap to make values <0.4 go to white. I tried masking those values and using set_bad but I ended up with a real blocky appearance, losing the nice smooth contours seen in the original image.
Result with continuous colormap (problem: no white):
Result with set_bad (problem: no smooth transition to white):
Code so far:
from netCDF4 import Dataset as NetCDFFile
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.basemap import Basemap
nc = NetCDFFile('C:/myfile1.nc')
nc1 = NetCDFFile('C:/myfile2.nc')
lat = nc.variables['lat'][:]
lon = nc.variables['lon'][:]
time = nc.variables['time'][:]
uwnd = nc.variables['uwnd'][:]
vwnd = nc1.variables['vwnd'][:]
map = Basemap(llcrnrlon=180.,llcrnrlat=0.,urcrnrlon=340.,urcrnrlat=80.)
lons,lats = np.meshgrid(lon,lat)
x,y = map(lons,lats)
speed = np.sqrt(uwnd*uwnd+vwnd*vwnd)
#speed = np.ma.masked_where(speed < 0.4, speed)
#cmap = plt.cm.jet
#cmap.set_bad(color='white')
levels = np.arange(0.0,3.0,0.1)
ticks = np.arange(0.0,3.0,0.2)
cs = map.contourf(x,y,speed[0],levels, cmap='jet')
vw = plt.quiver(x,y,speed)
cbar = plt.colorbar(cs, orientation='horizontal', cmap='jet', spacing='proportional',ticks=ticks)
cbar.set_label('850 mb Vector Wind Anomalies (m/s)')
map.drawcoastlines()
map.drawparallels(np.arange(20,80,20),labels=[1,1,0,0], linewidth=0.5)
map.drawmeridians(np.arange(200,340,20),labels=[0,0,0,1], linewidth=0.5)
#plt.show()
plt.savefig('phase8_850wind_anom.png',dpi=600)
The answer to get the result smooth lies in constructing your own colormap. To do this one has to create an RGBA-matrix: a matrix with on each row the amount (between 0 and 1) of Red, Green, Blue, and Alpha (transparency; 0 means that the pixel does not have any coverage information and is transparent).
As an example the distance to some point is plotted in two dimensions. Then:
For any distance higher than some critical value, the colors will be taken from a standard colormap.
For any distance lower than some critical value, the colors will linearly go from white to the first color of the previously mentioned map.
The choices depend fully on what you want to show. The colormaps and their sizes depend on your problem. For example, you can choose different types of interpolation: linear, exponential, ...; single- or multi-color colormaps; etc..
The code:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
# create colormap
# ---------------
# create a colormap that consists of
# - 1/5 : custom colormap, ranging from white to the first color of the colormap
# - 4/5 : existing colormap
# set upper part: 4 * 256/4 entries
upper = mpl.cm.jet(np.arange(256))
# set lower part: 1 * 256/4 entries
# - initialize all entries to 1 to make sure that the alpha channel (4th column) is 1
lower = np.ones((int(256/4),4))
# - modify the first three columns (RGB):
# range linearly between white (1,1,1) and the first color of the upper colormap
for i in range(3):
lower[:,i] = np.linspace(1, upper[0,i], lower.shape[0])
# combine parts of colormap
cmap = np.vstack(( lower, upper ))
# convert to matplotlib colormap
cmap = mpl.colors.ListedColormap(cmap, name='myColorMap', N=cmap.shape[0])
# show some example
# -----------------
# open a new figure
fig, ax = plt.subplots()
# some data to plot: distance to point at (50,50)
x,y = np.meshgrid(np.linspace(0,99,100),np.linspace(0,99,100))
z = (x-50)**2. + (y-50)**2.
# plot data, apply colormap, set limit such that our interpretation is correct
im = ax.imshow(z, interpolation='nearest', cmap=cmap, clim=(0,5000))
# add a colorbar to the bottom of the image
div = make_axes_locatable(ax)
cax = div.append_axes('bottom', size='5%', pad=0.4)
cbar = plt.colorbar(im, cax=cax, orientation='horizontal')
# save/show the image
plt.savefig('so.png')
plt.show()
The result:
I plot a graph created by networkx with matplotlib. Now, I'd like to add annotation to a specific node with a circle around. For instance,
I use plt.annotate(*args, **kwargs) with the following code,
# add annotate text
pos = nx.get_node_attributes(G, 'pos')
pos_annotation_node = pos['Medici']
ax2.annotate('Midici',
xy=pos_annotation_node,
xytext=(i+0.2 for i in pos_annotation_node),
color='blue',
arrowprops=dict(facecolor='blue', shrink=0.01)
)
And I got this ugly graph,
I have two questions:
how do I draw a circle around the node 6 as shown in first figure.
to get a nice looking figure, I need manually set the value of xytext many times. Is there a better way?
If you use the fancyarrow arrowprops syntax as demonstrated in annotation_demo2, there is a shrinkA and shrinkB option that lets you shrink your arrow tail (shrinkA) and tip (shrinkB) independently, in points units.
Here's some arbitrary setup code:
import matplotlib.pyplot as plt
import numpy as np
# Some data:
dat = np.array([[5, 3, 4, 4, 6],
[1, 5, 3, 2, 2]])
# This is the point you want to point out
point = dat[:, 2]
# Make the figure
plt.figure(1, figsize=(4, 4))
plt.clf()
ax = plt.gca()
# Plot the data
ax.plot(dat[0], dat[1], 'o', ms=10, color='r')
ax.set_xlim([2, 8])
ax.set_ylim([0, 6])
And here is the code that puts a circle around one of these points and draws an arrow that is shrunk-back at the tip only:
circle_rad = 15 # This is the radius, in points
ax.plot(point[0], point[1], 'o',
ms=circle_rad * 2, mec='b', mfc='none', mew=2)
ax.annotate('Midici', xy=point, xytext=(60, 60),
textcoords='offset points',
color='b', size='large',
arrowprops=dict(
arrowstyle='simple,tail_width=0.3,head_width=0.8,head_length=0.8',
facecolor='b', shrinkB=circle_rad * 1.2)
)
Note here that:
1) I've made the marker face color of the circle transparent with mfc='none', and set the circle size (diameter) to twice the radius.
2) I've shrunk the arrow by 120% of the circle radius so that it backs off of the circle just a bit. Obviously you can play with circle_rad and the value of 1.2 until you get what you want.
3) I've used the "fancy" syntax that defines several of the arrow properties in a string, rather than in the dict. As far as I can tell the shrinkB option is not available if you don't use the fancy arrow syntax.
4) I've used the textcoords='offset points' so that I can specify the position of the text relative to the point, rather than absolute on the axes.
how do I draw a circle around the node 6 as shown in first figure.
You get a center of node #6 (tuple pos). Use this data to set the blue circle position.
to get a nice looking figure, I need manually set the value of xytext many times. Is there a better way?
Make a list of your labels and iterate in it and in tuples of coordinates of nodes to post annotate text. Look to comments of a code.
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from matplotlib.patches import Circle
import matplotlib.patches as patches
import numpy as np
from matplotlib.font_manager import FontProperties
font = FontProperties()
font.set_weight('bold')
font.set_size('medium')
labels = ["Midici","Firenze"]
image = mpimg.imread("g.png") # just a image of your graph
plt.imshow(image)
ax = plt.gca()
# set your own radius and centers of circles in loop, like here
r = 11; c = (157,177)
circ1 = patches.Circle(c,2*r,lw=3.,ec='b',fill=False)
ax.add_artist(circ1)
circ1.set_clip_box(ax.bbox)
# annotate circles
# I have one circle but for your array pos_annotation_node
# you need 'i' to extract proper position
for i,label in enumerate(labels):
annot_array_end = (c[0], c[1]+2*(-1)**i*r)
annot_text_pos = (c[0]+3,c[1]+5*(-1)**i*r)
ax.annotate(label,
xy= annot_array_end,
xytext=annot_text_pos,
color='b',
fontproperties=font,
arrowprops=dict(fc='b', shrink=.005)
)
plt.show()
Just an observation for other people finding this thread. Not everything has to be done in Matplotlib.
It might well be easier to use a drawing package, with your network chart saved as a PDF (or PNG) in the background...
Is there an easy way to draw a zigzag or wavy line in matplotlib?
I'm aware of the different line styles (http://matplotlib.org/examples/lines_bars_and_markers/line_styles_reference.html), and I'm of course aware that instead of plotting
plt.figure(); plt.plot(n.linspace(0.7,1.42,100),[0.7]*100)
I could plot
plt.figure(); plt.plot(n.linspace(0.7,1.42,100),[0.69,0.71]*50)
for a zigzag-line, but I was wondering whether there was a more straightforward way?
Yes there is, but it comes with a little bit of fallout. The easiest way is to use the xkcd mode in matplotlib.
import numpy as np
import matplotlib.pyplot as plt
plt.xkcd()
plt.figure()
plt.plot(np.linspace(0.7,1.42,100),[0.7]*100)
plt.show()
Which gives you the following:
If you take a look at the code used to achieve this you will find that the xkcd function makes some changes to the rcParams dictionary. Most notably the entry rcParams['path.sketch'] = (scale, length, randomness) which is a path effect that is able to simulate a hand drawn look. The default parameters used by xkcd style are:
# explanation from the docstring of the xkcd function
scale = 1 # amplitude of the wiggle
length = 100 # length of the wiggle along the line
randomness = 2 # scale factor for shrinking and expanding the length
You can change the entries in the rcParams dictionary if you import it from the matplotlib package. In the following example I increased the randomness value from 2 to 100:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['path.sketch'] = (1, 100, 100)
plt.plot(np.linspace(0.7,1.42,100),[0.7]*100)
plt.show()
Which will result in the following plot:
As you can see, more jiggling and the font used for the ticks is still 'normal'. However, the style is also used to draw the axes and so far I have not found a way around that.
Two workarounds could be:
Work without drawn borders/ spines.
Plot spines and line independently (hard and annoying to automize).
Dig through the documentation of matplotlib and path styles and find out if there is a way to set path styles only for a subset of drawn lines.
Option 1 can be achieved like this:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['path.sketch'] = (10, 10, 100)
fig = plt.plot(np.linspace(0.7,1.42,100),[0.7]*100)
for pos, spine in fig[0].axes.spines.items():
spine.set_visible(False)
plt.show()
Which, in my opinion look quite ok. borders around plots are highly overrated anyways.
Edit: Less Chaos
To get an evenly waved line, set the randomness parameter to 1 and pick small values for amplitude and length:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['path.sketch'] = (3, 10, 1)
fig = plt.plot(np.linspace(0.7,1.42,100),[0.7]*100)
for pos, spine in fig[0].axes.spines.items():
spine.set_visible(False)
plt.show()
Bonus image: More Chaos
rcParams['path.sketch'] = (100, 1, 100)
You can apply the change in rcParams['path.sketch'] dictionary only to selected curves using with.
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
# prepare some fancy data
x = np.linspace(0,5,200)
y_0 = 10*x**0.2-x**1.5
y_1 = 20*np.sin(x)
y_2 = x**2
# prepare figure and axis
fig, ax = plt.subplots(nrows=1, ncols = 1, figsize = (5,3), dpi = 128)
# plot with some normal style
ax.plot(x, y_0, color = 'gray', ls='-.', lw = 2, label = 'normal style')
# now plot the wavy-like style!!!!
with mpl.rc_context({'path.sketch': (5, 15, 1)}):
ax.plot(x, y_1, color = 'blue', label = 'wavy style!')
# again plot with some different normal style
ax.plot(x, y_2, color = 'orange', ls = '-', lw = 3, label = 'again normal style')
ax.legend(loc='best') # turn on legend with automatic best location
plt.show()
I have two polygon patch plots with shading in grayscale, with each patch added to an axis. and I would like to add a colorbar underneath each subplot.
I'm using
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
poly=mpatches.Polygon(verts,color=rgb,ec='black') #create patch
ax.add_patch(poly) #add patch to axis
plt.set_cmap('gray') #grayscale colormap
#verts,rgb input created in above lines of code
The colorbars should be in grayscale and have range [0,1], with 0 being black, 0.5 marked, and 1 being white. im using subplot(121) and (122).
Thanks in advance.
To use colorbars you have to have some sort of ScalarMappable instance (this is used for imshow, scatter, etc.):
mappable = plt.cm.ScalarMappable(cmap='gray')
# the mappable usually contains an array of data, here we can
# use that to set the limits
mappable.set_array([0,1])
ax.colorbar(mappable)
I might be late here but now matplotlib has unique functionality to set colormap to gray as shown in this link.
plt.gray()
fig = plt.figure(figsize=(10, 10))
plt.subplots_adjust(left = 0, right = 1, top = 1, bottom = 0)
im = plt.imshow(output)
pos = fig.add_axes([0.93, 0.1, 0.02, 0.35]) # Set colorbar position in fig
fig.colorbar(im, cax=pos) # Create the colorbar
plt.savefig(os.path.join(args.output_path, image_name))
the line 1 will set your colormap to grayscale. the result is shown in the image.