Putting text from one corner to the opposite one - python-3.x

I'd like my plot to have a background text to be stretched from one corner (say lower left) to the opposite corner. The x and y dimensions are not isometric and it's not a square plot, so a fixed angle of 45 degrees will not work.
So far I have the text starting in the correct corner. How can the text be rotated and stretched so it spans the entire plot?
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
# plot command not shown
ax.text(ax.get_xlim()[0], ax.get_ylim()[0] , 'PRELIMINARY' , rotation=45 )

To position something with respect to the subplot (the ax), it helps to work in axes coordinates. These go from 0,0 in the lower left to 1,1 in the top right. Putting the text at 0.5,0.5 would set it nicely centered.
To calculate the angle, one could divide the subplot's height in pixels by its width, then take the arc tangent, and convert from radians to degrees.
The optimal size for the text is harder to calculate. One would need to render it, measure it, change the font size and render it again. Or just manually try a few sizes until it looks OK.
Note that when the window size gets changed interactively, the text will stay nicely in the center, but the rotation will get a bit off.
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.text(0.5, 0.5, 'PRELIMINARY', transform=ax.transAxes, size=50,
rotation=np.degrees(np.arctan(ax.get_window_extent().height / ax.get_window_extent().width)),
ha='center', va='center')
plt.show()

Related

how to draw shapes (ellipses, rectange, circle, etc) with line width thicker than 1 pixel

The skimage.draw module has functions to draw circles, ellipses, lines, etc. However the line width seems to be fixed at 1 pixel. There doesn't appear to be a parameter to set the line width.
Stefan van der Walt suggested that there is hidden functionality in the skimage.measure submodule to draw thicker lines, but I had a look at the documentation and only saw the profile_line function which does have a linewidth parameter. I don't know if this what he meant, or how I can use that to draw an ellipse with width=3.
So how can I draw an ellipse with thickness of 3 pixels into a numpy image array (type float)? Preferably using skimage.
I would use draw to draw a 1-pixel thick line, then use skimage.morphology.dilation to "thicken" that line:
import numpy as np
import matplotlib.pyplot as plt
from skimage import draw, morphology
image = np.zeros((128, 128))
rr, cc = draw.ellipse_perimeter(64, 64, 20, 10)
image[rr, cc] = 1
dilated = morphology.dilation(image, morphology.disk(radius=1))
fig, (ax0, ax1) = plt.subplots(1, 2)
ax0.imshow(image)
ax1.imshow(dilated)
I am trying to do the same thing.
Fortunately I just need to draw a bounding box, so I do it iteratively this way.
Not sure whether this approach is applicable to other shape or not.
for i in range(0,thickness):
rect = skimage.draw.rectangle_perimeter(start=(b[1]-i,b[0]-i)
,end(b[3]+i,b[2]+i),shape=(1024,1024))
skimage.draw.set_color(img_with_box,rect,(1.0,-1,-1))

How to plot fill_betweenx to fill the area between y1 and y2 with different scales using matplotlib.pyplot?

I am trying to fill the area between two vertical curves(RHOB and NPHI) using matplotlib.pyplot. Both RHOB and NPHI are having different scale of x-axis.
But when i try to plot i noticed that the fill_between is filling the area between RHOB and NPHI in the same scale.
#well_data is the data frame i am reading to get my data
#creating my subplot
fig, ax=plt.subplots(1,2,figsize=(8,6),sharey=True)
ax[0].get_xaxis().set_visible(False)
ax[0].invert_yaxis()
#subplot 1:
#ax01 to house the NPHI curve (NPHI curve are having values between 0-45)
ax01=ax[0].twiny()
ax01.set_xlim(-15,45)
ax01.invert_xaxis()
ax01.set_xlabel('NPHI',color='blue')
ax01.spines['top'].set_position(('outward',0))
ax01.tick_params(axis='x',colors='blue')
ax01.plot(well_data.NPHI,well_data.index,color='blue')
#ax02 to house the RHOB curve (RHOB curve having values between 1.95,2.95)
ax02=ax[0].twiny()
ax02.set_xlim(1.95,2.95)
ax02.set_xlabel('RHOB',color='red')
ax02.spines['top'].set_position(('outward',40))
ax02.tick_params(axis='x',colors='red')
ax02.plot(well_data.RHOB,well_data.index,color='red')
# ax03=ax[0].twiny()
# ax03.set_xlim(0,50)
# ax03.spines['top'].set_position(('outward',80))
# ax03.fill_betweenx(well_data.index,well_data.RHOB,well_data.NPHI,alpha=0.5)
plt.show()
ax03=ax[0].twiny()
ax03.set_xlim(0,50)
ax03.spines['top'].set_position(('outward',80))
ax03.fill_betweenx(well_data.index,well_data.RHOB,well_data.NPHI,alpha=0.5)
above is the code that i tried, but the end result is not what i expected.
it is filling area between RHOB and NPHI assuming RHOB and NPHI is in the same scale.
How can i fill the area between the blue and the red curve?
Since the data are on two different axes, but each artist needs to be on one axes alone, this is hard. What would need to be done here is to calculate all data in a single unit system. You might opt to transform both datasets to display-space first (meaning pixels), then plot those transformed data via fill_betweenx without transforming again (transform=None).
import numpy as np
import matplotlib.pyplot as plt
y = np.linspace(0, 22, 101)
x1 = np.sin(y)/2
x2 = np.cos(y/2)+20
fig, ax1 = plt.subplots()
ax2 = ax1.twiny()
ax1.tick_params(axis="x", colors="C0", labelcolor="C0")
ax2.tick_params(axis="x", colors="C1", labelcolor="C1")
ax1.set_xlim(-1,3)
ax2.set_xlim(15,22)
ax1.plot(x1,y, color="C0")
ax2.plot(x2,y, color="C1")
x1p, yp = ax1.transData.transform(np.c_[x1,y]).T
x2p, _ = ax2.transData.transform(np.c_[x2,y]).T
ax1.autoscale(False)
ax1.fill_betweenx(yp, x1p, x2p, color="C9", alpha=0.4, transform=None)
plt.show()
We might equally opt to transform the data from the second axes to the first. This has the advantage that it's not defined in pixel space and hence circumvents a problem that occurs when the figure size is changed after the figure is created.
x2p, _ = (ax2.transData + ax1.transData.inverted()).transform(np.c_[x2,y]).T
ax1.autoscale(False)
ax1.fill_betweenx(y, x1, x2p, color="grey", alpha=0.4)

Set matplotlib legend markersize to a constant

I'm making a diagram using matplotlib, and it has plt.Circles and plt.axvlines to represent different shapes. I need a legend to describe these shapes, but the problem is the legend marker (the image part), changes size depending on the input, which looks awful. How do I set the size to a constant?
fig = plt.figure(figsize=(6.4, 6), dpi=200, frameon=False)
ax = fig.gca()
# 3 Circles, they produce different sized legend markers
ax.add_patch(plt.Circle((0,0), radius=1, alpha=0.9, zorder=0, label="Circle"))
ax.add_patch(plt.Circle((-1,0), radius=0.05, color="y", label="Point on Circle"))
ax.add_patch(plt.Circle((1, 0), radius=0.05, color="k", label="Opposite Point on Circle"))
# A vertical line which produces a huge legend marker
ax.axvline(0, ymin=0.5-0.313, ymax=0.5+0.313, linewidth=12, zorder=1, c="g", label="Vertical Line")
ax.legend(loc=2)
ax.set_xlim(-2,1.2) # The figsize and limits are meant to preserve the circle's shape
ax.set_ylim(-1.5, 1.5)
fig.show()
I've seen solutions including legend.legendHandles[0]._size or various assortments of that, and it doesn't seem to change the size regardless of the value I set
The legend markers for the circles are different in size because the first circle has no edgecolor, while the two other ones have an edgecolor set via color. You may instead set the facecolor of the circle. Alternatively, you can set the linewidth of all 3 circles equal.
The legend marker for the line is so huge because it simply copies the attribute from the line in the plot. If you want to use a different linewidth, you can update it via the respective legend handler.
import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerLine2D
def update_prop(handle, orig):
handle.update_from(orig)
handle.set_linewidth(2)
fig, ax = plt.subplots(figsize=(6.4, 6), dpi=200, frameon=False)
# 3 Circles, set the facecolor instead of edge- and face-color
ax.add_patch(plt.Circle((0,0), radius=1, alpha=0.9, zorder=0, label="Circle"))
ax.add_patch(plt.Circle((-1,0), radius=0.05, facecolor="y", label="Point on Circle"))
ax.add_patch(plt.Circle((1, 0), radius=0.05, facecolor="k", label="Opposite Point on Circle"))
# Line, update the linewidth via
ax.axvline(0, ymin=0.5-0.313, ymax=0.5+0.313, linewidth=12, zorder=1, c="g", label="Vertical Line")
ax.legend(loc=2, handler_map={plt.Line2D:HandlerLine2D(update_func=update_prop)})
ax.set_xlim(-2,1.2)
ax.set_ylim(-1.5, 1.5)
plt.show()

How to fill area under step curve using pyplot?

I have plotted two step curves using pyplot.step(), and I would like to shade in the area beneath these curves (ideally with transparent shading). pyplot.fill_between() assumes linear interpolation, whereas I want to see step interpolation, as displayed below:
How can I shade in the region beneath these curves? Transparent coloring would be great, as this would make clear where these curves overlap.
You can use the alpha value of the fill_between to make it semi-transparent.
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0,50,35)
y = np.random.exponential(1, len(x))
y2 = np.random.exponential(1, len(x))
plt.fill_between(x,y, step="pre", alpha=0.4)
plt.fill_between(x,y2, step="pre", alpha=0.4)
plt.plot(x,y, drawstyle="steps")
plt.plot(x,y2, drawstyle="steps")
plt.show()

How to combine gridspec with plt.subplots() to eliminate space between rows of subplots

I am trying to plot multiple images in subplots and either eliminate the space between subplots (horizontal and vertical) or control it. I tried to use the suggestion in How to Use GridSpec.... I also tried here but they are not using subplots(): space between subplots
I am able to eliminate the horizontal space but not the vertical space with what I am doing in the code below. Please do not mark as duplicate as I have tried the other posts and they do not do what I want. My code is shown below. Maybe there is another keyword argument that I need in the gridspec_kw dictionary?
I want to use plt.subplots() not plt.subplot() for this. In case it matters, the images are not square they are rectangular. I also tried adding f.tight_layout(h_pad=0,w_pad=0) before plt.show() but it did not change anything.
def plot_image_array_with_angles(img_array,correct_angles,predict_angles,
fontsize=10,figsize=(8,8)):
'''
Imports:
import matplotlib.gridspec as gridspec
import numpy as np
import matplotlib.pyplot as plt
'''
num_images = len(img_array)
grid = int(np.sqrt(num_images)) # will only show all images if square
#f, axarr = plt.subplots(grid,grid,figsize=figsize)
f, axarr = plt.subplots(grid,grid,figsize=figsize,
gridspec_kw={'wspace':0,'hspace':0})
im = 0
for row in range(grid):
for col in range(grid):
axarr[row,col].imshow(img_array[im])
title = 'cor = ' + str(correct_angles[im]) + ' pred = ' + str(predict_angles[im])
axarr[row,col].set_title(title,fontsize=fontsize)
axarr[row,col].axis('off') # turns off all ticks
#axarr[row,col].set_aspect('equal')
im += 1
plt.show()
return
The aspect ratio of an imshow plot is automatically set such that pixels in the image are squared. This setting is stronger than any of the subplots_adjust or gridspec settings for spacing. Or in other words you cannot directly control the spacing between subplots if those subplots have their aspect set to "equal".
First obvious solution is to set the image aspect to automatic ax.set_aspect("auto"). This solves the problem of subplot spacing, but distorts the images.
The other option is to adjust the figure margins and the figure size such that the spacing between the subplots is as desired.
Let's say figh and figw are the figure height and width in inch, and s the subplot width in inch. The margins are bottom, top, left and right (relative to figure size) and the spacings hspace in vertical and wspace in horizontal direction (relative to subplot size). The number of rows is denoted n and the number of columns m. The aspect is the ratio between subplot (image) height and width (aspect = image height / image width).
Then the dimensions can be set via
fig, axes = plt.subplots(nrows=n, ncols=m, figsize=(figwidth, figheight))
plt.subplots_adjust(top=top, bottom=bottom, left=left, right=right,
wspace=wspace, hspace=hspace)
The respective values can be calculated according to:
Or, if the margins are the same:
An example:
import matplotlib.pyplot as plt
image = plt.imread("https://i.stack.imgur.com/9qe6z.png")
aspect = image.shape[0]/float(image.shape[1])
print aspect
n = 2 # number of rows
m = 4 # numberof columns
bottom = 0.1; left=0.05
top=1.-bottom; right = 1.-left
fisasp = (1-bottom-(1-top))/float( 1-left-(1-right) )
#widthspace, relative to subplot size
wspace=0.15 # set to zero for no spacing
hspace=wspace/float(aspect)
#fix the figure height
figheight= 3 # inch
figwidth = (m + (m-1)*wspace)/float((n+(n-1)*hspace)*aspect)*figheight*fisasp
fig, axes = plt.subplots(nrows=n, ncols=m, figsize=(figwidth, figheight))
plt.subplots_adjust(top=top, bottom=bottom, left=left, right=right,
wspace=wspace, hspace=hspace)
for ax in axes.flatten():
ax.imshow(image)
ax.set_title("title",fontsize=10)
ax.axis('off')
plt.show()

Resources