I am trying to reproduce a plot like this:
So the requirements are actually that the grid (that is to be present just on the left side) behaves just like a grid, that is, if we zoom in and out, it is always there present and not dependent on specific x-y limits for the actual data.
Unfortunately there is no diagonal version of axhline/axvline (open issue here) so I was thinking about using the grid from polar plots.
So for that I have two problems:
This answer shows how to overlay a polar axis on top of a rectangular one, but it does not match the origins and x-y values. How can I do that?
I also tried the suggestion from this answer for having polar plots using ax.set_thetamin/max but I get an AttributeError: 'AxesSubplot' object has no attribute 'set_thetamin' How can I use these functions?
This is the code I used to try to add a polar grid to an already existing rectangular plot on ax axis:
ax_polar = fig.add_axes(ax, polar=True, frameon=False)
ax_polar.set_thetamin(90)
ax_polar.set_thetamax(270)
ax_polar.grid(True)
I was hoping I could get some help from you guys. Thanks!
The mpl_toolkits.axisartist has the option to plot a plot similar to the desired one. The following is a slightly modified version of the example from the mpl_toolkits.axisartist tutorial:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cbook as cbook
from mpl_toolkits.axisartist import SubplotHost, ParasiteAxesAuxTrans
from mpl_toolkits.axisartist.grid_helper_curvelinear import GridHelperCurveLinear
import mpl_toolkits.axisartist.angle_helper as angle_helper
from matplotlib.projections import PolarAxes
from matplotlib.transforms import Affine2D
# PolarAxes.PolarTransform takes radian. However, we want our coordinate
# system in degree
tr = Affine2D().scale(np.pi/180., 1.) + PolarAxes.PolarTransform()
# polar projection, which involves cycle, and also has limits in
# its coordinates, needs a special method to find the extremes
# (min, max of the coordinate within the view).
# 20, 20 : number of sampling points along x, y direction
extreme_finder = angle_helper.ExtremeFinderCycle(20, 20,
lon_cycle=360,
lat_cycle=None,
lon_minmax=None,
lat_minmax=(0, np.inf),)
grid_locator1 = angle_helper.LocatorDMS(36)
tick_formatter1 = angle_helper.FormatterDMS()
grid_helper = GridHelperCurveLinear(tr,
extreme_finder=extreme_finder,
grid_locator1=grid_locator1,
tick_formatter1=tick_formatter1
)
fig = plt.figure(1, figsize=(7, 4))
fig.clf()
ax = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper)
# make ticklabels of right invisible, and top axis visible.
ax.axis["right"].major_ticklabels.set_visible(False)
ax.axis["right"].major_ticks.set_visible(False)
ax.axis["top"].major_ticklabels.set_visible(True)
# let left axis shows ticklabels for 1st coordinate (angle)
ax.axis["left"].get_helper().nth_coord_ticks = 0
# let bottom axis shows ticklabels for 2nd coordinate (radius)
ax.axis["bottom"].get_helper().nth_coord_ticks = 1
fig.add_subplot(ax)
## A parasite axes with given transform
## This is the axes to plot the data to.
ax2 = ParasiteAxesAuxTrans(ax, tr)
## note that ax2.transData == tr + ax1.transData
## Anything you draw in ax2 will match the ticks and grids of ax1.
ax.parasites.append(ax2)
intp = cbook.simple_linear_interpolation
ax2.plot(intp(np.array([150, 230]), 50),
intp(np.array([9., 3]), 50),
linewidth=2.0)
ax.set_aspect(1.)
ax.set_xlim(-12, 1)
ax.set_ylim(-5, 5)
ax.grid(True, zorder=0)
wp = plt.Rectangle((0,-5),width=1,height=10, facecolor="w", edgecolor="none")
ax.add_patch(wp)
ax.axvline(0, color="grey", lw=1)
plt.show()
Related
I have been experimenting with geopandas and some data exported from openstreetmaps as part of a community project I am working on. To trial the data visualisation side I have taken an export of a section of town and plotted it using geopandas. This worked except that regardless of the order I plot the layers the road layers will appear on top of everything else.
I have tried googling a solution and everythign indicates that the order you perform the plots should dictate the layering, with the last thing to be plotted appearing on top of everything else. Applying this logic the road lines should be underneath the buidings and the spoofed gps location points? can someone please advise how I fix this such that I can control which layer is on top?
import matplotlib.pyplot as plt
import geopandas as gpd
import numpy as np
# import and clean up map data
derby_roads = gpd.read_file('map/roads.geojson')
derby_roads_clean = derby_roads[derby_roads.highway.str.contains('motorway|trunk|primary|secondary|tertiary|residential')]
derby_buildings = gpd.read_file('map/buildings.geojson')
#print(derby_roads_clean)
#print(derby_buildings.head())
# generate random gps data
# min x -1.4879, max x -1.4407
# min y 52.8801, max y 52.8962
points_x = np.random.uniform(-1.4879, -1.4407, size = (50,))
points_y = np.random.uniform(52.8801, 52.8962, size = (50,))
points_z = np.random.uniform(0, 100, size = (50,))
gdf = gpd.GeoDataFrame(points_z, geometry=gpd.points_from_xy(points_x,points_y))
print(gdf.head())
# Create Matplotlib figure
fig, ax = plt.subplots()## configure axis
ax.set_aspect('equal')
ax.set_frame_on(False)
ax.get_xaxis().set_ticks([])
ax.get_xaxis().set_ticklabels([])
ax.get_yaxis().set_ticks([])
ax.get_yaxis().set_ticklabels([])
# plot map data
derby_roads.plot(ax=ax, color='#e6e6e6')
derby_roads_clean.plot(ax=ax, color='grey')
derby_buildings.plot(ax=ax, color='#000000')
gdf.plot(ax=ax, color='red')
#mng = plt.get_current_fig_manager()
#mng.full_screen_toggle()
plt.tight_layout()
plt.show()
You have specify zorder. (https://matplotlib.org/3.1.1/gallery/misc/zorder_demo.html)
In your case it should be like this
derby_roads.plot(ax=ax, color='#e6e6e6', zorder=1)
derby_roads_clean.plot(ax=ax, color='grey', zorder=2)
derby_buildings.plot(ax=ax, color='#000000', zorder=3)
gdf.plot(ax=ax, color='red', zorder=4)
I'm trying to plot projections of coordinates onto a line, but for some reason, Matplotlib is plotting the projections in a slightly slanted manner. Ideally, I would like the (blue) projections to be perpendicular to the (green) line. Here's an image of how it looks with sample data:
As you can see, the angles between the blue lines and the green line are slightly obtuse instead of right. I tried playing around with the rotation parameter to the annotate function, but this did not help. The code for this plot is below, although the data might look a bit different since the random generator is not seeded:
import numpy as np
import matplotlib.pyplot as plt
prefs = {'color':'purple','edgecolors':'black'}
X = np.dot(np.random.rand(2,2), np.random.rand(2,50)).T
pts = np.linspace(-1,1)
v1_m = 0.8076549717643662
plt.scatter(X[:,0],X[:,1],**prefs)
plt.plot(pts, [v1_m*x for x in pts], color='lightgreen')
for x,y in X:
# slope of connecting line
# y = mx+b
m = -np.reciprocal(v1_m)
b = y-m*x
# find intersecting point
zx = b/(v1_m-m)
zy = v1_m*zx
# draw line
plt.annotate('',(zx,zy),(x,y),arrowprops=dict(linewidth=2,arrowstyle='-',color='lightblue'))
plt.show()
The problem lies in the unequal axes which makes it look like they are not at a right angle. Use plt.axis('equal') to have equal axis spans on x- and y-axis and a square figure with equal height and width. plt.axis('scaled') works the same way. As pointed out by #CedricZoppolo, you should set the equal aspect ratios before plt.show(). As per docs, setting the aspect ratio to "equal" means
same scaling from data to plot units for x and y
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8,8))
# Your code here
plt.axis('equal')
plt.show()
Choosing a square figure is not necessary as it works also with rectangular figures as
fig = plt.figure(figsize=(8,6))
# Your code here
plt.axis('equal')
plt.show()
The blue lines not being perpendicular is due to axis not being equal.
You just need to add below line before plt.show()
plt.gca().set_aspect('equal')
Below you can see the resulted graph:
I'm creating a plot using python 3.5.1 and matplotlib 1.5.1 that has two subplots (side by side) with a shared Y axis. A sample output image is shown below:
Notice the extra white space at the top and bottom of each set of axes. Try as I might I can't seem to get rid of it. The overall goal of the figure is to have a waterfall type plot on the left with a shared Y axes with the plot on the right.
Here's some sample code to reproduce the image above.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
%matplotlib inline
# create some X values
periods = np.linspace(1/1440, 1, 1000)
# create some Y values (will be datetimes, not necessarily evenly spaced
# like they are in this example)
day_ints = np.linspace(1, 100, 100)
days = pd.to_timedelta(day_ints, 'D') + pd.to_datetime('2016-01-01')
# create some fake data for the number of points
points = np.random.random(len(day_ints))
# create some fake data for the color mesh
Sxx = np.random.random((len(days), len(periods)))
# Create the plots
fig = plt.figure(figsize=(8, 6))
# create first plot
ax1 = plt.subplot2grid((1,5), (0,0), colspan=4)
im = ax1.pcolormesh(periods, days, Sxx, cmap='viridis', vmin=0, vmax=1)
ax1.invert_yaxis()
ax1.autoscale(enable=True, axis='Y', tight=True)
# create second plot and use the same y axis as the first one
ax2 = plt.subplot2grid((1,5), (0,4), sharey=ax1)
ax2.scatter(points, days)
ax2.autoscale(enable=True, axis='Y', tight=True)
# Hide the Y axis scale on the second plot
plt.setp(ax2.get_yticklabels(), visible=False)
#ax1.set_adjustable('box-forced')
#ax2.set_adjustable('box-forced')
fig.colorbar(im, ax=ax1)
As you can see in the commented out code I've tried a number of approaches, as suggested by posts like https://github.com/matplotlib/matplotlib/issues/1789/ and Matplotlib: set axis tight only to x or y axis.
As soon as I remove the sharey=ax1 part of the second subplot2grid call the problem goes away, but then I also don't have a common Y axis.
Autoscale tends to add a buffer to the data so that all of the data points are easily visible and not part-way cut off by the axes.
Change:
ax1.autoscale(enable=True, axis='Y', tight=True)
to:
ax1.set_ylim(days.min(),days.max())
and
ax2.autoscale(enable=True, axis='Y', tight=True)
to:
ax2.set_ylim(days.min(),days.max())
To get:
For example the orientation of histogram in the picture below is (2,-2)
Use transformations. Since you did not provide any code that would plot the non-rotated picture, I'm using a simple example:
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy
n = numpy.random.normal(size=10000)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_aspect(1)
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
base_trans = ax.transData
tr = matplotlib.transforms.Affine2D().rotate_deg(-30) + base_trans
ax.hist(n, normed=True, transform=tr, bins=20)
fig.savefig('t.png')
Notes:
I do not know what you mean by a "direction given by a tuple". In your picture the axes are clearly not just rotated, but moved as well (the (0,0) point is not on the x-axis). I only used rotation in this example; see docs for Affine2D for more transformation properties.
In order for your graph to not look skewed, you must match the plot's aspect ratio, x/y limits, and the transformation's scaling coefficients. In the example I used the aspect 1 and the same scale for x and y axes, so I could just use the rotate_deg() method without any additional corrections.
I am not able to draw a simple, vertical arrow in the following log-log plot:
#!/usr/bin/python2
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.yscale('log')
plt.xscale('log')
plt.ylim((1e-20,1e-10))
plt.xlim((1e-12,1))
plt.arrow(0.00006666, 1e-20, 0, 1e-8 - 1e-20, length_includes_head=True)
plt.savefig('test.pdf')
It just doesn't show. From the documentation it appears as if all the arguments, like width, height and so on relate to the scale of the axis. This is very counter-intuitive. I tried using twin() of the axisartist package to define an axis on top of mine with limits (0,1), (0,1) to have more control over the arrow's parameters, but I couldn't figure out how to have a completely independent axis on top of the primary one.
Any ideas?
I was looking for an answer to this question, and found a useful answer! You can specify any "mathtext" character (matplotlib's version of LaTeX) as a marker. Try:
plt.plot(x,y, 'ko', marker=r'$\downarrow$', markersize=20)
This will plot a downward pointing, black arrow at position (x,y) that looks good on any plot (even log-log).
See: matplotlib.org/users/mathtext.html#mathtext-tutorial for more symbols you can use.
Subplots approach
After creating the subplots do the following
Align the positions
Use set_axis_off() to turn the axis off (ticks, labels, etc)
Draw the arrow!
So a few lines gets whats you want!
E.g.
#!/usr/bin/python2
import matplotlib.pyplot as plt
hax = plt.subplot(1,2,1)
plt.yscale('log')
plt.xscale('log')
plt.ylim((1e-20,1e-10))
plt.xlim((1e-12,1))
hax2 = plt.subplot(1,2,2)
plt.arrow(0.1, 1, 0, 1, length_includes_head=True)
hax.set_position([0.1, 0.1, 0.8, 0.8])
hax2.set_position([0.1, 0.1, 0.8, 0.8])
hax2.set_axis_off()
plt.savefig('test.pdf')
Rescale data
Alternatively a possibly easier approach, though the axis labels may be tricky, is to rescale the data.
i.e.
import numpy
# Other import commands and data input
plt.plot(numpy.log10(x), numpy.log10(y)))
Not a great solution, but a decent result if you can handle the tick labels!
I know this thread has been dead for a long time now, but I figure posting my solution might be helpful for anyone else trying to figure out how to draw arrows on log-scale plots efficiently.
As an alternative to what others have already posted, you could use a transformation object to input the arrow coordinates not in the scale of the original axes but in the (linear) scale of the "axes coordinates". What I mean by axes coordinates are those that are normalized to [0,1] (horizontal range) by [0,1] (vertical range), where the point (0,0) would be the bottom-left corner and the point (1,1) would be the top-right, and so on. Then you could simply include an arrow by:
plt.arrow(0.1, 0.1, 0.9, 0.9, transform=plot1.transAxes, length_includes_head=True)
This gives an arrow that spans diagonally over 4/5 of the plot's horizontal and vertical range, from the bottom-left to the top-right (where plot1 is the subplot name).
If you want to do this in general, where exact coordinates (x0,y0) and (x1,y1) in the log-space can be specified for the arrow, this is not too difficult if you write two functions fx(x) and fy(y) that transform from the original coordinates to these "axes" coordinates. I've given an example of how the original code posted by the OP could be modified to implement this below (apologies for not including the images the code produces, I don't have the required reputation yet).
#!/usr/bin/python3
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
# functions fx and fy take log-scale coordinates to 'axes' coordinates
ax = 1E-12 # [ax,bx] is range of horizontal axis
bx = 1E0
def fx(x):
return (np.log(x) - np.log(ax))/(np.log(bx) - np.log(ax))
ay = 1E-20 # [ay,by] is range of vertical axis
by = 1E-10
def fy(y):
return (np.log(y) - np.log(ay))/(np.log(by) - np.log(ay))
plot1 = plt.subplot(111)
plt.xscale('log')
plt.yscale('log')
plt.xlim(ax, bx)
plt.ylim(ay, by)
# transformed coordinates for arrow from (1E-10,1E-18) to (1E-4,1E-16)
x0 = fx(1E-10)
y0 = fy(1E-18)
x1 = fx(1E-4) - fx(1E-10)
y1 = fy(1E-16) - fy(1E-18)
plt.arrow(
x0, y0, x1, y1, # input transformed arrow coordinates
transform = plot1.transAxes, # tell matplotlib to use axes coordinates
facecolor = 'black',
length_includes_head=True
)
plt.grid(True)
plt.savefig('test.pdf')