Trim figure (Matplotlib, 3D) - python

fig = plt.figure()
ax = Axes3D(fig, auto_add_to_figure=False)
fig.add_axes(ax)
ax.plot([0, 1], [1, 0], [-1, 1])
yields (borders added manually)
Adding
for anm in ('x', 'y', 'z'):
getattr(ax, f'set_{anm}ticks')([])
getattr(ax, f'w_{anm}axis').set_pane_color((1, 1, 1, 0))
getattr(ax, f'w_{anm}axis').line.set_color((1, 1, 1, 0))
getattr(ax, f'set_{anm}margin')(0)
I get what's on left, but I seek what's on right
How can this be accomplished? Full code
I seek a native solution (no save-stage post-processing, or third party libraries), as the figure is processed further natively in matplotlib (specifically in an animation). Ideally the solution is general-purpose, i.e. not specific to Axes3D.
To test the answer, it should produce the following output in full context code, as a .mp4 (I got this output with manual post-processing):

Related

Sizing figure with variable number of subplots and 2 legends

I'm having a really hard time attempting to properly size a figure with a variable number of subplots (between 3 and 8) and 2 legends that should appear glued to each other.
I also checked every related issue here in stack overflow, but couldn't get any answer to this specific case, due to my need for 2 legends.
The important to me is to get an optimal figure that I save as pdf to include in a report. I tried everything, and in the end the closes I got was with using tight: fig.savefig(f'variations{len(list_variations)}_B.pdf', bbox_inches='tight').
Here is a fully reproducible example (that emulates my code and figures):
list_variations = [0, 1, 2, 3, 4, 5, 6, 7, 8] # Does not work for any option
list_variations = [0, 1, 2] # Works Fine for Option A
n_subplots = len(list_variations)
fig_size = (5.457, n_subplots*3.5/3)
fig, axs = plt.subplots(n_subplots, 1, figsize=fig_size, sharex=True, sharey=True)
labels_upp = ('abdications', 'liner wint.ol.:$\\pm$0.19e', 'liner wint.ol.:$\\pm$0.1e')
labels_low = ('apportions', 'bisections', 'ablations', 'saktis')
for idx in list_variations:
for i, lab_upp in enumerate(labels_upp):
axs[idx].plot(60+i, 0.2, label=lab_upp)
for lab_low in labels_low:
axs[idx].plot(60+i, -0.2, label=lab_low)
axs[idx].set_title(f'Variation {idx}', fontsize=8)
axs[-1].set_xlim((60, 80))
axs[-1].set(ylim=(-1, 1))
axs[-1].set(xlabel='elasticity (e)')
plt.subplots_adjust(hspace=0.25)
# Make Legends (PROBLEM IS HERE)
# Option A - relative to fig
props_leg_upp = dict(facecolor='white', bbox_to_anchor=(0, -0.102, 1, 0.1), mode='expand', loc='upper center')
props_leg_low = dict(facecolor='lightgrey', bbox_to_anchor=(0, -0.172, 1, 0.1), mode='expand', loc='upper center')
upper_leg = fig.legend(labels_upp, ncol=len(labels_upp), **props_leg_upp)
lower_leg = fig.legend(labels_low, ncol=len(labels_low), **props_leg_low)
axs[-1].add_artist(upper_leg)
# Option B - relative to axs[-1]
props_leg_upp = dict(facecolor='white', bbox_to_anchor=(0, -0.262, 1, 0.1), mode='expand', loc='upper center')
props_leg_low = dict(facecolor='lightgrey', bbox_to_anchor=(0, -0.322, 1, 0.1), mode='expand', loc='upper center')
upper_leg = axs[-1].legend(labels_upp, ncol=len(labels_upp), **props_leg_upp)
lower_leg = axs[-1].legend(labels_low, ncol=len(labels_low), **props_leg_low)
axs[-1].add_artist(upper_leg)
I tried every combination of matplotlib.legend properties that I could think of, and in the end I got to these 2 options: A-apply the legend to figure; B-apply the legend to the last axis.
Option A works pretty well for 3 subplots:
In Option B (adding the legend to last axis), that I tried to force the legend to be the same width of the axis, the legends appear on top of each other (although I tried to finetune the bbox_to_anchor properties).
Yet, the biggest problem is when I use a greater number of subplots (e.g. 9 which is the maximum). For these case none of the options work.
Option A:
Option B:
Is there any way that I can make it work for different numbers of subplots, while (ideally) keeping the width of the legends the same as the width of the axis?
To align the legend in the subplot, I would need to set the transform coordinate axis of the legend box. In this case, the settings are added to match the last axis of the subplot. The box values were adjusted manually.
Since the box value parameters are bbox_to_anchor=(x0,y0,x1,y1), in this case y0,y1 are the same value.
import matplotlib.pyplot as plt
list_variations = [0, 1, 2, 3, 4, 5, 6, 7, 8] # Does not work for any option
#list_variations = [0, 1, 2] # Works Fine for Option A
n_subplots = len(list_variations)
fig_size = (5.457, n_subplots*3.5/3)
fig, axs = plt.subplots(n_subplots, 1, figsize=fig_size, sharex=True, sharey=True)
labels_upp = ('abdications', 'liner wint.ol.:$\\pm$0.19e', 'liner wint.ol.:$\\pm$0.1e')
labels_low = ('apportions', 'bisections', 'ablations', 'saktis')
for idx in list_variations:
for i, lab_upp in enumerate(labels_upp):
axs[idx].plot(60+i, 0.2, label=lab_upp)
for lab_low in labels_low:
axs[idx].plot(60+i, -0.2, label=lab_low)
axs[idx].set_title(f'Variation {idx}', fontsize=8)
axs[-1].set_xlim((60, 80))
axs[-1].set(ylim=(-1, 1))
axs[-1].set(xlabel='elasticity (e)')
plt.subplots_adjust(hspace=0.25)
# Make Legends (PROBLEM IS HERE)
# # Option A - relative to fig
props_leg_upp = dict(facecolor='white', bbox_to_anchor=(-0.1, -0.350, 1.2, 0.-0.350), mode='expand', loc='upper center')
props_leg_low = dict(facecolor='lightgrey', bbox_to_anchor=(-0.1, -0.650, 1.2, -0.650), mode='expand', loc='upper center')
upper_leg = fig.legend(labels_upp, ncol=len(labels_upp), bbox_transform=axs[-1].transAxes, **props_leg_upp)
lower_leg = fig.legend(labels_low, ncol=len(labels_low), bbox_transform=axs[-1].transAxes, **props_leg_low)
axs[-1].add_artist(upper_leg)
plt.show()
If you enable the following: list_variations = [0, 1, 2]

How to use a custom colormap with irregular intervals?

I'm trying to use a custom colorbar in matplotlib with irregular intervals.
But when following the tutorial and using the colorbar, it gets used as a regular interval colorbar.
How do I construct/use a colorbar with irregular intervals?
MWE below:
I'm plotting various data with plt.matshow(), like this:
testdf = pd.DataFrame([
(7, 7.1, 8 , 9),
(0, 1, 1.5, 2),
(2.001, 3, 3.5, 4),
(4.001, 5, 6, 6.9999),
], index=[0, 1, 2, 3], columns=('A', 'B', 'C', 'D'),)
and
plt.matshow(testdf)
However, I want only certain numbers highlighted and I want to group others, i.e. I want a discrete, custom colorbar, instead of the default continuous one.
Luckily, the matplotlib documentation has just what I need.
So, lets set up this colorbar:
fig, ax = plt.subplots(figsize=(6, 1))
fig.subplots_adjust(bottom=0.5)
cmap = (mpl.colors.ListedColormap(['red', 'green', 'blue', 'cyan'])
.with_extremes(over='0.25', under='0.75'))
bounds = [1, 2, 4, 7, 8]
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
fig.colorbar(
mpl.cm.ScalarMappable(cmap=cmap, norm=norm),
cax=ax,
boundaries=[0] + bounds + [13], # Adding values for extensions.
extend='both',
ticks=bounds,
spacing='proportional',
orientation='horizontal',
label='Discrete intervals, some other units',
)
Looks great! Numbers from 1 to 2 in red and 7 to 8 in blue and two large groups for all the uninteresting stuff between 2 and 7.
So, let's use it.
plt.matshow(testdf, cmap=cmap)
plt.colorbar()
...and that's not what I expected.
The colorbar should look like the one I just constructed before, not regularly spaced and thus lines 0 and 1 should contain a black/grey box for over/under, line 2 should be all green and line 3 all blue.
How do I fix this? What am I missing?
As Jody Klymak and JohanC have pointed out in the comments, norm also needs to be passed into matshow, i.e. plt.matshow(testdf, cmap=cmap, norm=norm).
However, this doesn't work for stuff where I can't pass further arguments with my colormap (or where I can't figure out how to do so…), e.g. in sns.clustermap.
A possible workaround is to define a colormap with regular intervals, with many following intervals being of the same color:
fig, ax = plt.subplots(figsize=(6, 1))
fig.subplots_adjust(bottom=0.5)
cmap = (mpl.colors.ListedColormap(['red',
'green', 'green',
'blue', 'blue', 'blue', 'blue',
'cyan'])
.with_extremes(over='0.25', under='0.75'))
bounds = [1, 2, 3, 4, 5, 6, 7, 8]
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
fig.colorbar(
mpl.cm.ScalarMappable(cmap=cmap, norm=norm),
cax=ax,
boundaries=[0] + bounds + [13], # Adding values for extensions.
extend='both',
ticks=bounds,
spacing='proportional',
orientation='horizontal',
label='Discrete intervals, some other units',
)
Results in
and following that
As you can see, it's still different from what I expected in the question, but turns out that the limits are different from what I expected them to be, i.e. the interval 1, 2 means >1, <2which is easily fixable, if you know/expect this behaviour.

Interacting with histogram colorbars in physt polar_map

Using the physt library you can create a polar histogram of data that automatically returns a colorbar, such as:
from physt.histogram_nd import Histogram2D
# Histogram2D([radian_bins, angular_bins], [histogram values for given bins])
hist = Histogram2D([[0, 0.5, 1], [0, 1, 2, 3]], [[0.2, 2.2, 7.3], [6, 5, 3]])
ax = hist.plot.polar_map(cmap = 'viridis', show_zero=False)
I can't link an image of this output as I don't yet have enough reputation it seems.
The colorbar is created and looks great but has no label whatsoever. Is there some keyword or arguement I can use in the polar_map function to:
Label my colorbar or
extract the colorbar object so I can use established functions such as:
cbar.ax.set_ylabel("colorbar name")
A tutorial exists (https://physt.readthedocs.io/en/latest/tutorial.html) for this library but it doesn't really interact with the colorbar anywhere in the tutorial
You can indeed extract the colorbar object with ax.get_figure():
from physt.histogram_nd import Histogram2D
#
# Histogram2D([radian_bins, angular_bins], [histogram values for given bins])
hist = Histogram2D([[0, 0.5, 1], [0, 1, 2, 3]], [[0.2, 2.2, 7.3], [6, 5, 3]])
ax = hist.plot.polar_map(cmap = 'viridis', show_zero=False)
fig = ax.get_figure()
fig.axes[1].set_ylabel("colorbar name")
Resulting figure:

Matplotlib 3D scatter giving confusing error message

I want to set the color of different scatters and here comes the error, as is shown in the following code:
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
points = np.array([[1,2,3]])
labels = np.array([1])
colors = [[255, 0, 0],[0, 255, 0],[0, 0, 255], [255, 255, 0],[255, 0, 255],[0,255,255],[128,255,128]]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for i in range(0,len(points)):
print('point and label')
print(points[i], labels[i])
color = colors[labels[i]-1]
print([0,0,0])
ax.scatter(points[i,0], points[i,1],zs=points[i,2],c=[0,0,0]) # work
print(color)
ax.scatter(points[i,0], points[i,1],zs=points[i,2],c=color) # error
print('finish')
plt.savefig('a.jpg',format='jpg')
The problem is that, if I set the c of the ax.scatter as [0,0,0], it works. However, if I set it to a list chosen from the colors I defined, it reports errors.
The complete print message is shown as follows (including the error message):
point and label
(array([1, 2, 3]), 1)
[0, 0, 0]
[255, 0, 0]
Traceback (most recent call last):
File "plot.py", line 47, in <module>
ax.scatter(points[i,0], points[i,1],zs=points[i,2],c=color) # error
File "mypath/local/lib/python2.7/site-packages/mpl_toolkits/mplot3d/axes3d.py", line 2362, in scatter
xs, ys, s=s, c=c, *args, **kwargs)
File "mypath/local/lib/python2.7/site-packages/matplotlib/__init__.py", line 1867, in inner
return func(ax, *args, **kwargs)
File "mypath/local/lib/python2.7/site-packages/matplotlib/axes/_axes.py", line 4293, in scatter
.format(c.shape, x.size, y.size))
AttributeError: 'list' object has no attribute 'shape'
What's wrong with my code and how to set the color of 3D scatter?
Thank you!
I cannot reproduce your error using matplotlib 3.0.1. However, here are a few suggestions.
First, matplotlib expects RGB[A] values to be in the range 0–1 and not 0–255
Second, do you really need to process your points in a loop? Your code could be simplified to a one line call to scatter:
points = np.random.random(size=(7,3))
colors = np.array([[1, 0, 0],[0, 1, 0],[0, 0, 1], [1, 1, 0],[1, 0, 1],[0,1,1],[0.5,1,0.5]])
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(points[:,0], points[:,1], zs=points[:,2], c=colors, s=100)
Third, if you have to pass points one at a time, you should have received a warning like:
'c' argument looks like a single numeric RGB or RGBA sequence, which
should be avoided as value-mapping will have precedence in case its
length matches with 'x' & 'y'. Please use a 2-D array with a single
row if you really want to specify the same RGB or RGBA value for all
points.
As it clearly states, when passing a single color, you should still use a 2D array to specify the color, i.e. ax.scatter(x,y,zs=z,c=[[0,0,0]])

How to draw more type of lines in matplotlib

There are only 4 types of line style in matplotlib: ['--', '-.', '-', ':']. Can one make more than 4 different types of line style in matplotlib?
You can create far more than these four types using the dashes argument to specify custom dash styles. For example:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 10)
y = np.sin(x)
plt.plot(x, y, dashes=[10, 5, 20, 5], linewidth=2, color='black')
The dashes argument is a list of integers which specify the size of dashes and spaces in points: in the example above there is a 10-point dash, a 5-point space, a 20-point dash, and another 5-point space, and then the sequence repeats.
Here's another example which you can use to try out different custom line styles (defined as elements in the list 'dashList'), and adapt if you want to use multiple different custom line styles in your plots:
import matplotlib.pyplot as plt
dashList = [(5,2),(2,5),(4,10),(3,3,2,2),(5,2,20,2)]
# List of Dash styles, each as integers in the format: (first line length, first space length, second line length, second space length...)
# set up the axes to look nice:
frame1 = plt.gca()
frame1.axes.xaxis.set_ticklabels([]) # hide x axis numbers
plt.xlim(0,6) # set x and y axis extents
plt.ylim(-0.5,len(dashList)-0.5)
plt.ylabel("dashList element") # add a label to the y axis
for n in range(0,len(dashList)):
plt.plot([0.5,4],[n,n], color = 'black', linestyle='--', dashes=dashList[n]) # plot a horizontal line using each custom line style
# NB plot.plt draws a line between the following points: ([x0,x1],[y0,y1])
plt.text(4.5,n,dashList[n]) # ...and show the numbers used to generate each custom linestyle
plt.show()
The latest matplotlib documentation (currently unreleased) includes many custom linestyle examples right now. Here's a screen shot:
For easier copy paste, here's part of the code used to make that plot:
linestyle_tuple = [
('loosely dotted', (0, (1, 10))),
('dotted', (0, (1, 1))),
('densely dotted', (0, (1, 1))),
('loosely dashed', (0, (5, 10))),
('dashed', (0, (5, 5))),
('densely dashed', (0, (5, 1))),
('loosely dashdotted', (0, (3, 10, 1, 10))),
('dashdotted', (0, (3, 5, 1, 5))),
('densely dashdotted', (0, (3, 1, 1, 1))),
('dashdotdotted', (0, (3, 5, 1, 5, 1, 5))),
('loosely dashdotdotted', (0, (3, 10, 1, 10, 1, 10))),
('densely dashdotdotted', (0, (3, 1, 1, 1, 1, 1)))]

Categories