Adding a circle on specific date in a matplotlib plot - python

I have a matpltolib plot made using this code:
ax.plot(df_c.index, y1, color='b')
Here df_c.index is:
DatetimeIndex(['2019-10-31', '2019-11-01', '2019-11-02', '2019-11-03',
'2019-11-04', '2019-11-05', '2019-11-06', '2019-11-07',
'2019-11-08', '2019-11-09',
...
'2020-04-04', '2020-04-05', '2020-04-06', '2020-04-07',
'2020-04-08', '2020-04-09', '2020-04-10', '2020-04-11',
'2020-04-12', '2020-04-13'],
dtype='datetime64[ns]', length=166, freq=None)
The above code makes a lineplot.
I want to add a circle on this date '2020-04-12' with a value of 100. How do I do that? I tried:
ax.plot(datetime.date(2020, 04, 12), 100, 'bo')
but it does not work. How can I fix it?

I'm not entirely certain where you want to draw your circle, but I present here three different circle positions, and a simpler fourth alternative just for highlighting a specific date. A demo image is shown at the bottom. First, let's just plot some data:
import matplotlib.pyplot as plt
dates = ['2019-10-31', '2019-11-01', '2019-11-02', '2019-11-03']
y = [i*i for i in range(len(dates))] # some random y values
# An arbitrary date value to encircle
encircled_date = dates[1]
val_of_encircled_date = y[1]
# Plot the graph
fig, ax = plt.subplots()
ax.plot_date(dates,y,'-')
bottom, top = plt.ylim() # Later, we'll need the min value of the y-axis for correct positioning of circle
Now, if you just want a circle at the graph, as it passes through your specific date, the simplest and (imho) best approach is to simply replot that specific value, but with markerstyle='o'. Adjust marker size, line width and color to your preferences:
# Plot a circle around specific graph value
ax.plot_date(encircled_date, val_of_encircled_date,
'og', # marker style 'o', color 'g'
fillstyle='none', # circle is not filled (with color)
ms=10.0) # size of marker/circle
Then, if you instead wanted a circle around the date-tick, on the x-axis for your specific date, it is a little more tricky depending on what details you need to encircle. Of course, you could use the approach above to get a small circle around the tick only, but I'll show a more advanced approach based on another SO-question:
# Plot a circle around the 'tick' of specific date on the x-axis
circle1 = plt.Circle((encircled_date, bottom), # position
1.0 / len(dates), # radius
color='r',
clip_on=False, # allow drawing outside of axes
fill=False)
ax.add_artist(circle1)
The above solution only encircles the tick, and not the date label itself. We may micro adjust the circle to fit the date-label inside, by tuning two offset parameters,
# Plot a circle around the specific date's label on the x-axis
pos_offset = 0.5
len_offset = 0.4
circle2 = plt.Circle((encircled_date, bottom-pos_offset), # position
(1.0+len_offset) / len(dates), # radius
color='purple',
clip_on=False, # allow drawing outside of axis
fill=False)
ax.add_artist(circle2)
However, this tuning may be a tedious task. If your objective is only to emphasize this particular date, it may be better to simply reconfigure the x-label. You may for instance change the color of the label like this,
ax.get_xticklabels()[2].set_color("red")
ax.get_xticklabels()[2].set_weight("bold")
The four different approaches are shown in the image below. I hope this helps.
One final remark: When you get a densely populated x-axis of dates, it might be worthwhile looking into more advanced formatting of date-labels which you can read all about in the official documentation. For instance, they show how to neatly rotate the labels to fit them closer together without overlapping.

Related

Get automatically coordinates of subplots in order to set them for automatic positioning of legend

I tried in a first time to set manually the location for the main legend of a main plot produced by Getdist tool.
The plot below represents the 1/2 sigma confidence levels coming from a covariance matrix with joint distributions. It is produced by Getdist tool.
The main routine that generates this plot is :
# g.settings
g = plots.get_subplot_plotter()
g.settings.figure_legend_frame = True
g.settings.legend_fontsize = 21
g.triangle_plot([matrix1, matrix2],
names,
filled = True,
contour_colors = ['darkblue','red'],
line_args = [{'lw':2, 'color':'darkblue'},
{'lw':2, 'color':'red'}]
)
g.add_legend(['Opt. Flat. No Gamma. - cross - standard situation - Criterion taking into accound a = 200',\
'Pess. Flat. No Gamma. - cross - standard situation - Criterion taking into account a = 300' ],\
bbox_to_anchor = [1.5, 8.5])
The value 1.5 seems to correspond to the x-coordinate (width) 8.5 corresponds to the y-coordinate of legend (height).
Now, I would like to automatically do this process instead of set manual at each time the position of the legend.
I want the top right of the legend to be positioned at the top border of the first left upper box (just at the level of top line border below the "1sigma ± 0.0012" title).
I would like also the legend to be pushed to the right of the figure (up to the right border for the right lower box of the figure: identified by sigma8 "1sigma ± 0.001" title ; Caution: I want it located before the 1.0 and 0.0 xticks, just at the x-coordinate of right line border).
Here what I tried to get the global coordinates (the entire plot) of the top border for this left upper box :
# First, get y coordinates of top border for first Likelihood
box1 = g.subplots[0,0]
box1_coords = box1._position.bounds
print('box1_coords = ', box1_coords)
and I get at the execution the following values :
box1_coords = (0.125, 0.7860975609756098, 0.09451219512195125, 0.09390243902439022)
As you can see, these values seem to be normalized, so I don't know how to handle if I want to insert these values into :
bbox_to_anchor = [box1_coords[0], box1_coords[1]]
This line of code produces a bad position for legend, as expected.
So, how can I manage to automatically assign the good values for bbox_to_anchor to get what I want (y-coordinate at level of top border of left upper box identified by the "1sigma ± 0.0012" title) and pushed on the right side up to the right border of right lower box (x-coordinate identified by sigma8 with "1sigma ± 0.001" title)?
Update 1
I tried to adapt them to my case, but issue still occurs. Here what I have done:
# g.settings
g = plots.get_subplot_plotter()
# get the max y position of the top left axis
top_left_plot = g.subplots[0,0].axes.get_position().ymax
# get the max x position of the bottom right axis
# it is -1 to reference the last plot
bottom_right_plot = g.subplots[-1,-1].axes.get_position().xmax
I don't know why the values of top_left_plot and bottom_right_plot are not the good ones.
I think that subplots[0,0] (for top y-coordinate of legend) refers to the top left subplot and subplots[-1,-1] to the bottom right subplot (for right x-coordinate of legend) but considering this, it doesn't work.
For example :
# g.settings
g = plots.get_subplot_plotter()
# Call triplot
g.triangle_plot([matrix1, matrix2],
names,
filled = True,
legend_labels = [],
contour_colors = ['darkblue','red'],
line_args = [{'lw':2, 'color':'darkblue'},
{'lw':2, 'color':'red'}])
g.add_legend(['Opt. Flat. No Gamma. - cross - standard situation - Criterion taking into accound a = 200',
'Pess. Flat. No Gamma. - cross - standard situation - Criterion taking into account a = 300'],
legend_loc='upper right',
bbox_to_anchor=(bottom_right_plot, top_left_plot)
)
I get :
legend_coords y_max, x_max 0.88 0.9000000000000001
I can't understand why these values (seems to be comprised between 0.0 and 1.0) are not taken into account with g.add_legend.
With #mullinscr's solution, I get the following figure :
If I take for the coordinates of legend position by forcing :
top_left_plot = 8.3
bottom_right_plot = 1.0
This looks like to the first figure of this post. But these 2 values are not comprised between 0.0 and 1.0 like it should.
Update 2
#mullinscr, thanks, I have followed your update and always get an issue. If I apply the same code snippet directly in my script, i.e :
g.add_legend(['An example legend - item 1'],
legend_loc='upper right', # we want to specify the location of this point
bbox_to_anchor=(bottom_right_plot, top_left_plot),
bbox_transform=plt.gcf().transFigure, # this is the x and y co-ords we extracted above
borderaxespad=0, # this means there is no padding around the legend
edgecolor='black')
Then I get the following figure :
As you can see, the coordinates are not really what is really expected : a slight shift on x-coordinate and y-coordinate is present.
If I apply your code snippet for my legend text, I get:
I give you the link of my entire script, this will be easier maybe for you to see an error compared what is expected:
My entire Python script
Here's my answer, it's the same as #scleronomic's answer, but I'll point out some of the things that tripped me up when figuring this out.
Below is my code to reproduce your desired positioning, I've tried to create the same subplot layout to you, but through matplotlib not getdist -- same result though.
As you discovered, the trick lies in extracting the position data of the first and last axes (top-left and lower-right), to reference from. The bounds method that you used returns: the x0, y0, width and height of the axes (see the docs). However what we want is the maximum x and y, so that our legend corner is in the top right. This can be achieved by using the xmax and ymax method:
axes.flatten()[-1].get_position().xmax
axes.flatten()[0].get_position().ymax
Once we have these variables they can be passed into the bbox_to_anchor parameter of the add_legend() function, as you did. However, if we use loc='upper right' too, it tells matplotlib that we want the upper right of the legend to be pinned to this top right corner. Finally, we need to set borderaxespad=0 otherwise the legend won't sit exactly where we want it to due to default padding.
Please see my example code below, as well as the resulting picture. Note that I left the top-right plot in so you can see that it lines up correctly.
Also, note that as #scleronomic says, calls to plt.tight_layout() etc will mess this positioning up.
import matplotlib.pyplot as plt
# code to layout subplots as in your example:
# --------------------------------------------
g, axes = plt.subplots(nrows=7, ncols=7,figsize=(10,10))
unwanted = [1,2,3,4,5,9,10,11,12,13,17,
18,19,20,25,26,27,33,34,41]
for ax in axes.flatten():
ax.plot([1,2], [1,2])
ax.set_yticks([])
ax.set_xticks([])
for n, ax in enumerate(axes.flatten()):
if n in unwanted:
ax.remove()
# Code to answer your question:
# ------------------------------
# get the max y position of the top left axis
top_left_plot = axes.flatten()[0].get_position().ymax
# get the max x position of the bottom right axis
# it is -1 to reference the last plot
bottom_right_plot = axes.flatten()[-1].get_position().xmax
# I'm using the matplotlib so it is g.legend() not g.add_legend
# but g.add_legend() should work the same as it is a wrapper of th ematplotlib func
g.legend(['Opt. Flat. No Gamma. - cross - standard situation - Criterion taking into accound a = 200',
'Pess. Flat. No Gamma. - cross - standard situation - Criterion taking into account a = 300'],
loc='upper right', # we want to specify the location of this point
bbox_to_anchor=(bottom_right_plot, top_left_plot), # this is the x and y co-ords we extracted above
borderaxespad=0, # this means there is no padding around the legend
edgecolor='black') # I set it black for this example
plt.show()
Update
After #youpilat13's comments, I investigated some more and installed getdist to try and recreate with that tool. Initially I got the same results, but found the trick is, unlike if you were making this in matplotlib, you have to transform the legend's coordinates to figure coordinates. This can be achieved with the following in the g.add_legend() call:
bbox_transform=plt.gcf().transFigure
Here is a complete example:
import getdist
from getdist import plots, MCSamples
from getdist.gaussian_mixtures import GaussianND
covariance = [[0.001**2, 0.0006*0.05, 0], [0.0006*0.05, 0.05**2, 0.2**2], [0, 0.2**2, 2**2]]
mean = [0.02, 1, -2]
gauss=GaussianND(mean, covariance)
g = plots.get_subplot_plotter(subplot_size=3)
g.triangle_plot(gauss,filled=True)
top_left_plot = g.subplots.flatten()[0].get_position().ymax
bottom_right_plot = g.subplots.flatten()[-1].get_position().xmax
g.add_legend(['An example legend - item 1'],
legend_loc='upper right', # we want to specify the location of this point
bbox_to_anchor=(bottom_right_plot, top_left_plot),
bbox_transform=plt.gcf().transFigure, # this is the x and y co-ords we extracted above
borderaxespad=0, # this means there is no padding around the legend
edgecolor='black')
And the resulting image:
It basically works as you described. The bboxes (xmin, ymin, width, height) of the axes are given in fractions of the figure and plt.legend() uses the same format so the two are compatible. By setting the upper right corner of the legend to the corner defined by the outer most axes you get the clean layout and don't have to worry about the exact size of the legend.
import matplotlib.pyplot as plt
n = 4
# Create the subplot grid
# Alternative: fig, ax = plt.subplots(n, n); ax[i, j].remove() for j > i
fig = plt.figure()
gs = fig.add_gridspec(nrows=n, ncols=n)
ax = np.zeros((n, n), dtype=object)
for i in range(n):
for j in range(n):
if j <= i:
ax[i, j] = fig.add_subplot(gs[i, j])
# add this to make the position of the legend easier to spot
ax[0, -1] = fig.add_subplot(gs[0, -1])
# Plot some dummy data
ax[0, 0].plot(range(10), 'b-o', label='Dummy Label 4x4')
# Set the legend
y_max = ax[0][0].get_position().ymax
x_max = ax[-1][-1].get_position().xmax
fig.legend(loc='upper right', bbox_to_anchor=(x_max, y_max),
borderaxespad=0)
plt.show()
Some pitfalls could be using the Constrained Layout
or using bbox_inches='tight' when saving the file as both screw up the position of the legend in unexpected ways.
For some more examples of legend placement I found this collection
very helpful.

aligning horizontal label with grid lines in a graph [duplicate]

I would like to move some ticks' labels horizontally along the x-axis, without moving the corresponding ticks.
More specifically, when rotating labels with plt.setp, the centers of the labels' text stay aligned with the ticks. I would like to shift those labels to the right, so that the near ends of the labels get aligned instead as suggested on the image below.
I am aware of this post and this one, however the answers are interesting kludges rather than strict answers to the question.
my code:
import matplotlib.pyplot as plt
import numpy as np
import datetime
# my fake data
dates = np.array([datetime.datetime(2000,1,1) + datetime.timedelta(days=i) for i in range(365*5)])
data = np.sin(np.arange(365*5)/365.0*2*np.pi - 0.25*np.pi) + np.random.rand(365*5) /3
# creates fig with 2 subplots
fig = plt.figure(figsize=(10.0, 6.0))
ax = plt.subplot2grid((2,1), (0, 0))
ax2 = plt.subplot2grid((2,1), (1, 0))
## plot dates
ax2.plot_date( dates, data )
# rotates labels
plt.setp( ax2.xaxis.get_majorticklabels(), rotation=-45 )
# try to shift labels to the right
ax2.xaxis.get_majorticklabels()[2].set_y(-.1)
ax2.xaxis.get_majorticklabels()[2].set_x(10**99)
plt.show()
Strangely enough, set_y behaves as expected, but even if I set x to a fantasillion, the labels would not move by one iota.
(The use of plot_date may introduce additional confusion, but the same actually happens with plot.)
First of all, let's use a mcve to show the problem.
import numpy as np
import datetime
import matplotlib.pyplot as plt
plt.rcParams["date.autoformatter.month"] = "%b %Y"
# my fake data
dates = np.array([datetime.datetime(2000,1,1) + datetime.timedelta(days=i) for i in range(365)])
data = np.sin(np.arange(365)/365.0*2*np.pi - 0.25*np.pi) + np.random.rand(365) /3
# creates fig with 2 subplots
fig, ax = plt.subplots(figsize=(6,2))
## plot dates
ax.plot_date( dates, data )
# rotates labels
plt.setp( ax.xaxis.get_majorticklabels(), rotation=-45 )
plt.tight_layout()
plt.show()
Now as other anwers pointed out already, you may use horizontal alignment of the text.
# rotates labels and aligns them horizontally to left
plt.setp( ax.xaxis.get_majorticklabels(), rotation=-45, ha="left" )
You may use the rotation_mode argument to let the rotation happen about the top left point of the text, giving a slightly nicer result in this case.
# rotates labels and aligns them horizontally to left
plt.setp( ax.xaxis.get_majorticklabels(), rotation=-45, ha="left", rotation_mode="anchor")
In case those options are not fine grained enough, i.e. you want to position the labels more accurately, e.g. shifting it to the side by some points, you may use a transform. The following would offset the label by 5 points in horizontal direction, using a matplotlib.transforms.ScaledTranslation.
import matplotlib.transforms
plt.setp( ax.xaxis.get_majorticklabels(), rotation=-45)
# Create offset transform by 5 points in x direction
dx = 5/72.; dy = 0/72.
offset = matplotlib.transforms.ScaledTranslation(dx, dy, fig.dpi_scale_trans)
# apply offset transform to all x ticklabels.
for label in ax.xaxis.get_majorticklabels():
label.set_transform(label.get_transform() + offset)
The advantage of this, compared to e.g. the solution provided by #explorerDude is that the offset is independent on the data in the graph, such that it is generally applicable to any plot and would look the same for a given fontsize.
Instead of
ax2.xaxis.get_majorticklabels()[2].set_y(-.1)
ax2.xaxis.get_majorticklabels()[2].set_x(10**99)
use the set_horizontalalignment() for each tick on the axis:
for tick in ax2.xaxis.get_majorticklabels():
tick.set_horizontalalignment("left")
resulting in:
I found a way to shift the tick labels of the x-axis by an arbitrary and exact amount, but this way runs dangerously close to the steep and slippery cliffs towering above the sea of madness. So only the very brave or desperate should read on...
That being said, the problem is that the x position of the labels are set when the drawing is rendered (I have not looked into that part of the code, but that is my understanding). So everything you do with set_x() is overridden later. However, there is a way around that: you can monkey patch set_x for certain ticks so that the labels are not drawn where the renderer wants to draw them:
import types
SHIFT = 10. # Data coordinates
for label in ax2.xaxis.get_majorticklabels():
label.customShiftValue = SHIFT
label.set_x = types.MethodType( lambda self, x: matplotlib.text.Text.set_x(self, x-self.customShiftValue ),
label, matplotlib.text.Text )
You can do this selectively only for the labels you want to shift and you can of course also use a different shift for every label.
If anybody knows how to do this on a lower madness level, I would be very interested...
Another way of doing a horizontal alignment:
plt.xticks(ha='left')

How can I adjust Axes sizes in matplotlib polar plots? [duplicate]

I am starting to play around with creating polar plots in Matplotlib that do NOT encompass an entire circle - i.e. a "wedge" plot - by setting the thetamin and thetamax properties. This is something I was waiting for for a long time, and I am glad they have it done :)
However, I have noticed that the figure location inside the axes seem to change in a strange manner when using this feature; depending on the wedge angular aperture, it can be difficult to fine tune the figure so it looks nice.
Here's an example:
import numpy as np
import matplotlib.pyplot as plt
# get 4 polar axes in a row
fig, axes = plt.subplots(2, 2, subplot_kw={'projection': 'polar'},
figsize=(8, 8))
# set facecolor to better display the boundaries
# (as suggested by ImportanceOfBeingErnest)
fig.set_facecolor('paleturquoise')
for i, theta_max in enumerate([2*np.pi, np.pi, 2*np.pi/3, np.pi/3]):
# define theta vector with varying end point and some data to plot
theta = np.linspace(0, theta_max, 181)
data = (1/6)*np.abs(np.sin(3*theta)/np.sin(theta/2))
# set 'thetamin' and 'thetamax' according to data
axes[i//2, i%2].set_thetamin(0)
axes[i//2, i%2].set_thetamax(theta_max*180/np.pi)
# actually plot the data, fine tune radius limits and add labels
axes[i//2, i%2].plot(theta, data)
axes[i//2, i%2].set_ylim([0, 1])
axes[i//2, i%2].set_xlabel('Magnitude', fontsize=15)
axes[i//2, i%2].set_ylabel('Angles', fontsize=15)
fig.set_tight_layout(True)
#fig.savefig('fig.png', facecolor='skyblue')
The labels are in awkward locations and over the tick labels, but can be moved closer or further away from the axes by adding an extra labelpad parameter to set_xlabel, set_ylabel commands, so it's not a big issue.
Unfortunately, I have the impression that the plot is adjusted to fit inside the existing axes dimensions, which in turn lead to a very awkward white space above and below the half circle plot (which of course is the one I need to use).
It sounds like something that should be reasonably easy to get rid of - I mean, the wedge plots are doing it automatically - but I can't seem to figure it out how to do it for the half circle. Can anyone shed a light on this?
EDIT: Apologies, my question was not very clear; I want to create a half circle polar plot, but it seems that using set_thetamin() you end up with large amounts of white space around the image (especially above and below) which I would rather have removed, if possible.
It's the kind of stuff that normally tight_layout() takes care of, but it doesn't seem to be doing the trick here. I tried manually changing the figure window size after plotting, but the white space simply scales with the changes. Below is a minimum working example; I can get the xlabel closer to the image if I want to, but saved image file still contains tons of white space around it.
Does anyone knows how to remove this white space?
import numpy as np
import matplotlib.pyplot as plt
# get a half circle polar plot
fig1, ax1 = plt.subplots(1, 1, subplot_kw={'projection': 'polar'})
# set facecolor to better display the boundaries
# (as suggested by ImportanceOfBeingErnest)
fig1.set_facecolor('skyblue')
theta_min = 0
theta_max = np.pi
theta = np.linspace(theta_min, theta_max, 181)
data = (1/6)*np.abs(np.sin(3*theta)/np.sin(theta/2))
# set 'thetamin' and 'thetamax' according to data
ax1.set_thetamin(0)
ax1.set_thetamax(theta_max*180/np.pi)
# actually plot the data, fine tune radius limits and add labels
ax1.plot(theta, data)
ax1.set_ylim([0, 1])
ax1.set_xlabel('Magnitude', fontsize=15)
ax1.set_ylabel('Angles', fontsize=15)
fig1.set_tight_layout(True)
#fig1.savefig('fig1.png', facecolor='skyblue')
EDIT 2: Added background color to figures to better show the boundaries, as suggested in ImportanteOfBeingErnest's answer.
It seems the wedge of the "truncated" polar axes is placed such that it sits in the middle of the original axes. There seems so be some constructs called LockedBBox and _WedgeBbox in the game, which I have never seen before and do not fully understand. Those seem to be created at draw time, such that manipulating them from the outside seems somewhere between hard and impossible.
One hack can be to manipulate the original axes such that the resulting wedge turns up at the desired position. This is not really deterministic, but rather looking for some good values by trial and error.
The parameters to adjust in this case are the figure size (figsize), the padding of the labels (labelpad, as already pointed out in the question) and finally the axes' position (ax.set_position([left, bottom, width, height])).
The result could then look like
import numpy as np
import matplotlib.pyplot as plt
# get a half circle polar plot
fig1, ax1 = plt.subplots(1, 1, figsize=(6,3.4), subplot_kw={'projection': 'polar'})
theta_min = 1.e-9
theta_max = np.pi
theta = np.linspace(theta_min, theta_max, 181)
data = (1/6.)*np.abs(np.sin(3*theta)/np.sin(theta/2.))
# set 'thetamin' and 'thetamax' according to data
ax1.set_thetamin(0)
ax1.set_thetamax(theta_max*180./np.pi)
# actually plot the data, fine tune radius limits and add labels
ax1.plot(theta, data)
ax1.set_ylim([0, 1])
ax1.set_xlabel('Magnitude', fontsize=15, labelpad=-60)
ax1.set_ylabel('Angles', fontsize=15)
ax1.set_position( [0.1, -0.45, 0.8, 2])
plt.show()
Here I've set some color to the background of the figure to better see the boundary.

Misaligned bins in matplotlib stackplot

I am trying to make a stack plot where the bins don't seem to be aligning correctly with the data. What I have plotted is the proportion of something in a sphere as you go radially outward from the center. The error became visible to me in the rightmost section of this plot. The lighter blue should be a vertical column of one width. Instead the dark blue seems to slant into the lighter blue section.
What I believe is the problem is that the data are not evenly spaced. For example: at a radius of 300 I might have a certain proportion value. Then at a radius of 330 I might have another, then the next at 400.
I had thought that stackplot would be able to take care of this but it appears not. Is there a way for me to straighten up these columns of data?
Source Code:
def phaseProp(rad,phase):
#phaseLabel = np.array(['coe','en','fs','olv','maj','perov','ppv','ring','wad','per','wust','st'])
#print phaseLabel
rad = rad/1000.
phase = phase/100.
print phase[:,:]
#print phase[:,0]
fig, ax = plt.subplots(figsize = (15,10))
ax.stackplot(rad[:],phase[:,0],phase[:,1],phase[:,2],phase[:,3],phase[:,4], \
phase[:,5],phase[:,6],phase[:,7],phase[:,8], \
phase[:,9] ,phase[:,10],phase[:,11],phase[:,12], \
colors = ['gainsboro','gold','lightsage','darkorange','tomato','indianred',\
'darksage','sage','palevioletred','darkgrey','dodgerblue' ,'mediumblue' ,'darkblue' ])
plt.legend([mpatches.Patch(color='gainsboro'),
mpatches.Patch(color='gold'),
mpatches.Patch(color='lightsage'),
mpatches.Patch(color='darkorange'),
mpatches.Patch(color='tomato'),
mpatches.Patch(color='indianred'),
mpatches.Patch(color='darksage'),
mpatches.Patch(color='sage'),
mpatches.Patch(color='palevioletred'),
mpatches.Patch(color='darkgrey'),
mpatches.Patch(color='dodgerblue'),
mpatches.Patch(color='mediumblue'),
mpatches.Patch(color='darkblue')],
['coe','opx','ol','gt','pv','ppv','rw','wad','fp','st','h2o','iceIh','iceVII'],\
loc='upper center', bbox_to_anchor=(0.5, 1.127),fancybox=True, shadow=True, ncol=5,fontsize='20')
plt.ylabel(r'Phase Proportion',fontsize = 34)
plt.xlabel(r'Radius (km)',fontsize = 34)
plt.tick_params(axis='both', which='both', labelsize=32)
plt.xlim(rad[noc+1],rad[nr])
plt.ylim(0,1.0)
#ax.stackplot(rad,phase)
#plt.gca().invert_xaxis()
plt.show()
I've had a look at your problem and I think the problem lies with the fact that the last two points for the H20 line are (7100,0) and (7150,1) therefore it simply slopes up as you are seeing.
However it is very simple to add an additional point to give a square edge:
rad_amended = np.hstack((rad,rad[-1])) #extend the array by 1
rad_amended[-2] = rad[-2] +1 #alter the penultimate value
phase_amended = np.vstack((phase,phase[-1])) #extend the Y values
nr+=1 #extend the range of the x-axis
phaseProp(rad_amended,phase_amended)
This principle could be extended for the full dataset and give square edges to every Area, but I assume you are happy with the rest of the graph?

Autoscale a matplotlib Axes to make room for legend

I am plotting a 2D view of a spacecraft orbit using matplotlib. On this orbit, I identify and mark certain events, and then list these events and the corresponding dates in a legend. Before saving the figure to a file, I autozoom on my orbit plot, which causes the legend to be printed directly on top of my plot. What I would like to do is, after autoscaling, somehow find out the width of my legend, and then expand my xaxis to "make room" for the legend on the right side of the plot. Conceptually, something like this;
# ... code that generates my plot up here, then:
ax.autoscale_view()
leg = ax.get_legend()
leg_width = # Somehow get the width of legend in units that I can use to modify my axes
xlims = ax.get_xlim()
ax.set_xlim( [xlims[0], xlims[1] + leg_width] )
fig.savefig('myplot.ps',format='ps')
The main problem I'm having is that ax.set_xlim() takes "data" specific values, whereas leg.get_window_extent reports in window pixels (I think), and even that only after the canvas has been drawn, so I'm not sure how I can get the legend "width" in a way that I can use similar to above.
You can save the figure once to get the real legend location, and then use transData.inverted() to transform screen coordinate to data coordinate.
import pylab as pl
ax = pl.subplot(111)
pl.plot(pl.randn(1000), pl.randn(1000), label="ok")
leg = pl.legend()
pl.savefig("test.png") # save once to get the legend location
x,y,w,h = leg.get_window_extent().bounds
# transform from screen coordinate to screen coordinate
tmp1, tmp2 = ax.transData.inverted().transform([0, w])
print abs(tmp1-tmp2) # this is the with of legend in data coordinate
pl.savefig("test.png")

Categories