L-shaped Gridspec using matplotlib gs.update - python

This one is a quick and easy one for the matplotlib community. I was looking to plot an L-shaped gridspec layout, which I have done:
Ignoring a few layout issues I have for the moment, what I have is that the x-axis in the gs[0] plot (top left) shares the x-axis with the gs[2] plot (bottom left) and the gs[2] shares its y axis with the gs[3] plot. Now, what I was hoping to do was update the w-space and h-space to be tighter. So that the axes are almost touching, so perhaps wspace=0.02, hspace=0.02 or something similar.
I was also hoping that the bottom right hand plot was to be longer in the horizontal orientation, keeping the two left hand plots square in shape. Or as close to square as possible. If someone could run through all of the parameters I would be very appreciative. I can tinker then in my own time.

To change the spacings of the plot with grid spec:
gs = gridspec.GridSpec(2, 2,width_ratios=[1,1.5],height_ratios=[1,1])
This changes the relative size of plot gs[0] and gs[2] to gs1 and gs[3], whereas something like:
gs = gridspec.GridSpec(2, 2,width_ratios=[1,1],height_ratios=[1,2])
will change the relative sizes of plot gs[0] and gs1 to gs[2] and gs[3].
The following will tighten up the plots:
gs.update(hspace=0.01, wspace=0.01)
This gave me the following plot:
I also used the following to remove the axis labels where needed:
nullfmt = plt.NullFormatter()
ax.yaxis.set_major_formatter(nullfmt)
ax.xaxis.set_major_formatter(nullfmt)

Related

Is it possible to automatically scale the figure size, but keep the plot size constant in matplotlib?

I am using matplotlib to create multiple bar plots using the following code:
fig = plt.figure(figsize=(4, 4))
plt.barh(y=y, width=width, height=0.5)
plt.yticks(y, labels)
plt.xlabel("Contribution")
plt.tight_layout()
plt.show()
Since the length of my y-ticks labels can vary, the plot can get squeezed together as in the case below:
In other cases the plot looks fine:
Now, I was wondering, if there is an option in matplotlib to keep the plot size constant, but scale the figure size automatically (in horizontal direction)? My goal is that the plot size stays always the same, independent of the y-label length (because they vary inbetween plots). Thank you!

When using external axes method to plot multiple candlestick charts using mplfinance library, how to plot volume inside the candlestick chart?

The project I am doing requires code to plot more than 300 candlestick charts in several figures using mplfinance library. I am aware that this can only be done using external axes method as it provides more flexibilities and can plot unlimited charts theoretically.
The current code I am using is as below, the charts plotted can be seen below:
import mplfinance as mpf
s = mpf.make_mpf_style(base_mpf_style='yahoo', rc={'font.size': 6})
fig = mpf.figure(figsize=(34, 13.2), style=s, tight_layout=True)
ax_p = fig.add_subplot(n_rows, n_cols, pos_price)
ax_v = fig.add_subplot(n_rows, n_cols, pos_vol, sharex=ax_p)
fig, ax_list = mpf.plot(resampled_df, type='candle', ax=ax_p, volume=ax_v, show_nontrading=False,
datetime_format='%a %d-%m-%y', xrotation=0, returnfig=True)
The screenshot of the 6 sample charts from hundreds of charts my code plotted:
The screenshot of the two charts the above code plotted is as below:
As you can see the volume chart was plotted in an individual chart below the candlestick chart. I struggle to find the solution to move the volume into candlestick chart, there is a similar post in mplfinance documentation issue 114 kind of explains how to do this...... but I found it is rather difficult to understand for new ppl to the library like me.
Would highly appreciate it if you could post the detailed code to do this!
Update on 12th Feb 2021:
I modified the code with #Daniel's suggestion, use add_axes() rather than add_subplot() and now the volume is at the bottom of the candlestick chart when plotting multiple charts. Beautiful! Answer accepted.
ax_intra_day_candle = fig.add_axes([x_pos, y_pos, ax_width, ax_height])
ax_intra_day_candle.set_title(title)
ax_intra_day_volume = fig.add_axes([x_pos, y_pos - ax_vol_height, ax_width, ax_vol_height], sharex=ax_intra_day_candle)
mpf.plot(intra_day_df, type='candle', ax=ax_intra_day_candle, volume=ax_intra_day_volume, show_nontrading=False,
datetime_format='%a %m-%d', xrotation=0)
I will assume what you are asking is to have the volume and candlesticks share the same x-axis similar to this image here.
The simplest way to do this is to use fig.add_axes() (instead of fig.add_subplot())
In this way you can control exactly where in the Figure each Axes is placed. You can see this being done in the mplfinance code here.
The basic idea is that you specify the location of each Axes object in terms of a fraction of the total figure, indicating the lower left corner of the Axes, and its width and height.
When you want two Axes objects to touch, with no space between them, you specify the location and width/height accordingly so that the top of the lower Axes and the bottom of the upper Axes exactly meet.
So, for example, to stack two equally sized Axes exactly on top of each other, lets say in the upper left quadrant of the Figure you would have:
# ax = fig.add_axes([left,bottom,width,height])
ax1 = fig.add_axes([0.05,0.75,0.5,0.25])
ax2 = fig.add_axes([0.05,0.50,0.5,0.25])
The 0.05 space to the left allows room for the y-axis labels.
ax1 starts three quarters (0.75) of the way up from the bottom, and stretches half way (0.5) to the right with a height of 0.25 (which takes it to the very top of the Figure).
ax2 starts half way (0.50) up from the bottom, also stretches half way (0.5) across to the right, and has a height of 0.25 taking it exactly to the very bottom of ax1.
HTH
Here is a more specific example, and the result. Notice how the candles and volume plot together only take up the upper left quadrant of the figure:
fig = mpf.figure(figsize=(8,8),style='yahoo')
ax1 = fig.add_axes([0.05,0.75,0.5,0.25])
ax2 = fig.add_axes([0.05,0.50,0.5,0.25])
mpf.plot(df,type='candle',ax=ax1,volume=ax2)
mpf.show()
No need so long code use the below code
suppose you have volume and data for plotting in a chart using mplfinance
mpf.plot(data,type='candle',style='yahoo',volume=True)

Matplotlib: Constrain plot width while allowing flexible height

What I would like to achive are plots with equal scale aspect ratio, and fixed width, but a dynamically chosen height.
To make this more concrete, consider the following plotting example:
import matplotlib as mpl
import matplotlib.pyplot as plt
def example_figure(slope):
# Create a new figure
fig = plt.figure()
ax = fig.add_subplot(111)
# Set axes to equal aspect ratio
ax.set_aspect('equal')
# Plot a line with a given slope,
# starting from the origin
ax.plot([x * slope for x in range(5)])
# Output the result
return fig
This example code will result in figures of different widths, depending on the data:
example_figure(1).show()
example_figure(2).show()
Matplotlib seems to fit the plots into a certain height, and then chooses the width to accomodate the aspect ratio. The ideal outcome for me would be the opposite -- the two plots above would have the same width, but the second plot would be twice as tall as the first.
Bonus — Difficulty level: Gridspec
In the long run, I would like to create a grid in which one of the plots has a fixed aspect ratio, and I would again like to align the graphs exactly.
# Create a 2x1 grid
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(2, 1)
# Create the overall graphic, containing
# the top and bottom figures
fig = plt.figure()
ax1 = fig.add_subplot(gs[0, :], aspect='equal')
ax2 = fig.add_subplot(gs[1, :])
# Plot the lines as before
ax1.plot(range(5))
ax2.plot(range(5))
# Show the figure
fig.show()
The result is this:
So again, my question is: How does one create graphs that vary flexibly in height depending on the data, while having a fixed width?
Two points to avoid potential misunderstandings:
In the above example, both graphs have the same x-axis. This cannot be
taken for granted.
I am aware of the height_ratios option in the gridspec. I can compute
the dimensions of the data, and set the ratios, but this unfortunately
does not control the graphs directly, but rather their bounding boxes,
so (depending on the axis labels), graphs of different widths still occur.
Ideally, the plots' canvas would be aligned exactly.
Another unsolved question is similar, but slightly more convoluted.
Any ideas and suggestions are very welcome, and I'm happy to specify the question further, if required. Thank you very much for considering this!
Have you tried to fix the width with fig.set_figwidth()?

Matplotlib - axvspan vs subplots

I'm writing a pythonic script for a coastal engineering application which should output, amongst other things, a figure with two subplots.
The problem is that I would like to shade a section of both subplots using plt.axvspan() but for some reason it only shades one of them.
Please find below an excerpt of the section of the code where I set up the plots as well as the figure that it's currently outputting (link after code).
Thanks for your help, and sorry if this is a rookie question (but it just happens that I am indeed a rookie in Python... and programming in general) but I couldn't find an answer for this anywhere else.
Feel free to add any comments to the code.
# PLOTTING
# now we generate a figure with the bathymetry vs required m50 and another figure with bathy vs Hs
#1. Generate plots
fig = plt.figure() # Generate Figure
ax = fig.add_subplot(211) # add the first plot to the figure.
depth = ax.plot(results[:,0],results[:,1]*-1,label="Depth [mDMD]") #plot the first set of data onto the first set of axis.
ax2 = ax.twinx() # generate a secondary vertical axis with the same horizontal axis as the first
m50 = ax2.plot(results[:,0],results[:,6],"r",label="M50 [kg]") # plot the second set of data onto the second vertical axis
ax3 = fig.add_subplot(212) # generate the second subplot
hs = ax3.plot(results[:,0],results[:,2],"g",label="Hs(m)")
#Now we want to find where breaking starts to occur so we shade it on the plot.
xBreakingDistance = results[numpy.argmax(breakingIndex),0]
# and now we plot a box from the origin to the depth of breaking.
plt.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1) # this box is called a span in matplotlib (also works for axhspan)
# and then we write BREAKING ZONE in the box we just created
yLimits = ax.get_ylim() # first we get the range of y being plotted
yMiddle = (float(yLimits[1])-float(yLimits[0])) / 2 + yLimits[0] # then we calculate the middle value in y (to center the text)
xMiddle = xBreakingDistance / 2 # and then the middle value in x (to center the text)
#now we write BREAKING ZONE in the center of the box.
ax.text(xMiddle,yMiddle,"BREAKING ZONE",fontweight="bold",rotation=90,verticalalignment="center",horizontalalignment="center")
#FIGURE FORMATTING
ax.set_xlabel("Distance [m]") # define x label
ax.set_ylabel("Depth [mDMD]") # define y label on the first vertical axis (ax)
ax2.set_ylabel("M50 [kg]") # define y label on the second vertical axis (ax2)
ax.grid() # show grid
ax3.set_xlabel("Distance[m]") #define x label
ax3.set_ylabel("Hs[m]") # define y label
ax3.grid()
plt.tight_layout() # minimize subplot labels overlapping
# generating a label on a plot with 2 vertical axis is not very intuitive. Normally we would just write ax.label(loc=0)
combined_plots = depth+m50 #first we need to combine the plots in a vector
combined_labels = [i.get_label() for i in combined_plots] # and then we combine the labels
ax.legend(combined_plots,combined_labels,loc=0) # and finally we plot the combined_labels of the combined_plots
plt.savefig("Required M50(kg) along the trench.png",dpi=1000)
plt.close(fig)
Output Figure:
By just calling plt.axvspan, you are telling matplotlib to create the axvspan on the currently active axes (i.e. in this case, the last one you created, ax3)
You need to plot the axvspan on both of the axes you would like for it to appear on. In this case, ax and ax3.
So, you could do:
ax.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1)
ax3.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1)
or in one line:
[this_ax.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1) for this_ax in [ax,ax3]]
It's difficult to analyze your code and not being able to reproduce it. I advise you to build a minimal example. In any case notice that you are calling "plt.axvspan(" which is general call to the library.
You need to specifically state that you want this in both "ax" and "ax2" (i think).
Also if you need more control consider using Patches (I don't know axvspan):
import matplotlib.pyplot as plt
import matplotlib.patches as patches
fig1 = plt.figure()
ax1 = fig1.add_subplot(111, aspect='equal')
ax1.add_patch(
patches.Rectangle(
(0.1, 0.1), # (x,y)
0.5, # width
0.5, # height
)
)
fig1.savefig('rect1.png', dpi=90, bbox_inches='tight')
See that call to "ax1" in the example? Just make something similar to yours. Or just add axvspan to each of your plots.

matplotlib - subplots with fixed aspect ratio

I have a problem with plotting multiple subplots. I would like to set the PHYSICAL aspect ratio of the subplots to a fixed value.
In my example I have 12 subplots (4 rows and 3 columns) on a landscape A4 figure. There all subplots are nicely placed on the whole figure, and for all subplots the height is nearly equal to the width.
But if I change the layout of my figure to portrait, the subplots are stretched vertically.
And this is exactly what should not happen. I would like to have the same height and width of the subplots as on the landscape figure. Is there a possibility that the aspect ratio of the subplots stay the same?
Thanks in advance,
Peter
EDIT:
I have found a workaround. But this just works for non-logarithmic axes...
aspectratio=1.0
ratio_default=(ax.get_xlim()[1]-ax.get_xlim()[0])/(ax.get_ylim()[1]-ax.get_ylim()[0])
ax.set_aspect(ratio_default*aspectratio)
Actually, what you're wanting is quite simple... You just need to make sure that adjustable is set to 'box' on your axes, and you have a set aspect ratio for the axes (anything other than 'auto').
You can either do this with the adjustable kwarg when you create the subplots. Alternatively, you can do this after their creation by calling ax.set_adjustable('box'), or by calling ax.set_aspect(aspect, adjustable='box') (where aspect is either 'equal' or a number).
Now, regardless of how the figure is resized, the subplots will maintain the same aspect ratio.
For example:
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(2,1,1, adjustable='box', aspect=0.3)
ax2 = fig.add_subplot(2,1,2)
ax1.plot(range(10))
ax2.plot(range(10))
plt.show()
Now, compare how the top subplot responds to resizing, vs. how the bottom subplot responds:
The initial plot
Resized to a vertical layout:
Resized to a horizontal layout:
Your workaround works for me. After plotting the data, I call the following function:
def fixed_aspect_ratio(ratio):
'''
Set a fixed aspect ratio on matplotlib plots
regardless of axis units
'''
xvals,yvals = gca().axes.get_xlim(),gca().axes.get_ylim()
xrange = xvals[1]-xvals[0]
yrange = yvals[1]-yvals[0]
gca().set_aspect(ratio*(xrange/yrange), adjustable='box')
In reply to the remark about the solution not working for logarithmic plots in the edit to the original question - you need to adapt as follows:
def fixed_aspect_ratio_loglog(ratio):
'''
Set a fixed aspect ratio on matplotlib loglog plots
regardless of axis units
'''
xvals,yvals = gca().axes.get_xlim(),gca().axes.get_ylim()
xrange = log(xvals[1])-log(xvals[0])
yrange = log(yvals[1])-log(yvals[0])
gca().set_aspect(ratio*(xrange/yrange), adjustable='box')
(Adaptation for semilog plots should now be obvious)

Categories

Resources