I try to plot and connect points in in the shape of a rectangle, but I am doing something wrong.
I have vectors of coordinates like this:
x = [6.2372045620000005, 6.237194762000001, 6.237194762000001, 6.2372045620000005]
y = [51.071833453, 51.071835828999994, 51.071833453, 51.071835828999994]
First, I plot point data:
plt.scatter(x, y, color = 'blue')
Then, I try to add line between points in such a way, that a rectangle is formed. Unfortunately this below does not work correctly.
plt.scatter(x, y, color = 'blue')
plot.plot(x,y)
Do you know what I am doing wrong? It's a simple thing for sure, but I'm stuck with that..
Thanks for you help and comments.
Usually it is assumed that the coordinates, for any shape, are already ordered.
If you're confident that you have a rectangle (all right corners, not some quadrilateral) you can take the min/max values of your x and y coordinates to get the correct corner points. Given the coordinates that you already defined in your post:
import matplotlib.patches as patches
import matplotlib.pyplot as plt
xmin = min(x)
xmax = max(x)
xsize = xmax - xmin
ymin = min(y)
ymax = max(y)
ysize = ymax - ymin
Using Matplotlib, you can either use the Rectangle patch like:
fig, ax = plt.subplots(dpi=86)
ax.scatter(x, y, color='blue')
rect = patches.Rectangle(
(xmin, ymin), xsize, ysize,
lw=2, edgecolor='r', facecolor='none',
)
ax.add_patch(rect)
Or the Polygon patch, for any arbitrary shape. Both methods will work in this case:
points = [
(xmin, ymin),
(xmax, ymin),
(xmax, ymax),
(xmin, ymax),
]
poly = patches.Polygon(
points, lw=2, edgecolor='r', facecolor='#ff000011', closed=True,
)
ax.add_patch(poly)
If you do have something less regular shaped, like a quadrilateral, calculating the convex-hull might help getting the correct order of the coordinates. See for example:
Convex hull of 4 points
Related
I have an algorithm problem. I would like to do the following.
I have a polar plot in theta, r coords as below:
phis = np.linspace(0.01,63,100) # SV half cone ang, measured up from nadir
thetas = np.linspace(0,2*np.pi,361)# SV azimuth, 0 coincides with the vel vector
X,Y = np.meshgrid(thetas,phis)
rangeMap = orbits.range(orbits.h_mission, Y * u.deg)[0].value
fig, ax = plt.subplots(figsize=(8,7),subplot_kw=dict(projection='polar'))
X, Y = np.meshgrid(thetas, phis) # Create a grid over the range of bins for the plot
im = (ax.pcolormesh(thetas,phis, rangeMap, cmap=mpl.cm.jet_r, alpha=0.95,shading='auto') )
ax.set_theta_direction(-1)
ax.set_theta_offset(np.pi / 2.0)
plt.thetagrids([theta * 15 for theta in range(360//15)])
ax.grid(True)
# ax.set_xlabel("")
# ax.set_ylabel("")
# ax.set_xticklabels([])
# ax.set_yticklabels([])
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
## Add colorbar
gc.collect()
Notice that I plot with theta and phis as opposed to X,Y ... this seems to work (I don't have to use X,Y).
The theta and r correspond to pointing vectors from a spacecraft that intersect the earth. As such, I can transform those coordinates, (theta,r), into lon/lat on the globe for cartopy.
However, pcolormesh, obviously uses polar coordinates. And although I can translate each PAIR of theta,r into lon/lat, it doesn't help. I thought I could just substitute the theta, phi for lon,lat but that doesn't seem to work (keeping my Z = rangeMap values unchanged). i.e. - This doesn't work
resolution = '110m'
lls = [orbits.sphere_intersect(SV_pos_vec, SV_vel_vec, az << u.deg, el << u.deg, lonlat=True)[:2] for az in thetas for el in phis] # This returns a long/lat array for az/el
gd = Geodesic() # from cartopy
fig = plt.figure(figsize=(12,6), dpi=96)
ax = fig.add_subplot(111, projection=flatMap)
ax.imshow(np.tile(np.array([[cfeature.COLORS['water'] * 255]], dtype=np.uint8), [2, 2, 1]), origin='upper', transform=ccrs.PlateCarree(), extent=[-180, 180, -180, 180])
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', resolution, edgecolor='black', facecolor=cfeature.COLORS['land']))
ax.add_feature(cfeature.NaturalEarthFeature('cultural', 'admin_0_countries', resolution, edgecolor='black', facecolor='none'))
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'lakes', resolution, edgecolor='none', facecolor=cfeature.COLORS['water']), alpha=0.5)
im = (ax.pcolormesh(thetas, phis, rangeMap, cmap=mpl.cm.jet_r, alpha=0.95,shading='auto') )
fig.tight_layout()
# plt.savefig('PlotBeamInfo.pdf', dpi=96)
gc.collect()
The approach gives me something like:
Here's my goal: project a theta,r polar plot onto a cartopy map. Anyone have ideas on how to do this? How do I project a polar plot onto the globe?
You can make up any data you like for the rangeMap ... it doesn't matter... its the x,y for pcolormesh that I can't figure out.
I am unable to understand from the matplotlib documentation(https://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html), the working of a trisurf plot. Can someone please explain how the X,Y and Z arguments result in a 3-D plot?
Let me talk you through this example taken from the docs
'''
======================
Triangular 3D surfaces
======================
Plot a 3D surface with a triangular mesh.
'''
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
n_radii = 8
n_angles = 36
# Make radii and angles spaces (radius r=0 omitted to eliminate duplication).
radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
# Repeat all angles for each radius.
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
# Convert polar (radii, angles) coords to cartesian (x, y) coords.
# (0, 0) is manually added at this stage, so there will be no duplicate
# points in the (x, y) plane.
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
# Compute z to make the pringle surface.
z = np.sin(-x*y)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_trisurf(x, y, z, linewidth=0.2, antialiased=True)
plt.show()
The x, y values are a range of values over which we calculate the surface. For each (x, y) pair of coordinates, we have a single value of z, which represents the height of the surface at that point.
I'm trying to draw arrows and rectangles in matplotlib (to represent protein secondary structure) next to the y-axis of the plot, something like this:
From here I got the arrow part, but I can't figure out how to draw it outside the y-axis. Also, is there a way to draw rectangles in addition to arrows? Code and output below:
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
x_tail = 0.0
y_tail = -0.1
x_head = 0.0
y_head = 0.9
dx = x_head - x_tail
dy = y_head - y_tail
fig, axs = plt.subplots(nrows=2)
arrow = mpatches.FancyArrowPatch((x_tail, y_tail), (dx, dy),
mutation_scale=50,
transform=axs[0].transAxes)
axs[0].add_patch(arrow)
arrow = mpatches.FancyArrowPatch((x_tail, y_tail), (dx, dy),
mutation_scale=100,
transform=axs[1].transAxes)
axs[1].add_patch(arrow)
axs[1].set_xlim(0, 1)
axs[1].set_ylim(0, 1)
It looks like the original approach is somewhat confusing.
Although you can draw rectangles via mpatch.Rectangle, I think it is easier to also draw the rectangles via FancyArrowPatch. That makes them behave and scale similarly, which is interesting for setting the width. Similarly, the vertical line is also drawn using a FancyArrowPatch.
For the positioning, it seems you can just give (tail_x, tail_y) and head_x, head_y. Via arrowstyle= the visual dimensions can be set. Leaving out head_length= from the style seems to allow an arrow that looks like a rectangle. For coloring, there are facecolor= and edgecolor=. And also color= which treats facecolor and edgecolor simultaneously.
arrow1.set_clip_on(False) allows to draw the arrows in the margin. Other functions can have a clip_on=False parameter. zorder= is needed to make the correct lines visible when one is drawn on top of the other.
Here is some example code. The rectangle is drawn twice so the vertical line doesn't show through the hatching. Now x is defined in 'axis coordinates' and y in the standard data coordinates. The 'axis' coordinates go from 0, the left border where usually y-axis is drawn to 1, the right border. Setting x to -0.1 means 10% to the left of the y-axis.
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.transforms as mtransforms
x0 = -0.1
arrow_style="simple,head_length=15,head_width=30,tail_width=10"
rect_style="simple,tail_width=25"
line_style="simple,tail_width=1"
fig, ax = plt.subplots()
# the x coords of this transformation are axes, and the y coord are data
trans = mtransforms.blended_transform_factory(ax.transAxes, ax.transData)
y_tail = 5
y_head = 15
arrow1 = mpatches.FancyArrowPatch((x0, y_tail), (x0, y_head), arrowstyle=arrow_style, transform=trans)
arrow1.set_clip_on(False)
ax.add_patch(arrow1)
y_tail = 40
y_head = 60
arrow2 = mpatches.FancyArrowPatch((x0, y_tail), (x0, y_head), arrowstyle=arrow_style, facecolor='gold', edgecolor='black', linewidth=1, transform=trans)
arrow2.set_clip_on(False)
ax.add_patch(arrow2)
y_tail = 20
y_head = 40
rect_backgr = mpatches.FancyArrowPatch((x0, y_tail), (x0, y_head), arrowstyle=rect_style, color='white', zorder=0, transform=trans)
rect_backgr.set_clip_on(False)
rect = mpatches.FancyArrowPatch((x0, y_tail), (x0, y_head), arrowstyle=rect_style, fill=False, color='orange', hatch='///', transform=trans)
rect.set_clip_on(False)
ax.add_patch(rect_backgr)
ax.add_patch(rect)
line = mpatches.FancyArrowPatch((x0, 0), (x0, 80), arrowstyle=line_style, color='orange', transform=trans, zorder=-1)
line.set_clip_on(False)
ax.add_patch(line)
ax.set_xlim(0, 30)
ax.set_ylim(0, 80)
plt.show()
I have written the following code that calculates the orientation of a blob using eigenvalues. When the orientation is determined, the function "straighten_up" straightens the blob out.
The only thing I'm missing to be fully satisfied, is a 1px white border in the second output figure between the black area and the green area. How can I do this?
I'm using a mask image as input:
code:
import numpy as np
import matplotlib.pyplot as plt
import cv2
img = cv2.imread('input_image.png',100)
edges = cv2.Canny(img,0,255) #searching for a border
# compute the orientation of a blob
img = edges
y, x = np.nonzero(img) # Find the index of the white pixels
x = x - np.mean(x) #The average of an array of elements
y = y - np.mean(y)
coords = np.vstack([x, y])
cov = np.cov(coords) #determine covariance matrix
evals, evecs = np.linalg.eig(cov) #eigenvectors
sort_indices = np.argsort(evals)[::-1] #Sort Eigenvalues in decreasing order
x_v1, y_v1 = evecs[:, sort_indices[0]]
x_v2, y_v2 = evecs[:, sort_indices[1]]
scale = 30
plt.plot([x_v1*-scale*2, x_v1*scale*2], #plot to show the eigenvectors
[y_v1*-scale*2, y_v1*scale*2], color='red')
plt.plot([x_v2*-scale, x_v2*scale],
[y_v2*-scale, y_v2*scale], color='blue')
plt.plot(x, y, 'k.')
plt.axis('equal')
plt.gca().invert_yaxis()
plt.show()
def straighten_up(x_v1,y_v1,coords):
theta = np.arctan((x_v1)/(y_v1))
rotation_mat =np.matrix([[np.cos(theta), -np.sin(theta)],[np.sin(theta),np.cos(theta)]])
transformed_mat = rotation_mat*coords
x_transformed, y_transformed = transformed_mat.A
fig, ax = plt.subplots(nrows=1, ncols=1)
ax = fig.add_subplot(1, 1, 1) # nrows, ncols, index
ax.set_facecolor((1.0, 0.47, 0.42))
plt.plot(x_transformed,y_transformed,"black")
straighten_up(x_v1,y_v1,coords)
plt.show()
with output:
Your x_transformed and y_transformed are the x and y coordinates of the rotated border. So you can draw them e.g. with plt.scatter. This draws dots (the third parameter is the size) on these x,y positions. Use zorder to make sure the scatter dots are not hidden by the previous parts of the plot.
Following code does just that:
fig, ax = plt.subplots(nrows=1, ncols=1)
ax = fig.add_subplot(1, 1, 1) # nrows, ncols, index
ax.set_facecolor('fuchsia')
plt.axis('equal')
plt.plot(x_transformed, y_transformed, c="lime")
plt.scatter(x_transformed, y_transformed, 1, c="white", zorder=3)
plt.show()
As you notice, there is another problem: the plot of the filled figure isn't similar to your input image. What is happening, is that plot draws lines(x[0],y[0]) to (x[1],y[1]) to (x[2],y[2]) etc.. As your x and y are only the border points, not ordered as a polygon, it is more complicated to get a correctly filled polygon. For a random input image, you can have many borders, that can form polygons with holes and islands and which can touch the image borders.
To properly get the interior points, you might get y, x = np.nonzero(img) from the original image (instead of only the edges), then do the same shift subtracting the mean of the edges, and use the same transformation matrix.
I am generating a groundwater elevation contour and a streamplot in matplotlib
The contour indicates that the elevation is decreasing in many areas but the groundwater flow (streamplot) is pointed uphill. I have circled the arrows that seem to be pointed the wrong direction.
The arrows toward the bottom of the map appear to be pointed the correct direction. Does anyone know why this might be?
And here is most of the code which generates this plot:
#create empty arrays to fill up!
x_values = []
y_values = []
z_values = []
#iterate over wells and fill the arrays with well data
for well in well_arr:
x_values.append(well['xpos'])
y_values.append(well['ypos'])
z_values.append(well['value'])
#initialize numpy array as required for interpolation functions
x = np.array(x_values, dtype=np.float)
y = np.array(y_values, dtype=np.float)
z = np.array(z_values, dtype=np.float)
#create a list of x, y coordinate tuples
points = zip(x, y)
#create a grid on which to interpolate data
xi, yi = np.linspace(0, image['width'], image['width']),
np.linspace(0, image['height'], image['height'])
xi, yi = np.meshgrid(xi, yi)
#interpolate the data with the matlab griddata function
zi = griddata(x, y, z, xi, yi, interp='nn')
#create a matplotlib figure and adjust the width and heights
fig = plt.figure(figsize=(image['width']/72, image['height']/72))
#create a single subplot, just takes over the whole figure if only one is specified
ax = fig.add_subplot(111, frameon=False, xticks=[], yticks=[])
#create the contours
kwargs = {}
if groundwater_contours:
kwargs['colors'] = 'b'
CS = plt.contour(xi, yi, zi, linewidths=linewidth, **kwargs)
#add a streamplot
dx, dy = np.gradient(zi)
plt.streamplot(xi, yi, dx, dy, color='c', density=1, arrowsize=3)
Summary
I'm guessing, but your problem is probably because you have an inherent transpose going on. 2D numpy arrays are indexed as row, column. "x, y" indexing is column, row. In this context, numpy.gradient is basically going to return dy, dx and not dx, dy.
Try changing the line:
dx, dy = np.gradient(zi)
to:
dy, dx = np.gradient(zi)
Also, if your depths are defined as positive-up, it should be:
dy, dx = np.gradient(-zi)
However, I'm assuming you have positive-down depth conventions, so I'll leave that part of of the examples below. (So higher values are assumed to be deeper/lower in the example data below, and water will flow towards the high values.)
Reproducing the problem
For example, if we modify the code you gave to use random data and fill in a few variables that are coming from outside the scope of your code sample (so that it's a stand-alone example):
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.mlab import griddata
# Generate some reproducible but random data
np.random.seed(1981)
width, height = 200, 300
x, y, z = np.random.random((3,10))
x *= width
y *= height
#create a list of x, y coordinate tuples
points = zip(x, y)
#create a grid on which to interpolate data
xi, yi = np.linspace(0, width, width), np.linspace(0, height, height)
xi, yi = np.meshgrid(xi, yi)
#interpolate the data with the matlab griddata function
zi = griddata(x, y, z, xi, yi, interp='nn')
#create a matplotlib figure and adjust the width and heights
fig = plt.figure()
#create a single subplot, just takes over the whole figure if only one is specified
ax = fig.add_subplot(111, frameon=False, xticks=[], yticks=[])
#create the contours
CS = plt.contour(xi, yi, zi, linewidths=1, colors='b')
#add a streamplot
dx, dy = np.gradient(zi)
plt.streamplot(xi, yi, dx, dy, color='c', density=1, arrowsize=3)
plt.show()
The result will look like this:
Notice that there are lots of places where the flow lines are not perpendicular to the contours. That's an even easier indicator than the incorrect direction of the arrows that something is going wrong. (Though "perpendicular" assumes an aspect ratio of 1 for the plot, which isn't quite true for these plots unless you set it.)
Fixing the problem
If we just change the line
dx, dy = np.gradient(zi)
to:
dy, dx = np.gradient(zi)
We'll get the correct result:
Interpolation suggestions
On a side note, griddata is a poor choice in this case.
First, it's not a "smooth" interpolation method. It uses delaunay triangulation, which makes "sharp" ridges at triangle boundaries. This leads to anomalous gradients in those locations.
Second, it limits interpolation to the convex hull of your data points, which may or may not be a good choice.
A radial basis function (or any other smooth interpolant) is a much better choice for interpolation.
As an example, if we modify your code snippet to use an RBF:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import Rbf
# Generate data
np.random.seed(1981)
width, height = 200, 300
x, y, z = np.random.random((3,10))
x *= width
y *= height
#create a grid on which to interpolate data
xi, yi = np.mgrid[0:width:1j*width, 0:height:1j*height]
#interpolate the data with the matlab griddata function
interp = Rbf(x, y, z, function='linear')
zi = interp(xi, yi)
#create a matplotlib figure and adjust the width and heights
fig, ax = plt.subplots(subplot_kw=dict(frameon=False, xticks=[], yticks=[]))
#create the contours and streamplot
CS = plt.contour(xi, yi, zi, linewidths=1, colors='b')
dy, dx = np.gradient(zi.T)
plt.streamplot(xi[:,0], yi[0,:], dx, dy, color='c', density=1, arrowsize=3)
plt.show()
(You'll notice the intersections are not quite perpendicular due to the non-equal aspect ratio of the plot. They're all 90 degrees if we set the aspect ratio of the plot to 1, however.)
As a side-by-side comparison of the two methods:
You can specify the arrow style with arrowstyle='->'. Try both of these and see if this works for you:
plt.streamplot(xi, yi, dx, dy, color='c', density=1, arrowsize=3,
arrowstyle='<-')
plt.streamplot(xi, yi, dx, dy, color='c', density=1, arrowsize=3,
arrowstyle='->')