Rasterization of Poly3DCollection in Matplotlib - python

When trying to enforce rasterization of a Poly3DCollection object in Matplotlib, I get the following error message (and I can confirm no rasterization is applied):
/usr/lib/python3/dist-packages/matplotlib/artist.py:788: UserWarning: Rasterization of '<mpl_toolkits.mplot3d.art3d.Poly3DCollection object at 0x2b49e8faeba8>' will be ignored
warnings.warn("Rasterization of '%s' will be ignored" % self)
It is possible to rasterize the entire figure, but it is obviously preferable to only rasterize some objects, while keeping items like axes, labels, key, text, etc. as vector graphics.
I have tried using the following syntax in my code:
ax.add_collection3d(Poly3DCollection(polygons, rasterized=True), zs='z')
c = Poly3DCollection(polygons) and then c.set_rasterized(True)
According to this post, it is possible to rasterize a PolyCollection (without the 3D bit) object.
Any suggestions?
Here is an MWE:
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlim([0, 4])
ax.set_ylim([0, 4])
ax.set_zlim([0, 4])
polygons = [[(3, 1, 1), (1, 3, 1), (1, 1, 3)]]
ax.add_collection3d(Poly3DCollection(polygons, facecolors='r', rasterized=True), zs='z')
plt.show()

First note that rasterization is only useful for exporting in a vector graphics, like pdf. I am assuming that this is what you are talking about here, so I suppose you would call something like plt.savefig("some.pdf") at the end.
I think someone just forgot to allow rasterizations for 3D objects.
A fix would be to go into the matplotlib source code, locate
python\lib\site-packages\mpl_toolkits\mplot3d\art3d.py
In the imports section add
from matplotlib.artist import allow_rasterization
and further down inside Poly3DCollection find its draw method (In my case it's at line 709) and add #allow_rasterization. It should then look like
#allow_rasterization
def draw(self, renderer):
return Collection.draw(self, renderer)
If then running the above code, plus saving it to pdf, the triangle is rasterized. Screenshot of pdf:

Related

How to plot neat ImageGrid plots?

When I try to plot figures with limited amounts of dead-space on the top, sides, and bottom I use either tight_layout or constrained_layout. However, it seems like ImageGrid doesn't support this. Take a look at the following example:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
# Data to populate plots.
im = np.arange(100).reshape((10, 10))
fig = plt.figure(figsize=(4, 6), constrained_layout=True)
grid = ImageGrid(fig, 111,
nrows_ncols=(5, 2),
axes_pad=0.2,
label_mode="L")
for ax in grid:
ax.imshow(im)
fig.suptitle("Testing a suptitle on with ImageGrid")
fig.savefig("imagegrid_suptitle.png")
I get the following warning:
UserWarning: There are no gridspecs with layoutgrids. Possibly did not call parent GridSpec with the "figure" keyword
fig.savefig("imagegrid_suptitle.png")
And this is the resulting plot: ImageGrid with constrained_layout=True
As can be seen, there's a lot of dead-space at the top, sides, and bottom, and the suptitle is located far above the top two grids. Is this a bug in constrained_layout, or is it simply not possible to support its use with ImageGrid?
Please note that I have tried to use tight_layout, but that produces a warning about the lack of support for ImageGrid:
UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.
fig.tight_layout()
It also results in the suptitle overlapping with the two top grids.

How do I customise the border and background color of my matplotlib-venn plot?

I'm trying to customise the figure area of a venn plot using the plt.figure method, but can't get the expected results.
I'm trying to create a black border and a white background on the plot, but the image still comes out transparent without a border.
I suspect I'm missing something basic with my code, but any pointers would be appreciated. Here's my code.
from matplotlib import pyplot as plt
from matplotlib_venn import venn2, venn2_circles
# Call 2 group Venn diagram
v = venn2(subsets = (10, 0, 5), set_labels = ('Euler', 'Venn'))
c = venn2_circles(subsets=(10,0,5), linestyle='dashed')
# Format
c[0].set_lw(2.0)
c[0].set_ls('dotted')
c[0].set_alpha(1)
c[0].set_color('#a6cee3')
c[1].set_lw(2.0)
c[1].set_ls('dotted')
c[1].set_alpha(1)
c[1].set_color('#b2df8a')
# Labels
plt.title("Diagrams")
for idx, subset in enumerate(v.subset_labels):
v.subset_labels[idx].set_visible(False)
# Figure
plt.figure(linewidth=10, edgecolor="black", facecolor="white")
plt.show()
You need to call plt.figure() before you call any drawing function. So, before calling v = venn2(....
plt.figure() creates a new area to draw or plot something on, and can handle a lot of options. If you don't call plt.figure() or some equivalent function, matplotlib creates a default figure. And when you call plt.figure() later on, matplotlib starts a new empty figure. Normally, matplotlib will show you two windows: the first with the default figure settings, and the second without a plot.
The complete example, a bit rewritten to make use of loops, would look like:
from matplotlib import pyplot as plt
from matplotlib_venn import venn2, venn2_circles
plt.figure(linewidth=10, edgecolor="black", facecolor="white")
# Call 2 group Venn diagram
v = venn2(subsets=(10, 0, 5), set_labels=('Euler', 'Venn'))
circles = venn2_circles(subsets=(10, 0, 5), linestyle='dashed')
# circle format
for circle, color in zip(circles, ['#a6cee3', '#b2df8a']):
circle.set_lw(2.0)
circle.set_ls('dotted')
circle.set_alpha(1)
circle.set_color(color)
# hide unwanted labels
for label in v.subset_labels:
label.set_visible(False)
plt.title("Diagrams")
plt.show()

How to change the line width while plotting over the Bloch sphere

I am using QuTiP for the Bloch sphere plotting in Python. If I have several points on the Bloch sphere then I can connect them with a line using the command
b.add_points(pnts,meth='l')|
I wanted to know how can I change the linewidth of the line connecting these points.
There isn't a direct way to do this, since by default no linewidth parameter is passed while making this plot, but you can always plot the lines manually.
The points need to be passed in as a list of numpy.ndarray objects.
The only catch is that to be consistent with what the Bloch class does, you need to make sure that the convention you are using to define the points is the same. It seems like the l method will only plot an connect the first three points that you feed in.
The following script reproduces this behaviour using a function that is similar to the one defined in Bloch:
import matplotlib.pyplot as plt
import qutip
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
pts = [np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]])]
fig, ax = plt.subplots(figsize=(5, 5), subplot_kw=dict(projection='3d'))
ax.axis('square')
b = qutip.Bloch(fig=fig, axes=ax)
for p in pts:
b.axes.plot(p[1], -p[0], p[2],
alpha=1, zdir='z', color='r',
linewidth=5)
b.render(fig=fig, axes=ax)
plt.show()
The output figure is here:

Matplotlib colorbar when LogNorm and ImageGrid is used

I have been trying to plot some panels using ImageGrid. When I use grid.cbar_axes[0].colorbar(im) to set the colorbar the colors look fine, but the scale on the colorbar reads like it's going from 2x10^0 to None.
I have tried dozens of workarounds but nothing worked. Here's the figure I'm trying to make (my wrong version):
Unfortunately I couldn't make a MWE that perfectly reproduces the problem. I did produce a MWE that partially reproduces it. If I use this code:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
from matplotlib.colors import LogNorm
def get_demo_image():
import numpy as np
from matplotlib.cbook import get_sample_data
f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)
z = np.load(f)
return abs(z), (-3, 4, -4, 3)
F = plt.figure(figsize=(5.5, 3.5))
grid = ImageGrid(F, 111, # similar to subplot(111)
nrows_ncols=(1, 3),
axes_pad=0.1,
add_all=True,
label_mode="L",
cbar_mode='single'
)
Z, extent = get_demo_image() # demo image
im1 = Z
im2 = Z
im3 = Z
vmin, vmax = 1e-3, 1e10
for i, im in enumerate([im1, im2, im3]):
ax = grid[i]
imc = ax.pcolormesh(range(15), range(15), im, norm=LogNorm(vmin=vmin, vmax=vmax), linewidth=0, rasterized=True)
cb=grid.cbar_axes[0].colorbar(imc)
I get almost the same behavior, except that the upper limit appears to be fine. The lower limit still presents the same weird behavior, no matter what values I use for vmin and vmax.
Any idea of what might be going on?
Although the official example uses something similar to
cb=grid.cbar_axes[0].colorbar(imc), which would translate here into grid[2].cax.colorbar(im), I'm lost on why that would even make sense.
Instead, the usual way to produce a colorbar would also work here, using the colorbar method of Figure with the ScalarMappable (here imc) as argument
and the axes to produce the colorbar as keywordargument to cax (here grid[2].cax):
cb=F.colorbar(imc, cax=grid[2].cax)

How do I put a circle with annotation in matplotlib?

I plot a graph created by networkx with matplotlib. Now, I'd like to add annotation to a specific node with a circle around. For instance,
I use plt.annotate(*args, **kwargs) with the following code,
# add annotate text
pos = nx.get_node_attributes(G, 'pos')
pos_annotation_node = pos['Medici']
ax2.annotate('Midici',
xy=pos_annotation_node,
xytext=(i+0.2 for i in pos_annotation_node),
color='blue',
arrowprops=dict(facecolor='blue', shrink=0.01)
)
And I got this ugly graph,
I have two questions:
how do I draw a circle around the node 6 as shown in first figure.
to get a nice looking figure, I need manually set the value of xytext many times. Is there a better way?
If you use the fancyarrow arrowprops syntax as demonstrated in annotation_demo2, there is a shrinkA and shrinkB option that lets you shrink your arrow tail (shrinkA) and tip (shrinkB) independently, in points units.
Here's some arbitrary setup code:
import matplotlib.pyplot as plt
import numpy as np
# Some data:
dat = np.array([[5, 3, 4, 4, 6],
[1, 5, 3, 2, 2]])
# This is the point you want to point out
point = dat[:, 2]
# Make the figure
plt.figure(1, figsize=(4, 4))
plt.clf()
ax = plt.gca()
# Plot the data
ax.plot(dat[0], dat[1], 'o', ms=10, color='r')
ax.set_xlim([2, 8])
ax.set_ylim([0, 6])
And here is the code that puts a circle around one of these points and draws an arrow that is shrunk-back at the tip only:
circle_rad = 15 # This is the radius, in points
ax.plot(point[0], point[1], 'o',
ms=circle_rad * 2, mec='b', mfc='none', mew=2)
ax.annotate('Midici', xy=point, xytext=(60, 60),
textcoords='offset points',
color='b', size='large',
arrowprops=dict(
arrowstyle='simple,tail_width=0.3,head_width=0.8,head_length=0.8',
facecolor='b', shrinkB=circle_rad * 1.2)
)
Note here that:
1) I've made the marker face color of the circle transparent with mfc='none', and set the circle size (diameter) to twice the radius.
2) I've shrunk the arrow by 120% of the circle radius so that it backs off of the circle just a bit. Obviously you can play with circle_rad and the value of 1.2 until you get what you want.
3) I've used the "fancy" syntax that defines several of the arrow properties in a string, rather than in the dict. As far as I can tell the shrinkB option is not available if you don't use the fancy arrow syntax.
4) I've used the textcoords='offset points' so that I can specify the position of the text relative to the point, rather than absolute on the axes.
how do I draw a circle around the node 6 as shown in first figure.
You get a center of node #6 (tuple pos). Use this data to set the blue circle position.
to get a nice looking figure, I need manually set the value of xytext many times. Is there a better way?
Make a list of your labels and iterate in it and in tuples of coordinates of nodes to post annotate text. Look to comments of a code.
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from matplotlib.patches import Circle
import matplotlib.patches as patches
import numpy as np
from matplotlib.font_manager import FontProperties
font = FontProperties()
font.set_weight('bold')
font.set_size('medium')
labels = ["Midici","Firenze"]
image = mpimg.imread("g.png") # just a image of your graph
plt.imshow(image)
ax = plt.gca()
# set your own radius and centers of circles in loop, like here
r = 11; c = (157,177)
circ1 = patches.Circle(c,2*r,lw=3.,ec='b',fill=False)
ax.add_artist(circ1)
circ1.set_clip_box(ax.bbox)
# annotate circles
# I have one circle but for your array pos_annotation_node
# you need 'i' to extract proper position
for i,label in enumerate(labels):
annot_array_end = (c[0], c[1]+2*(-1)**i*r)
annot_text_pos = (c[0]+3,c[1]+5*(-1)**i*r)
ax.annotate(label,
xy= annot_array_end,
xytext=annot_text_pos,
color='b',
fontproperties=font,
arrowprops=dict(fc='b', shrink=.005)
)
plt.show()
Just an observation for other people finding this thread. Not everything has to be done in Matplotlib.
It might well be easier to use a drawing package, with your network chart saved as a PDF (or PNG) in the background...

Categories

Resources