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

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:

Related

Horizontal line to infinity on one side only in matplotlib

I'd like to plot a line that goes to infinity, but starting from a finite point. For simplicity, let's say that the line can be horizontal. I would like to plot a line from (0, 0) to (inf, 0).
Using hlines:
>>> fig, ax = plt.subplots()
>>> ax.hlines(0, 0, np.inf)
.../python3.8/site-packages/matplotlib/axes/_base.py:2480: UserWarning: Warning: converting a masked element to nan.
xys = np.asarray(xys)
The result is an empty plot.
axhline has a starting parameter, but it is in axis coordinates rather than data. Similar problem for axline. Is there a way to plot a (horizontal) line with one end in data coordinates and the other at infinity?
The motivation behind this is that I'd like to be able to plot some cumulative probabilities without setting data past the last bin to zero, as here: Matplotlib cumulative histogram - vertical line placement bug or misinterpretation?. Rather than simply ending the histogram, I'd like to be able to extend the line from the last bin to infinity at y=1.0.
There's no built-in function for this, but you can re-draw the line to the axis limit on each change of the x limits.
From Axes:
The events you can connect to are 'xlim_changed' and 'ylim_changed'
and the callback will be called with func(ax) where ax is the Axes
instance.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
def hline_to_inf(ax, x, y):
line = ax.hlines(0, 0, ax.get_xlim()[1])
ax.callbacks.connect('xlim_changed',
lambda ax: line.set_paths([[[x, y], [ax.get_xlim()[1], y]]]))
hline_to_inf(ax, 0, 0)
plt.show()
Part of the issue is that normal plotting methods apply the same transform to the input data. What is required here is to apply a data transform to the start point, and a blended transform to the endpoint. It seems that there may be an answer using existing tools with ConnectionPatch, as explained in the Annotations Guide. The idea is to make the left point use data coordinates and the right point have a blended transform with x in axes coordinates and y in data.
from matplotlib import pyplot as plt
from matplotlib.patches import ConnectionPatch
fig, ax = plt.subplots()
line, = ax.plot([1, 2], [1, 2])
ax.add_artist(ConnectionPatch([2, 2], [1, 2], coordsA=ax.transData, coordsB=ax.get_yaxis_transform(), color=line.get_color(), linewidth=line.get_linewidth(), clip_on=True))
Turning on clipping is necessary, otherwise you could end up with artifacts that look like this:

retrieve leave colors from scipy dendrogram

I can not get the color leaves from the scipy dendrogram dictionary. As stated in the documentation and in this github issue, the color_list key in the dendrogram dictionary refers to the links, not the leaves. It would be nice to have another key referring to the leaves, sometimes you need this for coloring other types of graphics, such as this scatter plot in the example below.
import numpy as np
import matplotlib.pyplot as plt
from scipy.cluster.hierarchy import linkage, dendrogram
# DATA EXAMPLE
x = np.array([[ 5, 3],
[10,15],
[15,12],
[24,10],
[30,30],
[85,70],
[71,80]])
# DENDROGRAM
plt.figure()
plt.subplot(121)
z = linkage(x, 'single')
d = dendrogram(z)
# COLORED PLOT
# This is what I would like to achieve. Colors are assigned manually by looking
# at the dendrogram, because I failed to get it from d['color_list'] (it refers
# to links, not observations)
plt.subplot(122)
points = d['leaves']
colors = ['r','r','g','g','g','g','g']
for point, color in zip(points, colors):
plt.plot(x[point, 0], x[point, 1], 'o', color=color)
Manual color assignment seems easy in this example, but I'm dealing with huge datasets, so until we get this new feature in the dictionary (color leaves), I'm trying to infer it somehow with the current information contained in the dictionary but I'm out of ideas so far. Can anyone help me?
Thanks.
For scipy 1.7.1 the new functionality has been implemented and the dendogram function returns in the output dictionary also an entry 'leaves_color_list' that can be used to perform easily this task.
Here is a working code of the OP (see last line "NEW CODE")
import numpy as np
import matplotlib.pyplot as plt
from scipy.cluster.hierarchy import linkage, dendrogram
# DATA EXAMPLE
x = np.array([[ 5, 3],
[10,15],
[15,12],
[24,10],
[30,30],
[85,70],
[71,80]])
# DENDROGRAM
plt.figure()
plt.subplot(121)
z = linkage(x, 'single')
d = dendrogram(z)
# COLORED PLOT
# This is what I would like to achieve. Colors are assigned manually by looking
# at the dendrogram, because I failed to get it from d['color_list'] (it refers
# to links, not observations)
plt.subplot(122)
#NEW CODE
plt.scatter(x[d['leaves'],0],x[d['leaves'],1], color=d['leaves_color_list'])
The following approach seems to work. The dictionary returned by the dendogram contains 'color_list' with the colors of the linkages. And 'icoord' and 'dcoord' with the x, resp. y, plot coordinates of these linkages. These x-positions are 5, 15, 25, ... when the linkage starts at a point. So, testing these x-positions can bring us back from the linkage to the corresponding point. And allows to assign the color of the linkage to the point.
import numpy as np
import matplotlib.pyplot as plt
from scipy.cluster.hierarchy import linkage, dendrogram
# DATA EXAMPLE
x = np.random.uniform(0, 10, (20, 2))
# DENDROGRAM
plt.figure()
plt.subplot(121)
z = linkage(x, 'single')
d = dendrogram(z)
plt.yticks([])
# COLORED PLOT
plt.subplot(122)
points = d['leaves']
colors = ['none'] * len(points)
for xs, c in zip(d['icoord'], d['color_list']):
for xi in xs:
if xi % 10 == 5:
colors[(int(xi)-5) // 10] = c
for point, color in zip(points, colors):
plt.plot(x[point, 0], x[point, 1], 'o', color=color)
plt.text(x[point, 0], x[point, 1], f' {point}')
plt.show()
PS: This post about matching points with their clusters might also be relevant.

Rasterization of Poly3DCollection in Matplotlib

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:

Is there a way to plot a Line2D in points coordinates in Matplotlib in Python?

It is very straight forward to plot a line between two points (x1, y1) and (x2, y2) in Matplotlib using Line2D:
Line2D(xdata=(x1, x2), ydata=(y1, y2))
But in my particular case I have to draw Line2D instances using Points coordinates on top of the regular plots that are all using Data coordinates. Is that possible?
As #tom mentioned, the key is the transform kwarg. If you want an artist's data to be interpreted as being in "pixel" coordinates, specify transform=IdentityTransform().
Using Transforms
Transforms are a key concept in matplotlib. A transform takes coordinates that the artist's data is in and converts them to display coordinates -- in other words, pixels on the screen.
If you haven't already seen it, give the matplotlib transforms tutorial a quick read. I'm going to assume a passing familiarity with the first few paragraphs of that tutorial, so if you're
For example, if we want to draw a line across the entire figure, we'd use something like:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# The "clip_on" here specifies that we _don't_ want to clip the line
# to the extent of the axes
ax.plot([0, 1], [0, 1], lw=3, color='salmon', clip_on=False,
transform=fig.transFigure)
plt.show()
This line will always extend from the lower-left corner of the figure to the upper right corner, no matter how we interactively resize/zoom/pan the plot.
Drawing in pixels
The most common transforms you'll use are ax.transData, ax.transAxes, and fig.transFigure. However, to draw in points/pixels, you actually want no transform at all. In that case, you'll make a new transform instance that does nothing: the IdentityTransform. This specifies that the data for the artist is in "raw" pixels.
Any time you'd like to plot in "raw" pixels, specify transform=IdentityTransform() to the artist.
If you'd like to work in points, recall that there are 72 points to an inch, and that for matplotlib, fig.dpi controls the number of pixels in an "inch" (it's actually independent of the physical display). Therefore, we can convert points to pixels with a simple formula.
As an example, let's place a marker 30 points from the bottom-left edge of the figure:
import matplotlib.pyplot as plt
from matplotlib.transforms import IdentityTransform
fig, ax = plt.subplots()
points = 30
pixels = fig.dpi * points / 72.0
ax.plot([pixels], [pixels], marker='o', color='lightblue', ms=20,
transform=IdentityTransform(), clip_on=False)
plt.show()
Composing Transforms
One of the more useful things about matplotlib's transforms is that they can be added to create a new transform. This makes it easy to create shifts.
For example, let's plot a line, then add another line shifted by 15 pixels in the x-direction:
import matplotlib.pyplot as plt
from matplotlib.transforms import Affine2D
fig, ax = plt.subplots()
ax.plot(range(10), color='lightblue', lw=4)
ax.plot(range(10), color='gray', lw=4,
transform=ax.transData + Affine2D().translate(15, 0))
plt.show()
A key thing to keep in mind is that the order of the additions matters. If we did Affine2D().translate(15, 0) + ax.transData instead, we'd shift things by 15 data units instead of 15 pixels. The added transforms are "chained" (composed would be a more accurate term) in order.
This also makes it easy to define things like "20 pixels from the right hand side of the figure". For example:
import matplotlib.pyplot as plt
from matplotlib.transforms import Affine2D
fig, ax = plt.subplots()
ax.plot([1, 1], [0, 1], lw=3, clip_on=False, color='salmon',
transform=fig.transFigure + Affine2D().translate(-20, 0))
plt.show()
You can use the transform keyword to change between data coordinate (the default) and axes coordinates. For example:
import matplotlib.pyplot as plt
import matplotlib.lines as lines
plt.plot(range(10),range(10),'ro-')
myline = lines.Line2D((0,0.5,1),(0.5,0.5,0),color='b') # data coords
plt.gca().add_artist(myline)
mynewline = lines.Line2D((0,0.5,1),(0.5,0.5,0),color='g',transform=plt.gca().transAxes) # Axes coordinates
plt.gca().add_artist(mynewline)
plt.show()

How to zoomed a portion of image and insert in the same plot in matplotlib

I would like to zoom a portion of data/image and plot it inside the same figure. It looks something like this figure.
Is it possible to insert a portion of zoomed image inside the same plot. I think it is possible to draw another figure with subplot but it draws two different figures. I also read to add patch to insert rectangle/circle but not sure if it is useful to insert a portion of image into the figure. I basically load data from the text file and plot it using a simple plot commands shown below.
I found one related example from matplotlib image gallery here but not sure how it works. Your help is much appreciated.
from numpy import *
import os
import matplotlib.pyplot as plt
data = loadtxt(os.getcwd()+txtfl[0], skiprows=1)
fig1 = plt.figure()
ax1 = fig1.add_subplot(111)
ax1.semilogx(data[:,1],data[:,2])
plt.show()
Playing with runnable code is one of the
fastest ways to learn Python.
So let's start with the code from the matplotlib example gallery.
Given the comments in the code, it appears the code is broken up into 4 main stanzas.
The first stanza generates some data, the second stanza generates the main plot,
the third and fourth stanzas create the inset axes.
We know how to generate data and plot the main plot, so let's focus on the third stanza:
a = axes([.65, .6, .2, .2], axisbg='y')
n, bins, patches = hist(s, 400, normed=1)
title('Probability')
setp(a, xticks=[], yticks=[])
Copy the example code into a new file, called, say, test.py.
What happens if we change the .65 to .3?
a = axes([.35, .6, .2, .2], axisbg='y')
Run the script:
python test.py
You'll find the "Probability" inset moved to the left.
So the axes function controls the placement of the inset.
If you play some more with the numbers you'll figure out that (.35, .6) is the
location of the lower left corner of the inset, and (.2, .2) is the width and
height of the inset. The numbers go from 0 to 1 and (0,0) is the located at the
lower left corner of the figure.
Okay, now we're cooking. On to the next line we have:
n, bins, patches = hist(s, 400, normed=1)
You might recognize this as the matplotlib command for drawing a histogram, but
if not, changing the number 400 to, say, 10, will produce an image with a much
chunkier histogram, so again by playing with the numbers you'll soon figure out
that this line has something to do with the image inside the inset.
You'll want to call semilogx(data[3:8,1],data[3:8,2]) here.
The line title('Probability')
obviously generates the text above the inset.
Finally we come to setp(a, xticks=[], yticks=[]). There are no numbers to play with,
so what happens if we just comment out the whole line by placing a # at the beginning of the line:
# setp(a, xticks=[], yticks=[])
Rerun the script. Oh! now there are lots of tick marks and tick labels on the inset axes.
Fine. So now we know that setp(a, xticks=[], yticks=[]) removes the tick marks and labels from the axes a.
Now, in theory you have enough information to apply this code to your problem.
But there is one more potential stumbling block: The matplotlib example uses
from pylab import *
whereas you use import matplotlib.pyplot as plt.
The matplotlib FAQ says import matplotlib.pyplot as plt
is the recommended way to use matplotlib when writing scripts, while
from pylab import * is for use in interactive sessions. So you are doing it the right way, (though I would recommend using import numpy as np instead of from numpy import * too).
So how do we convert the matplotlib example to run with import matplotlib.pyplot as plt?
Doing the conversion takes some experience with matplotlib. Generally, you just
add plt. in front of bare names like axes and setp, but sometimes the
function come from numpy, and sometimes the call should come from an axes
object, not from the module plt. It takes experience to know where all these
functions come from. Googling the names of functions along with "matplotlib" can help.
Reading example code can builds experience, but there is no easy shortcut.
So, the converted code becomes
ax2 = plt.axes([.65, .6, .2, .2], axisbg='y')
ax2.semilogx(t[3:8],s[3:8])
plt.setp(ax2, xticks=[], yticks=[])
And you could use it in your code like this:
from numpy import *
import os
import matplotlib.pyplot as plt
data = loadtxt(os.getcwd()+txtfl[0], skiprows=1)
fig1 = plt.figure()
ax1 = fig1.add_subplot(111)
ax1.semilogx(data[:,1],data[:,2])
ax2 = plt.axes([.65, .6, .2, .2], axisbg='y')
ax2.semilogx(data[3:8,1],data[3:8,2])
plt.setp(ax2, xticks=[], yticks=[])
plt.show()
The simplest way is to combine "zoomed_inset_axes" and "mark_inset", whose description and
related examples could be found here:
Overview of AxesGrid toolkit
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
import numpy as np
def get_demo_image():
from matplotlib.cbook import get_sample_data
import numpy as np
f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)
z = np.load(f)
# z is a numpy array of 15x15
return z, (-3,4,-4,3)
fig, ax = plt.subplots(figsize=[5,4])
# prepare the demo image
Z, extent = get_demo_image()
Z2 = np.zeros([150, 150], dtype="d")
ny, nx = Z.shape
Z2[30:30+ny, 30:30+nx] = Z
# extent = [-3, 4, -4, 3]
ax.imshow(Z2, extent=extent, interpolation="nearest",
origin="lower")
axins = zoomed_inset_axes(ax, 6, loc=1) # zoom = 6
axins.imshow(Z2, extent=extent, interpolation="nearest",
origin="lower")
# sub region of the original image
x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9
axins.set_xlim(x1, x2)
axins.set_ylim(y1, y2)
plt.xticks(visible=False)
plt.yticks(visible=False)
# draw a bbox of the region of the inset axes in the parent axes and
# connecting lines between the bbox and the inset axes area
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")
plt.draw()
plt.show()
The nicest way I know of to do this is to use mpl_toolkits.axes_grid1.inset_locator (part of matplotlib).
There is a great example with source code here: https://github.com/NelleV/jhepc/tree/master/2013/entry10
The basic steps to zoom up a portion of a figure with matplotlib
import numpy as np
from matplotlib import pyplot as plt
# Generate the main data
X = np.linspace(-6, 6, 1024)
Y = np.sinc(X)
# Generate data for the zoomed portion
X_detail = np.linspace(-3, 3, 1024)
Y_detail = np.sinc(X_detail)
# plot the main figure
plt.plot(X, Y, c = 'k')
# location for the zoomed portion
sub_axes = plt.axes([.6, .6, .25, .25])
# plot the zoomed portion
sub_axes.plot(X_detail, Y_detail, c = 'k')
# insert the zoomed figure
# plt.setp(sub_axes)
plt.show()

Categories

Resources