I have a set of 7x4 plots arranged in a grid using subplot. I now want to add diagonal axes on top of these.
I know you can superpose axes on top of previously made subplots by setting the background to 'none':
ax = fig.add_subplot(111)
ax.set_axis_bgcolor('none')
But I can't find a rotated axis thing. Currently I'm trying to use a top view 3D axes, but I'm far from a usable solution there.
I'm willing to accept drawing the axis+ticks by hand, if this is the only way possible.
EDIT: using the floating_axis module, I was able to draw rotated (and sheared) axes, but unable to edit the ticks, which is very necessary for what I need. The following snippet demonstrates adding a floating_axis to an existing figure fig. Any manipulation of the axes' ticks fails.
from matplotlib.transforms import Affine2D
import mpl_toolkits.axisartist.floating_axes as floating_axes
trafo = Affine2D().skew_deg(-10,-10).rotate_deg(45)
grid_helper = floating_axes.GridHelperCurveLinear(trafo, extremes=(0, 4, 0, 4))
artistax = floating_axes.FloatingSubplot(fig, 111, grid_helper=grid_helper)
artistax.set_axis_bgcolor('none')
artistax.axis["top"].set_visible(False)
artistax.axis["right"].set_visible(False)
fig.add_subplot(artistax)
Related
I am trying to create two images side by side: one satellite image alone, and next to it, the same satellite image with outlines of agricultural fields. My raster data "raster_clip" is loaded into rioxarray (original satellite image from NAIP, converted from .sid to .tif), and my vector data "ag_clip" is in geopandas. My code is as follows:
fig, (ax1, ax2) = plt.subplots(ncols = 2, figsize=(14,8))
raster_clip.plot.imshow(ax=ax1)
raster_clip.plot.imshow(ax=ax2)
ag_clip.boundary.plot(ax=ax1, color="yellow")
I can't seem to figure out how to get the y axes in each plot to be the same. When the vector data is excluded, then the two plots end up the same shape and size.
I have tried the following:
Setting sharey=True in the subplots method. Doesn't affect shape of resulting images, just removes the tic labels on the second image.
Setting "aspect='equal'" in the imshow method, leads to an error, which doesn't make sense because the 'aspect' kwarg is listed in the documentation for xarray.plot.imshow.
plt.imshow's 'aspect' kwarg is not available in xarray
Removing the "figsize" variable, doesn't affect the ratio of the two plots.
not entirely related to your question but i've used cartopy before for overlaying a GeoDataFrame to a DataArray
plt.figure(figsize=(16, 8))
ax = plt.subplot(projection=ccrs.PlateCarree())
ds.plot(ax=ax)
gdf.plot(ax=ax)
i use jupyterlab together with matplotlib widgets. I have ipywidgets installed.
My goal is to choose which y-axis data is displayed in the bottom of the figure.
When i use the interactive tool to see the coordinates i get only the data of the right y-axis displayed. Both would be really nice^^ My minimal code example:
import matplotlib.pyplot as plt
import numpy as np
%matplotlib widgets
x=np.linspace(0,100)
y=x**2
y2=x**3
fig,ax=plt.subplots()
ax2=ax.twinx()
ax.plot(x,y)
ax2.plot(x,y2)
plt.show()
With this example you might ask why not to plot them to the same y-axis but thats why it is a minimal example. I would like to plot data of different units.
To choose which y-axis is used, you can set the zorder property of the axes containing this y-axis to a higher value than that of the other axes (0 is the default):
ax.zorder = 1
However, that will cause this Axes to obscure the other Axes. To counteract this, use
ax.set_facecolor((0, 0, 0, 0))
to make the background color of this Axes transparent.
Alternatively, use the grab_mouse function of the figure canvas:
fig.canvas.grab_mouse(ax)
See here for the (minimal) documentation for grab_mouse.
The reason this works is this:
The coordinate line shown below the figure is obtained by an event callback which ultimately calls matplotlib.Axes.format_coord() on the axes instance returned by the inaxes property of the matplotlib events that are being generated by your mouse movement. This Axes is the one returned by FigureCanvasBase.inaxes() which uses the Axes zorder, and in case of ties, chooses the last Axes created.
However, you can tell the figure canvas that one Axes should receive all mouse events, in which case this Axes is also set as the inaxes property of generated events (see the code).
I have not found a clean way to make the display show data from both Axes. The only solution I have found would be to monkey-patch NavigationToolbar2._mouse_event_to_message (also here) to do what you want.
Problem Statement
I'm attempting to add two patches -- a rectangle patch and a text patch -- to the same space within a 3D plot. The ultimate goal is to annotate the rectangle patch with a corresponding value (about 20 rectangles across 4 planes -- see Figure 3). The following code does not get all the way there, but does demonstrate a rendering issue where sometimes the text patch is completely visible and sometimes it isn't -- interestingly, if the string doesn't extend outside the rectangle patch, it never seems to become visible at all. The only difference between Figures 1 and 2 is the rotation of the plot viewer image. I've left the cmap code in the example below because it's a requirement of the project (and just in case it affects the outcome).
Things I've Tried
Reversing the order that the patches are drawn.
Applying zorder values -- I think art3d.pathpatch_2d_to_3d is overriding that.
Creating a patch collection -- I can't seem to find a way to add the rectangle patch and the text patch to the same 3D collection.
Conclusion
I suspect that setting zorder to each patch before adding them to a 3D collection may be the solution, but I can't seem to find a way to get to that outcome. Similar questions suggest this, but I haven't been able to apply their answers to this problem specifically.
Environment
macOS: Big Sur 11.2.3
Python 3.8
Matplotlib 3.3.4
Figure 1
Figure 2
Figure 3
The Code
Generates Figures 1 and 2 (not 3).
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
from matplotlib.patches import Rectangle, PathPatch
from matplotlib.text import TextPath
from matplotlib.transforms import Affine2D
import mpl_toolkits.mplot3d.art3d as art3d
import matplotlib.pyplot as plt
from matplotlib.colors import Normalize
plt.style.use('dark_background')
fig = plt.figure()
ax = fig.gca(projection='3d')
cmap = plt.cm.bwr
norm = Normalize(vmin=50, vmax=80)
base_color = cmap(norm(50))
# Draw box
box = Rectangle((25, 25), width=50, height=50, color=cmap(norm(62)), ec='black', alpha=1)
ax.add_patch(box)
art3d.pathpatch_2d_to_3d(box, z=1, zdir="z")
# Draw text
text_path = TextPath((60, 50), "xxxx", size=10)
trans = Affine2D().rotate(0).translate(0, 1)
p1 = PathPatch(trans.transform_path(text_path))
ax.add_patch(p1)
art3d.pathpatch_2d_to_3d(p1, z=1, zdir="z")
ax.set_xlabel('x')
ax.set_xlim(0, 100)
ax.set_xticklabels([])
ax.xaxis.set_pane_color(base_color)
ax.set_ylabel('y')
ax.set_ylim(0, 100)
ax.set_yticklabels([])
ax.yaxis.set_pane_color(base_color)
ax.set_zlabel('z')
ax.set_zlim(1, 4)
ax.set_zticks([1, 2, 3, 4])
ax.zaxis.set_pane_color(base_color)
ax.set_zticklabels([])
plt.show()
This is a well-known problem with matplotlib 3D plotting: objects are drawn in a particular order, and those plotted last appear on "top" of the others, regardless of which should be in front in a "true" 3D plot.
See the FAQ here: https://matplotlib.org/mpl_toolkits/mplot3d/faq.html#my-3d-plot-doesn-t-look-right-at-certain-viewing-angles
My 3D plot doesn’t look right at certain viewing angles
This is probably the most commonly reported issue with mplot3d. The problem is that – from some viewing angles – a 3D object would appear in front of another object, even though it is physically behind it. This can result in plots that do not look “physically correct.”
Unfortunately, while some work is being done to reduce the occurrence of this artifact, it is currently an intractable problem, and can not be fully solved until matplotlib supports 3D graphics rendering at its core.
The problem occurs due to the reduction of 3D data down to 2D + z-order scalar. A single value represents the 3rd dimension for all parts of 3D objects in a collection. Therefore, when the bounding boxes of two collections intersect, it becomes possible for this artifact to occur. Furthermore, the intersection of two 3D objects (such as polygons or patches) can not be rendered properly in matplotlib’s 2D rendering engine.
This problem will likely not be solved until OpenGL support is added to all of the backends (patches are greatly welcomed). Until then, if you need complex 3D scenes, we recommend using MayaVi.
I'm having trouble giving colorbars to a grid of line plots in Matplotlib.
I have a grid of plots, which each shows 64 lines. The lines depict the penalty value vs time when optimizing the same system under 64 different values of a certain hyperparameter h.
Since there are so many lines, instead of using a standard legend, I'd like to use a colorbar, and color the lines by the value of h. In other words, I'd like something that looks like this:
The above was done by adding a new axis to hold the colorbar, by calling figure.add_axes([0.95, 0.2, 0.02, 0.6]), passing in the axis position explicitly as parameters to that method. The colorbar was then created as in the example code here, by instantiating a ColorbarBase(). That's fine for single plots, but I'd like to make a grid of plots like the one above.
To do this, I tried doubling the number of subplots, and using every other subplot axis for the colorbar. Unfortunately, this led to the colorbars having the same size/shape as the plots:
Is there a way to shrink just the colorbar subplots in a grid of subplots like the 1x2 grid above?
Ideally, it'd be great if the colorbar just shared the same axis as the line plot it describes. I saw that the colorbar.colorbar() function has an ax parameter:
ax
parent axes object from which space for a new colorbar axes will be stolen.
That sounds great, except that colorbar.colorbar() requires you to pass in a imshow image, or a ContourSet, but my plot is neither an image nor a contour plot. Can I achieve the same (axis-sharing) effect using ColorbarBase?
It turns out you can have different-shaped subplots, so long as all the plots in a given row have the same height, and all the plots in a given column have the same width.
You can do this using gridspec.GridSpec, as described in this answer.
So I set the columns with line plots to be 20x wider than the columns with color bars. The code looks like:
grid_spec = gridspec.GridSpec(num_rows,
num_columns * 2,
width_ratios=[20, 1] * num_columns)
colormap_type = cm.cool
for (x_vec_list,
y_vec_list,
color_hyperparam_vec,
plot_index) in izip(x_vec_lists,
y_vec_lists,
color_hyperparam_vecs,
range(len(x_vecs))):
line_axis = plt.subplot(grid_spec[grid_index * 2])
colorbar_axis = plt.subplot(grid_spec[grid_index * 2 + 1])
colormap_normalizer = mpl.colors.Normalize(vmin=color_hyperparam_vec.min(),
vmax=color_hyperparam_vec.max())
scalar_to_color_map = mpl.cm.ScalarMappable(norm=colormap_normalizer,
cmap=colormap_type)
colorbar.ColorbarBase(colorbar_axis,
cmap=colormap_type,
norm=colormap_normalizer)
for (line_index,
x_vec,
y_vec) in zip(range(len(x_vec_list)),
x_vec_list,
y_vec_list):
hyperparam = color_hyperparam_vec[line_index]
line_color = scalar_to_color_map.to_rgba(hyperparam)
line_axis.plot(x_vec, y_vec, color=line_color, alpha=0.5)
For num_rows=1 and num_columns=1, this looks like:
I am trying to draw something similar:
The main idea is to draw ellipses with different color in some specific range, for example from [-6, 6].
I have understood that plt.contour function can be used. But I do not understand how to generate lines.
I personally wouldn't do this with contour as you then need to add information about the elevation which I don't think you want?
matplotlib has Ellipse which is a subclass of Artist. The following example adds a single ellipse to a plot.
import matplotlib as mpl
ellipse = mpl.patches.Ellipse(xy=(0, 0), width=2.0, height=1.0)
fig, ax = plt.subplots()
fig.gca().add_artist(ellipse)
ax.set_aspect('equal')
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
You then need to research how to get the effect you are looking for, I would have a read of the docs in general making things transparent is done through alpha.