Changing only the line properties inside the circle when using pie in matplotlib - matplotlib

When I am segmenting a circle with pie from matplotlib I would like to change the properties of the lines only inside the circle:
plt.rcParams['patch.edgecolor'] = 'lightgrey'
plt.rcParams['patch.linewidth'] = 1
Affect all the lines including the line of the circle itsef.

Step 1 - changing 'inner' lines
As usual it is a good idea to look at the matplotlib API documentation, where we find that pie plot provides a lot of arguments, one of which is wedgeprops
wedgeprops: [ None | dict of key value pairs ]
Dict of arguments passed to the wedge objects making the pie. For example, you can pass in wedgeprops = { ‘linewidth’ : 3 } to set the width of the wedge border lines equal to 3. For more details, look at the doc/arguments of the wedge object.
One of the arguments to Wedge is edgecolor, another is linewidth.
So in total you have to call
plt.pie([215, 130], colors=['b', 'r'],
wedgeprops = { 'linewidth' : 1 , 'edgecolor' : 'lightgrey'} )
However, since this also changes the outline of the pie diagram we need...
Step 2 - setting circonference circle
Now, in order to get a circle around the pie, or restore the initial linestyle for the circonference of the pie, we can set a new Circle patch with the desired properties on top of the pie.
The complete solution then looks something like this
import matplotlib.pyplot as plt
import matplotlib.patches
fig, ax = plt.subplots(figsize=(3,3))
ax.axis('equal')
slices, labels = ax.pie([186, 130, 85], colors=['b', 'r','y'],
wedgeprops = { 'linewidth' : 1 , 'edgecolor' : 'lightgrey'} )
# get the center and radius of the pie wedges
center = slices[0].center
r = slices[0].r
# create a new circle with the desired properties
circle = matplotlib.patches.Circle(center, r, fill=False, edgecolor="k", linewidth=2)
# add the circle to the axes
ax.add_patch(circle)
plt.show()

For a solution that works also with any pie chart, including exploded pie charts, e.g.
import numpy as np
import matplotlib as plt
data = [1, 2, 3, 1, 4, 2]
explode = [0.05] * len(data)
labels = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ'[:len(data)])
fig, ax = plt.subplots()
pie = ax.pie(data, labels=labels, explode=explode)
use one of the following options:
Option A, add lines for each wedge of the pie
pie = ax.pie(data, labels=labels, explode=explode)
for wedge in pie[0]:
ax.plot([wedge.center[0], wedge.r*np.cos(wedge.theta1*np.pi/180)+wedge.center[0]], [wedge.center[1], wedge.r*np.sin(wedge.theta1*np.pi/180)+wedge.center[1]], color='k')
ax.plot([wedge.center[0], wedge.r*np.cos(wedge.theta2*np.pi/180)+wedge.center[0]], [wedge.center[1], wedge.r*np.sin(wedge.theta2*np.pi/180)+wedge.center[1]], color='k')
fig.show()
Option B, add edges to the pie wedges then overwrite the radial edge with another color (e.g. white)
from matplotlib import patches
pie = ax.pie(data, labels=labels, explode=explode, wedgeprops=dict(ec='k')
for wedge in pie[0]:
arc = patches.Arc(wedge.center, 2*wedge.r, 2*wedge.r, 0, theta1=wedge.theta1, theta2=wedge.theta2, ec='w', lw=1.5)
ax.add_patch(arc)
fig.show()

Related

Matplotlib FuncAnimation color changing scatter plot

I am trying to create an animated scatter plot whereby the scatter points plot in order and change color over time, thus the newest scatter points always appear in the same color (in this case, red) while the older scatter points age to different colors using a color map.
The code works except for the newest scatter point in every frame of the animation, which appears as the 'oldest' color in the plot, rather than the newest. How can I get it to appear in the correct color?
My code is this:
import matplotlib.animation as animation
from matplotlib import cm
import matplotlib.pyplot as plt
%matplotlib notebook
brg = cm.get_cmap('hsv',500)
cmapz = brg(range(500))
x = [0]
y = [0]
def update_lines(num):
dx = x[-1]+np.random.random()
x.append(dx)
dy = np.random.random()
y.append(dy)
text.set_text("{:d}: [{:.0f},{:.0f}]".format(num, x[-1], y[-1]))
array = cmapz[:num]
graph.set_offsets(np.c_[x, y])
graph.set_color(array[::-1])
return graph,
fig,ax=plt.subplots(1,1,figsize=(8,5))
ax = plt.axes(xlim=(0,251),ylim=(-1,2))
graph = ax.scatter(x, y,c=cmapz[0])
text = fig.text(0, 1, "TEXT", va='top')
ani = animation.FuncAnimation(fig, update_lines, frames=499, interval=10, blit=False, repeat = False)
plt.show()

Utilise a slider to update the position of legend in Matplotlib

I am trying to make a slider that can adjust the x and y coordinates of the legend anchor, but this does not seem to be updating on the plot. I keep getting the message in console "No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument", each time the slider value is updated.
Here is the code, taken from this example in the matplotlib docs
from cProfile import label
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
# The parametrized function to be plotted
def f(t, amplitude, frequency):
return amplitude * np.sin(2 * np.pi * frequency * t)
t = np.linspace(0, 1, 1000)
# Define initial parameters
init_amplitude = 5
init_frequency = 3
# Create the figure and the line that we will manipulate
fig, ax = plt.subplots()
line, = ax.plot(t, f(t, init_amplitude, init_frequency), lw=2, label = "wave")
ax.set_xlabel('Time [s]')
# adjust the main plot to make room for the sliders
fig.subplots_adjust(left=0.25, bottom=0.25)
initx = 0.4
inity = 0.2
def l(x,y):
return (x,y)
legend = fig.legend(title = 'title', prop={'size': 8}, bbox_to_anchor = l(initx,inity))
legend.remove( )
# Make a horizontal slider to control the frequency.
axfreq = fig.add_axes([0.25, 0.1, 0.3, 0.3])
freq_slider = Slider(
ax=axfreq,
label='Frequency [Hz]',
valmin=0.1,
valmax=30,
valinit=init_frequency,
)
# Make a vertically oriented slider to control the amplitude
axamp = fig.add_axes([0.1, 0.25, 0.0225, 0.63])
amp_slider = Slider(
ax=axamp,
label="Amplitude",
valmin=0,
valmax=10,
valinit=init_amplitude,
orientation="vertical"
)
# The function to be called anytime a slider's value changes
def update(val):
legend = plt.legend(title = '$J_{xx}$', prop={'size': 8}, bbox_to_anchor= l(amp_slider.val, freq_slider.val))
legend.remove()
#line.set_ydata(f(t, amp_slider.val, freq_slider.val))
fig.canvas.draw_idle()
# register the update function with each slider
freq_slider.on_changed(update)
amp_slider.on_changed(update)
# Create a `matplotlib.widgets.Button` to reset the sliders to initial values.
resetax = fig.add_axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, 'Reset', hovercolor='0.975')
def reset(event):
freq_slider.reset()
amp_slider.reset()
button.on_clicked(reset)
plt.show()
Is it even possible to update other matplotlib plot parameters like xticks/yticks or xlim/ylim with a slider, rather than the actual plotted data? I am asking so that I can speed up the graphing process, as I tend to lose a lot of time just getting the right plot parameters whilst making plots presentable, and would like to automate this in some way.

Matplotlib Legend with Different Number and Color of Markers per Handle

Given the following:
import pandas as pd
import matplotlib.pyplot as plt
d=pd.DataFrame({'category':['a','a','a','b','b','b'],
'year':[1,2,1,2,1,2],
'x':[2,4,5,1,2,3],
'y':[1,2,3,2,4,6],
'clr':['grey','green','grey','blue','grey','orange']})
d
category clr x y year
0 a grey 2 1 1
1 a green 4 2 2
2 a grey 5 3 1
3 b blue 1 2 2
4 b grey 2 4 1
5 b orange 3 6 2
and
for i in np.arange(len(d)):
plt.plot(d.x[i],d.y[i],marker='o',linestyle='none',markerfacecolor=d.clr[i],
markeredgecolor='none',markersize=15)
#clean up axes
plt.tick_params(axis='x',which='both',bottom='off',top='off',color='none',labelcolor='none')
plt.tick_params(axis='y',which='both',left='off',right='off',color='none',labelcolor='none')
lgnd=plt.legend(['Year 1','Year 2'],
numpoints=1,
loc=0,
ncol=1,
fontsize=10,
frameon=False)
lgnd.legendHandles[0]._legmarker.set_markersize(15)
lgnd.legendHandles[1]._legmarker.set_markersize(15)
I'd like for the legend to have one grey dot for the Year 1 marker (as it currently does) but for the Year 2 markers, one dot for each distinct color (in this case, an orange, blue, and green dot all on the same line order doesn't matter at this time, in a row).
Like this:
I've tried the following, but to no avail:
lgnd.legendHandles[1]._legmarker.set_numpoints(len(d.clr.unique()))
lgnd.legendHandles[1]._legmarker.set_markeredgecolor(d.clr)
Thanks in advance!
I had fun figuring out a solution to your problem (and learning a few new tricks in the process). Essentially, you could make your own legend handler object to map all colours to a year. Making a custom legend handler can be done by making any object that has function legend_artist(self, legend, orig_handle, fontsize, handlebox). The detail of why this works can be found in the "Implementing custom handler" section of this page. I commented all the explanation in the code since there is too much to explain by words without codes to demonstrate.
Example code:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import pdb
import matplotlib.patches as mpatches
class MyLegendHandler(object):
def __init__(self,color):
self.color = color
def legend_artist(self, legend, orig_handle, fontsize, handlebox):
x0, y0 = handlebox.xdescent, handlebox.ydescent #offset of the lower left corner
width, height = handlebox.width, handlebox.height #width, height bound box of legend, for now, it is the dimension of each circle legend
#NOTE: to be practicle, let's just set radius = height as if width != height, it's an ellipse
#NOTE: these will latter on be changed internally to accomdate adding text
handlebox.width += len(self.color)*height # reset width of handlebox to accomodate multiple legends
for i in range(len(self.color)): #loop through all colors
#for each color, draw a circle of that color
#NOTE: play around here to align the legends right rather than left :)
center = [0.5*(i + 1) * width - 0.5*x0, 0.5 * height - 0.5 * y0]
patch = mpatches.Ellipse(center, height, height, facecolor=self.color[i],
edgecolor=None, hatch=None, transform=handlebox.get_transform())
handlebox.add_artist(patch)
return patch
###################################
d=pd.DataFrame({'category':['a','a','a','b','b','b'],
'year':[1,2,1,2,1,2],
'x':[2,4,5,1,2,3],
'y':[1,2,3,2,4,6],
'clr':['grey','green','grey','blue','grey','orange']})
unique_year_elements = []
years_seen = []
tmp = None
my_map = {}
for i in np.arange(len(d)):
tmp, = plt.plot(d.x[i],d.y[i],marker='o',linestyle='none',markerfacecolor=d.clr[i],
markeredgecolor='none',markersize=15)
#collect the plot elements that are of unique years-- 1 year might have several plot element, we only need 1
if not (d.year[i] in years_seen):
years_seen.append(d.year[i])
unique_year_elements.append(tmp)
#build handler_map for plt.legend to map elements to its legend handler object
for i in np.arange(len(years_seen)):
color_list = d.loc[d['year'] == years_seen[i]].clr.unique().tolist()
#pdb.set_trace()
my_map[unique_year_elements[i]] = MyLegendHandler(color_list)
#creating the legend object
plt.legend( unique_year_elements, ["Year "+str(y) for y in years_seen],
handler_map=my_map)
#clean up axes
plt.tick_params(axis='x',which='both',bottom='off',top='off',color='none',labelcolor='none')
plt.tick_params(axis='y',which='both',left='off',right='off',color='none',labelcolor='none')
plt.show()
Sample output:
Another approach, which worked for me, was to plot circles (ellipses - see why here) and text:
import matplotlib.patches as mpatches
#Set ellipse dimension coordinates
xmax_el=xmax/30
ymax_el=ymax/28
#Set ellipse y-location coordinates
yloc1=max(ind)+2.5
yloc2=max(ind)+1.75
#Create first circle in grey as just one grey circle is needed:
circlex=mpatches.Ellipse((pmax-.2*pmax,yloc1), xmax_el, ymax_el ,alpha=0.5,clip_on=False\
,edgecolor='grey',linewidth=2,facecolor='none')
#Start a list of patches (circles), with the grey one being the first:
patches=[circlex]
clrs=['g','r','b']
#Populate a list of circles, one for each unique color for patch names
circles=[]
for i in np.arange(len(clrs)):
circles.append('circle'+str(i))
#This list is for multiplying by the x-position dimension to space out the colored bubbles:
clrnum=np.arange(len(clrs))
#Reverse the order of colors so they plot in order on the chart (if clrs was based on real data that is being plotted)
clrs2=clrs[::-1]
#Iterate through the color, circle, and circle number lists, create patches, and plot.
for i,j,k in zip(clrs2,circles,clrnum):
j=mpatches.Ellipse((pmax-(.2+k*0.05)*pmax,yloc2),xmax_el,ymax_el,alpha=0.5,clip_on=False,edgecolor=i,linewidth=2,facecolor='none')
patches.append(j)
for i in patches:
ax.add_artist(i)
#Add text:
ax.text(pmax-.15*pmax,yloc1,'2015 Plan Offering',color='grey',ha='left',va='center')
ax.text(pmax-.15*pmax,yloc2,'2016 Plan Offering',color='grey',ha='left',va='center')
Result:

How to hide contour lines / data from a specific area on Basemap

I am working some meteorological data to plot contour lines on a basemap. The full working example code I have done earlier is here How to remove/omit smaller contour lines using matplotlib. All works fine and I don’t complain with the contour plot. However there is a special case that I have to hide all contour lines over a specific region (irregular lat & lon) on a Basemap.
The only possible solution I can think of is to draw a ploygon lines over a desired region and fill with the color of same as Basemap. After lot of search I found this link How to draw rectangles on a Basemap (code below)
from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
def draw_screen_poly( lats, lons, m):
x, y = m( lons, lats )
xy = zip(x,y)
poly = Polygon( xy, facecolor='red', alpha=0.4 )
plt.gca().add_patch(poly)
lats = [ -30, 30, 30, -30 ]
lons = [ -50, -50, 50, 50 ]
m = Basemap(projection='sinu',lon_0=0)
m.drawcoastlines()
m.drawmapboundary()
draw_screen_poly( lats, lons, m )
plt.show()
It seems to work partially. However, I want to draw a region which is irregular.
Any solution is appreciated.
Edit: 1
I have understood where the problem is. It seems that any colour (facecolor) filled within the polygon region does not make it hide anything below. Always it is transparent only, irrespective of alpha value used or not. To illustrate the problem, I have cropped the image which has all three regions ie. contour, basemap region and polygon region. Polygon region is filled with red colour but as you can see, the contour lines are always visible. The particular line I have used in the above code is :-
poly = Polygon(xy, facecolor='red', edgecolor='b')
Therefore the problem is not with the code above. It seem the problem with the polygon fill. But still no solution for this issue. The resulting image (cropped image) is below (See my 2nd edit below the attached image):-
Edit 2:
Taking clue from this http://matplotlib.1069221.n5.nabble.com/Clipping-a-plot-inside-a-polygon-td41950.html which has the similar requirement of mine, I am able to remove some the data. However, the removed data is only from outside of polygon region instead of within. Here is the code I have taken clue from:-
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import RegularPolygon
data = np.arange(100).reshape(10, 10)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.contourf(data)
poly = RegularPolygon([ 0.5, 0.5], 6, 0.4, fc='none',
ec='k', transform=ax.transAxes)
for artist in ax.get_children():
artist.set_clip_path(poly)
Now my question is that what command is used for removing the data within the polygon region?
Didn't noticed there was a claim on this so I might just give the solution already proposed here. You can tinker with the zorder to hide stuff behind your polygon:
import matplotlib
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
matplotlib.rcParams['xtick.direction'] = 'out'
matplotlib.rcParams['ytick.direction'] = 'out'
delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
# difference of Gaussians
Z = 10.0 * (Z2 - Z1)
# Create a simple contour plot with labels using default colors. The
# inline argument to clabel will control whether the labels are draw
# over the line segments of the contour, removing the lines beneath
# the label
fig = plt.figure()
ax = fig.add_subplot(111)
CS = plt.contour(X, Y, Z,zorder=3)
plt.clabel(CS, inline=1, fontsize=10)
plt.title('Simplest default with labels')
rect1 = matplotlib.patches.Rectangle((0,0), 2, 1, color='white',zorder=5)
ax.add_patch(rect1)
plt.show()
, the result is:

matplotlib shared row label (not y label) in plot containing subplots

I have a trellis-like plot I am trying to produce in matplotlib. Here is a sketch of what I'm going for:
One thing I am having trouble with is getting a shared row label for each row. I.e. in my plot, I have four rows for four different sets of experiments, so I want row labels "1 source node, 2 source nodes, 4 source nodes and 8 source nodes".
Note that I am not referring to the y axis label, which is being used to label the dependent variable. The dependent variable is the same in all subplots, but the row labels I am after are to describe the four categories of experiments conducted, one for each row.
At the moment, I'm generating the plot with:
fig, axes = plt.subplots(4, 5, sharey=True)
While I've found plenty of information on sharing the y-axis label, I haven't found anything on adding a single shared row label.
As far as I know there is no ytitle or something. You can use text to show some text. The x and y are in data-coordinates. ha and va are horizontal and vertical alignment, respectively.
import numpy
import matplotlib
import matplotlib.pyplot as plt
n_rows = 4
n_cols = 5
fig, axes = plt.subplots(n_rows, n_cols, sharey = True)
axes[0][0].set_ylim(0,10)
for i in range(n_cols):
axes[0][i].text(x = 0.5, y = 12, s = "column label", ha = "center")
axes[n_rows-1][i].set_xlabel("xlabel")
for i in range(n_rows):
axes[i][0].text(x = -0.8, y = 5, s = "row label", rotation = 90, va = "center")
axes[i][0].set_ylabel("ylabel")
plt.show()
You could give titles to subplots on the top row like Robbert suggested
fig, axes = plt.subplots(4,3)
for i, ax in enumerate(axes[0,:]):
ax.set_title('col%i'%i)