Append data with different colour in matplotlib in real time - python

I'm updating dynamically a plot in a loop:
dat=[0, max(X[:, 0])]
fig = plt.figure()
ax = fig.add_subplot(111)
Ln, = ax.plot(dat)
Ln2, = ax.plot(dat)
plt.ion()
plt.show()
for i in range(1, 40):
ax.set_xlim(int(len(X[:i])*0.8), len(X[:i])) #show last 20% data of X
Ln.set_ydata(X[:i])
Ln.set_xdata(range(len(X[:i])))
Ln2.set_ydata(Y[:i])
Ln2.set_xdata(range(len(Y[:i])))
plt.pause(0.1)
But now I want to update it in a different way: append some values and show them in other colour:
X.append(other_data)
# change colour just to other_data in X
The result should look something like this:
How could I do that?

Have a look at the link I posted. Linesegments can be used to plot colors at a particular location differently. If you want to do it in real-time you can still use line-segments. I leave that up to you.
# adjust from https://stackoverflow.com/questions/38051922/how-to-get-differents-colors-in-a-single-line-in-a-matplotlib-figure
import numpy as np, matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap, BoundaryNorm
# my func
x = np.linspace(-2 * np.pi, 2 * np.pi, 100)
y = 3000 * np.sin(x)
# select how to color
cmap = ListedColormap(['r','b'])
norm = BoundaryNorm([2000,], cmap.N)
# get segments
xy = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.hstack([xy[:-1], xy[1:]])
# control which values have which colors
n = y.shape[0]
c = np.array([plt.cm.RdBu(0) if i < n//2 else plt.cm.RdBu(255) for i in range(n)])
# c = plt.cm.Reds(np.arange(0, n))
# make line collection
lc = LineCollection(segments,
colors = c
# norm = norm,
)
# plot
fig, ax = plt.subplots()
ax.add_collection(lc)
ax.autoscale()
ax.axvline(x[n//2], linestyle = 'dashed')
ax.annotate("Half-point", (x[n//2], y[n//2]), xytext = (4, 1000),
arrowprops = dict(headwidth = 30))
fig.show()

Related

How to plot a vertical thermal plot?

How to plot this kind of thermal plot in Python? I tried to search for any sample plot like this but didn't find one.
This image I got from the internet. I want to plot something same like this:
FROM
TO
To represent this type of data the canonical solution is, of course, a heat map. Here it is the code to produce both the figures at the top of this post.
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(0, 5, 501)
x = np.linspace(0, 1, 201)[:, None]
T = 50 + (30-6*t)*(4*x*(1-x)) + 4*t
fig, ax = plt.subplots(layout='constrained')
hm = ax.imshow(T, cmap='plasma',
aspect='auto', origin='lower', extent=(0, 5, 0, 1))
fig.colorbar(hm)
def heat_lines(x, t, T, n):
from matplotlib.cm import ScalarMappable
from matplotlib.collections import LineCollection
lx, lt = T.shape
ones = np.ones(lx)
norm = plt.Normalize(np.min(T), np.max(T))
plasma = plt.cm.plasma
fig, ax = plt.subplots(figsize=(1+1.2*n, 9), layout='constrained')
ax.set_xlim((-0.6, n-0.4))
ax.set_ylim((x[0], x[-1]))
ax.set_xticks(range(n))
ax.tick_params(right=False,top=False, bottom=False)
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["bottom"].set_visible(False)
ax.grid(axis='y')
fig.colorbar(ScalarMappable(cmap=plasma, norm=norm))
dt = round(lt/(n-1))
for pos, ix in enumerate(range(0, len(t)+dt//2, dt)):
points = np.array([ones*pos, x[:,0]]).T.reshape(-1,1,2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments, linewidth=72, ec=None,
color=plasma(norm(T[:,ix])))
lc.set_array(T[:,ix])
ax.add_collection(lc)
heat_lines(x, t, T, 6)

Display matplotlib legend element as 2D line of colormap

I wish to modify the 2D line in my legend to plot as line segments (or another method like patches) that will display the range of my colormap (here viridis_r) instead of a singular color. While the third variable (radius) is included in the colorbar, having it displayed in the legend as well will be informative when I add more complications to the plot. Thanks!
fig, ax = plt.subplots()
radii = [1,2,3,4,5]
angle = np.linspace(0, 2 * np.pi, 150)
cmap = plt.get_cmap('viridis_r')
norm = plt.Normalize(radii[0], radii[-1])
m = plt.cm.ScalarMappable(cmap=cmap)
m.set_array(radii)
for radius in radii:
x = radius * np.cos(angle)
y = radius * np.sin(angle)
ax.plot(x, y, color=cmap(norm(radius)))
radius_2Dline = plt.Line2D((0, 1), (0, 0), color='k', linewidth=2)
ax.legend([radius_2Dline],['Radius'], loc='best')
ax.set_aspect( 1 )
fig.colorbar(m).set_label('Radius', size=15)
plt.show()
The following approach uses the "tuple legend handler". That handler puts a list of legend handles (in this case the circles drawn via ax.plot). Setting ndivide=None will draw one short line for each element in the list. The padding can be set to 0 to avoid gaps between these short lines. The default handlelength might be too small to properly see these special handles; therefore, the example code below increases it a bit.
import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerTuple
import numpy as np
fig, ax = plt.subplots()
radii = [1, 2, 3, 4, 5]
angle = np.linspace(0, 2 * np.pi, 150)
cmap = plt.get_cmap('viridis_r')
norm = plt.Normalize(radii[0], radii[-1])
lines = [] # list of lines to be used for the legend
for radius in radii:
x = radius * np.cos(angle)
y = radius * np.sin(angle)
line, = ax.plot(x, y, color=cmap(norm(radius)))
lines.append(line)
ax.legend(handles=[tuple(lines)], labels=['Radius'],
handlelength=3, handler_map={tuple: HandlerTuple(ndivide=None, pad=0)})
ax.set_aspect('equal')
plt.tight_layout()
plt.show()
I am not sure if this is your goal but here is a stab at it. Following this answer, you can make a 'fake' legend with a colormap.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
fig, ax = plt.subplots()
radii = [1, 2, 3, 4, 5]
angle = np.linspace(0, 2 * np.pi, 150)
cmap = plt.get_cmap('viridis_r')
norm = plt.Normalize(radii[0], radii[-1])
m = plt.cm.ScalarMappable(cmap=cmap)
m.set_array(radii)
for radius in radii:
x = radius * np.cos(angle)
y = radius * np.sin(angle)
ax.plot(x, y, color=cmap(norm(radius)))
# Set box that will act as a 'fake' legend, 25% width of the
# x-axis, 15% of y-axis
cbbox = inset_axes(ax, width="25%", height="15%", loc=2)
cbbox.tick_params(
axis = 'both',
left = False,
top = False,
right = False,
bottom = False,
labelleft = False,
labeltop = False,
labelright = False,
labelbottom = False
)
# Semi-transparent like the usual ax.legend()
cbbox.set_facecolor([1, 1, 1, 0.7])
# Colorbar inside the fake legend box, occupying 85% of the
# box width and %5 box height
cbaxes = inset_axes(cbbox, width="85%", height="5%", loc=2)
cbar = fig.colorbar(m, cax=cbaxes, orientation='horizontal',
ticks=[1, 3, 5])
cbar.set_label('Radius', size=9)
cbar.ax.tick_params(labelsize=9)
ax.set_aspect(1)
plt.show()
I was unsuccessful in creating an actual ax.legend() from a LineCollection or a multicolored line - it only plotted one color - so my solution was this 'fake' legend approach. Hope this helps, cheers.

How to create a multi-colored curve in 3d?

I'm trying to plot a 3d curve that has different colors depending on one of its parameters. I tried this method similar to this question, but it doesn't work. Can anyone point me in the right direction?
import matplotlib.pyplot as plt
from matplotlib import cm
T=100
N=5*T
x=np.linspace(0,T,num=N)
y=np.cos(np.linspace(0,T,num=N))
z=np.sin(np.linspace(0,T,num=N))
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot(x,y,z,cmap = cm.get_cmap("Spectral"),c=z)
plt.show()
To extend the approach in this tutorial to 3D, use x,y,z instead of x,y.
The desired shape for the segments is (number of segments, 2 points, 3 coordinates per point), so N-1,2,3. First the array of points is created with shape N, 3. Then start (xyz[:-1, :]) and end points (xyz[1:, :]) are stacked together.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Line3DCollection
T = 100
N = 5 * T
x = np.linspace(0, T, num=N)
y = np.cos(np.linspace(0, T, num=N))
z = np.sin(np.linspace(0, T, num=N))
xyz = np.array([x, y, z]).T
segments = np.stack([xyz[:-1, :], xyz[1:, :]], axis=1) # shape is 499,2,3
cmap = plt.cm.get_cmap("Spectral")
norm = plt.Normalize(z.min(), z.max())
lc = Line3DCollection(segments, linewidths=2, colors=cmap(norm(z[:-1])))
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.add_collection(lc)
ax.set_xlim(-10, 110)
ax.set_ylim(-1.1, 1.1)
ax.set_zlim(-1.1, 1.1)
plt.show()

How can I create custom break points in a matplotlib colorbar?

I'm borrowing an example from the matplotlib custom cmap examples page:
https://matplotlib.org/examples/pylab_examples/custom_cmap.html
This produces the same image with different numbers of shading contours, as specified in the number of bins: n_bins:
https://matplotlib.org/_images/custom_cmap_00.png
However, I'm interested not only in the number of bins, but the specific break points between the color values. For example, when nbins=6 in the top right subplot, how can I specify the ranges of the bins to such that the shading is filled in these custom areas:
n_bins_ranges = ([-10,-5],[-5,-2],[-2,-0.5],[-0.5,2.5],[2.5,7.5],[7.5,10])
Is it also possible to specify the inclusivity of the break points? For example, I'd like to specify in the range between -2 and 0.5 whether it's -2 < x <= -0.5 or -2 <= x < -0.5.
EDIT WITH ANSWER BELOW:
Using the accepted answer below, here is code that plots each step including finally adding custom colorbar ticks at the midpoint. Note I can't post an image since I'm a new user.
Set up data and 6 color bins:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
# Make some illustrative fake data:
x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2*np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 10
# Create colormap with 6 discrete bins
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] # R -> G -> B
n_bin = 6
cmap_name = 'my_list'
cm = matplotlib.colors.LinearSegmentedColormap.from_list(
cmap_name, colors, N=n_bin)
Plot different options:
# Set up 4 subplots
fig, axs = plt.subplots(2, 2, figsize=(6, 9))
fig.subplots_adjust(left=0.02, bottom=0.06, right=0.95, top=0.94, wspace=0.05)
# Plot 6 bin figure
im = axs[0,0].imshow(Z, interpolation='nearest', origin='lower', cmap=cm)
axs[0,0].set_title("Original 6 Bin")
fig.colorbar(im, ax=axs[0,0])
# Change the break points
n_bins_ranges = [-10,-5,-2,-0.5,2.5,7.5,10]
norm = matplotlib.colors.BoundaryNorm(n_bins_ranges, len(n_bins_ranges))
im = axs[0,1].imshow(Z, interpolation='nearest', origin='lower', cmap=cm, norm=norm)
axs[0,1].set_title("Custom Break Points")
fig.colorbar(im, ax=axs[0,1])
# Arrange color labels by data interval (not colors)
im = axs[1,0].imshow(Z, interpolation='nearest', origin='lower', cmap=cm, norm=norm)
axs[1,0].set_title("Linear Color Distribution")
fig.colorbar(im, ax=axs[1,0], spacing="proportional")
# Provide custom labels at color midpoints
# And change inclusive equality by adding arbitrary small value
n_bins_ranges_arr = np.asarray(n_bins_ranges)+1e-9
norm = matplotlib.colors.BoundaryNorm(n_bins_ranges, len(n_bins_ranges))
n_bins_ranges_midpoints = (n_bins_ranges_arr[1:] + n_bins_ranges_arr[:-1])/2.0
im = axs[1,1].imshow(Z, interpolation='nearest', origin='lower', cmap=cm ,norm=norm)
axs[1,1].set_title("Midpoint Labels\n Switched Equal Sign")
cbar=fig.colorbar(im, ax=axs[1,1], spacing="proportional",
ticks=n_bins_ranges_midpoints.tolist())
cbar.ax.set_yticklabels(['Red', 'Brown', 'Green 1','Green 2','Gray Blue','Blue'])
plt.show()
You can use a BoundaryNorm as follows:
import matplotlib.pyplot as plt
import matplotlib.colors
import numpy as np
x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2*np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 10
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] # R -> G -> B
n_bin = 6 # Discretizes the interpolation into bins
n_bins_ranges = [-10,-5,-2,-0.5,2.5,7.5,10]
cmap_name = 'my_list'
fig, ax = plt.subplots()
# Create the colormap
cm = matplotlib.colors.LinearSegmentedColormap.from_list(
cmap_name, colors, N=n_bin)
norm = matplotlib.colors.BoundaryNorm(n_bins_ranges, len(n_bins_ranges))
# Fewer bins will result in "coarser" colomap interpolation
im = ax.imshow(Z, interpolation='nearest', origin='lower', cmap=cm, norm=norm)
ax.set_title("N bins: %s" % n_bin)
fig.colorbar(im, ax=ax)
plt.show()
Or, if you want proportional spacing, i.e. the distance between colors according to their values,
fig.colorbar(im, ax=ax, spacing="proportional")
As the boundary norm documentation states
If b[i] <= v < b[i+1]
then v is mapped to color j; as i varies from 0 to len(boundaries)-2, j goes from 0 to ncolors-1.
So the colors are always chosen as -2 <= x < -0.5, in order to obtain the equal sign on the other side you would need to supply
something like n_bins_ranges = np.array([-10,-5,-2,-0.5,2.5,7.5,10])-1e-9

How to animate the colorbar in matplotlib

I have an animation where the range of the data varies a lot. I would like to have a colorbar which tracks the max and the min of the data (i.e. I would like it not to be fixed). The question is how to do this.
Ideally I would like the colorbar to be on its own axis.
I have tried the following four things
1. Naive approach
The problem: A new colorbar is plottet for each frame
#!/usr/bin/env python
"""
An animated image
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ax = fig.add_subplot(111)
def f(x, y):
return np.exp(x) + np.sin(y)
x = np.linspace(0, 1, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
frames = []
for i in range(10):
x += 1
curVals = f(x, y)
vmax = np.max(curVals)
vmin = np.min(curVals)
levels = np.linspace(vmin, vmax, 200, endpoint = True)
frame = ax.contourf(curVals, vmax=vmax, vmin=vmin, levels=levels)
cbar = fig.colorbar(frame)
frames.append(frame.collections)
ani = animation.ArtistAnimation(fig, frames, blit=False)
plt.show()
2. Adding to the images
Changing the for loop above to
initFrame = ax.contourf(f(x,y))
cbar = fig.colorbar(initFrame)
for i in range(10):
x += 1
curVals = f(x, y)
vmax = np.max(curVals)
vmin = np.min(curVals)
levels = np.linspace(vmin, vmax, 200, endpoint = True)
frame = ax.contourf(curVals, vmax=vmax, vmin=vmin, levels=levels)
cbar.set_clim(vmin = vmin, vmax = vmax)
cbar.draw_all()
frames.append(frame.collections + [cbar])
The problem: This raises
AttributeError: 'Colorbar' object has no attribute 'set_visible'
3. Plotting on its own axis
The problem: The colorbar is not updated.
#!/usr/bin/env python
"""
An animated image
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
def f(x, y):
return np.exp(x) + np.sin(y)
x = np.linspace(0, 1, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
frames = []
for i in range(10):
x += 1
curVals = f(x, y)
vmax = np.max(curVals)
vmin = np.min(curVals)
levels = np.linspace(vmin, vmax, 200, endpoint = True)
frame = ax1.contourf(curVals, vmax=vmax, vmin=vmin, levels=levels)
cbar = fig.colorbar(frame, cax=ax2) # Colorbar does not update
frames.append(frame.collections)
ani = animation.ArtistAnimation(fig, frames, blit=False)
plt.show()
A combination of 2. and 4.
The problem: The colorbar is constant.
A similar question is posted here, but it looks like the OP is satisfied with a fixed colorbar.
While I'm not sure how to do this specifically using an ArtistAnimation, using a FuncAnimation is fairly straightforward. If I make the following modifications to your "naive" version 1 it works.
Modified Version 1
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.axes_grid1 import make_axes_locatable
fig = plt.figure()
ax = fig.add_subplot(111)
# I like to position my colorbars this way, but you don't have to
div = make_axes_locatable(ax)
cax = div.append_axes('right', '5%', '5%')
def f(x, y):
return np.exp(x) + np.sin(y)
x = np.linspace(0, 1, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
frames = []
for i in range(10):
x += 1
curVals = f(x, y)
frames.append(curVals)
cv0 = frames[0]
cf = ax.contourf(cv0, 200)
cb = fig.colorbar(cf, cax=cax)
tx = ax.set_title('Frame 0')
def animate(i):
arr = frames[i]
vmax = np.max(arr)
vmin = np.min(arr)
levels = np.linspace(vmin, vmax, 200, endpoint = True)
cf = ax.contourf(arr, vmax=vmax, vmin=vmin, levels=levels)
cax.cla()
fig.colorbar(cf, cax=cax)
tx.set_text('Frame {0}'.format(i))
ani = animation.FuncAnimation(fig, animate, frames=10)
plt.show()
The main difference is that I do the levels calculations and contouring in a function instead of creating a list of artists. The colorbar works because you can clear the axes from the previous frame and redo it every frame.
Doing this redo is necessary when using contour or contourf, because you can't just dynamically change the data. However, as you have plotted so many contour levels and the result looks smooth, I think you may be better off using imshow instead - it means you can actually just use the same artist and change the data, and the colorbar updates itself automatically. It's also much faster!
Better Version
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.axes_grid1 import make_axes_locatable
fig = plt.figure()
ax = fig.add_subplot(111)
# I like to position my colorbars this way, but you don't have to
div = make_axes_locatable(ax)
cax = div.append_axes('right', '5%', '5%')
def f(x, y):
return np.exp(x) + np.sin(y)
x = np.linspace(0, 1, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
# This is now a list of arrays rather than a list of artists
frames = []
for i in range(10):
x += 1
curVals = f(x, y)
frames.append(curVals)
cv0 = frames[0]
im = ax.imshow(cv0, origin='lower') # Here make an AxesImage rather than contour
cb = fig.colorbar(im, cax=cax)
tx = ax.set_title('Frame 0')
def animate(i):
arr = frames[i]
vmax = np.max(arr)
vmin = np.min(arr)
im.set_data(arr)
im.set_clim(vmin, vmax)
tx.set_text('Frame {0}'.format(i))
# In this version you don't have to do anything to the colorbar,
# it updates itself when the mappable it watches (im) changes
ani = animation.FuncAnimation(fig, animate, frames=10)
plt.show()

Categories