Scatter plot in matplotlib not updating xlim and ylim - matplotlib

Given the below code I would expect the x-axis to be between 0 and 3 with some margins added.
Instead it is much larger. I would expect the call to scatter to automatically update x-axis limits.
I could set the xlim and ylim my self but would like them to be set automatically. What am I doing wrong?
import matplotlib.pyplot as plt
if __name__ == '__main__':
fig = plt.figure()
ax = fig.add_subplot(111)
x = [0, 4000, 8000, 100000]
y = [0, 10, 100, 150]
ax.scatter(x, y)
x = [0, 1, 2, 3]
y = x
ax.clear()
ax.scatter(x, y)
plt.show()

You can clear the figure, and open a new subplot, than the axes will be adjusted as you wanted.
fig = plt.figure()
ax = fig.add_subplot(111)
x = [0, 4000, 8000, 100000]
y = [0, 10, 100, 150]
ax.scatter(x, y)
plt.clf()
ax = fig.add_subplot(111)
x = [0, 1, 2, 3]
y = x
ax.scatter(x, y)
plt.show()
Edit: In this version figure is not closed, just cleared with the clf function.

It is a feature that scatter does not automatically re-limit the graph as in many cases that would be undesirable. See Axes.autoscale and Axes.relim
ax.relim() # might not be needed
ax.autoscale()
should do what you want.

Here is a way of making another scatter plot without needing to clear the figure.
I basically update offests of PathCollection returned by axes.scatter() and add the collection back to the axes. Of course, the axes has to be cleared first. One thing I notice is that I have to manually set the margins.
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
x = [0, 4000, 8000, 100000]
y = [0, 10, 100, 150]
scat = ax.scatter(x, y)
x = [0, 1, 2, 3]
y = x
ax.clear()
corners = (min(x), min(y)), (max(x), max(y))
ax.update_datalim(corners)
ax.margins(0.05, 0.05)
ax.autoscale_view()
scat.set_offsets(np.vstack((x,y)).transpose())
ax.add_collection(scat)
plt.show()

If you comment out the first scatter, everything will be fine

Related

Subplots not taking arguments

I'm, trying to do a 3x9 subplots with a contourf from 3 lists, but when I plotted it it only apply the plot options (ylim, axis scaled, no axis tick) to the last plot, how can I instead apply to all the plots?
Furthermore all the plots results one on top of each other as illustrated in the pic below, how can I spaced them properly?
fig, axes = plt.subplots(3, 9)
for j in range(len(axes[0])):
levels = np.linspace(v_min, v_max, 21)
for i in range(1, 19, 2):
axes[0][j].contourf(V_avg[i], levels=levels, cmap=rgb_V)
np.linspace(v_min, v_max, 11)
for i in range(2, 20, 2):
axes[1][j].contourf(V_avg[i], levels=levels, cmap=rgb_V)
np.linspace(v_min, v_max, 11)
levels = np.linspace(v_min_d_avg, v_max_d_avg, 21)
for i in range(0, 9):
axes[2][j].contourf(V_avg_dud[i], levels=levels, cmap=rgb_D_V)
np.linspace(v_min_d_avg, v_max_d_avg, 11)
plt.xticks([])
plt.yticks([])
plt.axis('scaled')
plt.ylim([15, 90])
plt.savefig("aaa", dpi=300, bbox_inches='tight')
plt.show()
Thanks in advance.
You can use axes.flatten() to loop through the subplots.
Here is a simple example:
import numpy as np
import matplotlib.pyplot as plt
fig, axes = plt.subplots(3, 9, sharex=True, sharey=True)
for ax in axes.flatten():
x = np.random.randint(1, high=10, size=10)
y = np.random.randint(1, high=10, size=10)
ax.scatter(x, y)
plt.show()
Which produces this:
You don't give an example data set format, but here is an example where your data are in a dictionary:
# Build example dataset
keys = ["A", "B", "C", "D"]
data_dict = {}
for key in keys:
data_dict[key] = [
np.random.randint(1, high=10, size=10).tolist(),
np.random.randint(1, high=10, size=10).tolist(),
]
# Loop through dictionary for plotting
fig, axes = plt.subplots(2, 2, sharex=True, sharey=True)
axes = axes.flatten()
for index, (key, value) in enumerate(data_dict.items()):
x, y = value[0], value[1]
axes[index].scatter(x, y)
axes[index].set_title(key)
plt.show()

Colorbar in plots with embedded plots

While I managed to put a plot inside a plot (see the question here), I am finding trouble putting a colorbar to the larger (outside) plot. The code below is as simple as it gets, but for some reason it places the colorbar in the wrong axis:
import numpy as np
from numpy import random
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
# Canvas
fig, ax1 = plt.subplots(figsize=(12, 10))
left, bottom, width, height = [0.65, 0.15, 0.32, 0.30]
ax2 = fig.add_axes([left, bottom, width, height])
# Labels
xlabel = 'x'
ylabel = 'y'
cbarlabel = 'Color'
cmap = plt.get_cmap('turbo')
# Data
x, y, z = np.random.rand(3,200)
# Plotting
sc = ax1.scatter(x, y, marker='o', c=z, cmap=cmap)
ax2.scatter(x, y, c=z, cmap=cmap)
#
ax1.set_xlabel(xlabel)
ax1.set_ylabel(ylabel)
ax1.legend(fontsize=12, loc='upper left')
plt.tight_layout()
# Colormap
ax1 = plt.gca()
divider = make_axes_locatable(plt.gca())
cax = divider.append_axes("right", "2%", pad="1%")
cbar = plt.colorbar(sc, cax=cax) # Colorbar
cbar.set_label(cbarlabel, rotation=270, labelpad=30)
sc.set_clim(vmin=min(z), vmax=max(z))
#
plt.show()
I have also tried inset_axes as in the documentation example, to no avail.
The trick is to actually set active axes with plt.sca(ax1) and then create colorbar. I also simplified a code little bit.
Here is modified code putting colormap to the large plot:
import matplotlib.pyplot as plt
import numpy as np
from numpy import random
# Canvas
fig, ax1 = plt.subplots(figsize=(12, 10))
left, bottom, width, height = [0.45, 0.15, 0.32, 0.30]
ax2 = fig.add_axes([left, bottom, width, height])
# Labels
xlabel = 'x'
ylabel = 'y'
cbarlabel = 'Color'
cmap = plt.get_cmap('turbo')
# Data
x, y, z = np.random.rand(3,200)
# Plotting
sc = ax1.scatter(x, y, marker='o', c=z, cmap=cmap)
ax2.scatter(x, y, c=z, cmap=cmap)
# Set active axes
plt.sca(ax1)
cbar = plt.colorbar(sc) # Colorbar
cbar.set_label(cbarlabel, rotation=270, labelpad=30)
sc.set_clim(vmin=min(z), vmax=max(z))
#
ax1.set_xlabel(xlabel)
ax1.set_ylabel(ylabel)
ax1.legend(fontsize=12, loc='upper left')
plt.tight_layout()
plt.show()
Resulting in:

PyPlot: hide axes but keep axis labels

I have the following code:
import matplotlib.pyplot as plt
fig, axs = plt.subplots(1, 4)
for n, ax in enumerate(axs):
ax.plot([1, 2], [1, 2])
ax.set_xticks([])
ax.set_yticks([])
ax.set_xlabel(n)
plt.show()
...which displays this:
What I want is to hide the black boxes but keep the labels. I've tried adding ax.set_axis_off() but that removes the labels as well:
How can I do this?
Just change the color of spines to None:
fig, axs = plt.subplots(1, 4)
for n, ax in enumerate(axs):
ax.plot([1, 2], [1, 2])
ax.set_xticks([])
ax.set_yticks([])
ax.set_xlabel(n)
plt.setp(ax.spines.values(), color=None)
plt.show()

matplotlib messes up the X-axis if first or last y value is NaN/None. Is this normal?

After subplot.plot([1, 2, 3], [8, 9, 10]), subplot.axis() returns an x-axis that goes roughly from 1 to 3.
But after subplot.plot([1, 2, 3], [None, None, None]), x goes from 0 to 1.
After subplot.plot([1, 2, 3], [8, 9, None]), it goes from 1 to 2.
Is this normal? What should I do to get a correct chart in such a case? (for example, an empty chart spanning 1 to 3 if all None).
#!/usr/bin/env python3
import matplotlib
matplotlib.use("AGG")
import matplotlib.pyplot as plt
def create_plot(ydata):
fig = plt.figure()
subplot = fig.add_subplot(1, 1, 1)
subplot.scatter([1, 2, 3], ydata)
x1, x2, y1, y2 = subplot.axis()
print(f"X axis goes from {x1} to {x2} when ydata={ydata}")
create_plot([8, 9, 10])
create_plot([None, None, None])
create_plot([8, 9, None])
Yes, it's normal.
In case ax.plot([1, 2, 3], [None, None, None]) there is no data to plot, hence the axes does not autoscale and stays at the default [0,1] view limits.
In case ax.plot([1, 2, 3], [8, 9, None]), the only data to show are (1,8) and (2,9). Hence the x axis range is 1 to 2.
If you want to get the same x-axis limits with or without data, you can update the data limits
ax.update_datalim(np.c_[xdata,[0]*len(xdata)], updatey=False)
ax.autoscale()
Complete example:
import numpy as np
import matplotlib.pyplot as plt
def create_plot(ydata):
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
xdata = [1, 2, 3]
ax.scatter(xdata, ydata)
ax.update_datalim(np.c_[xdata,[0]*len(xdata)], updatey=False)
ax.autoscale()
x1, x2, y1, y2 = ax.axis()
print(f"X axis goes from {x1} to {x2} when ydata={ydata}")
create_plot([8, 9, 10])
create_plot([None, None, None])
create_plot([8, 9, None])
plt.show()
IIUC, this is normal as plt will ignore None data. To change the limit, you could set the limit of xaxis:
def create_plot(ydata):
xvals = [1,2,3]
fig = plt.figure()
subplot = fig.add_subplot(1, 1, 1)
subplot.scatter(xvals, ydata)
# set the limit
subplot.set_xlim(min(xvals), max(xvals))
x1, x2, y1, y2 = subplot.axis()
print(f"X axis goes from {x1} to {x2} when ydata={ydata}")

Label is Missing from matplotlib legend

I'm plotting subplots with matplotlib and the legend does not show up for some plots.
In this example, the scatter plot legend does not show up.
import numpy as np
import matplotlib
from matplotlib import pyplot as plt
from matplotlib.legend_handler import HandlerLine2D
from matplotlib.patches import Rectangle, Circle
fig = plt.figure()
plt.cla()
plt.clf()
x = np.arange(5) + 1
y = np.full(5, 10)
fig, subplots = plt.subplots(2, sharex=False, sharey=False)
subplots[0].bar(x, y, color='r', alpha=0.5, label='a')
scat = subplots[0].scatter(x, y-1, color='g', label='c')
subplots[0].set_yscale('log')
subplots[1].bar(x, y, color='r', alpha=0.5, label='a')
x = [2, 3]
y = [4, 4]
subplots[1].bar(x, y, color='b', alpha=1, label='b')
subplots[1].set_yscale('log')
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5), handler_map={scat: HandlerLine2D(numpoints=4)})
plt.show()
Here is what I tried as a workaround:
p1 = Rectangle((0, 0), 1, 1, fc="r", alpha=0.5)
p2 = Rectangle((0, 0), 1, 1, fc="b")
p3 = Circle((0, 0), 1, fc="g")
legend([p1, p2, p3], ['a', 'b', 'c'], loc='center left', bbox_to_anchor=(1, 0.5))
I really prefer to fix this without the workaround so if anyone knows how to fix it please let me know.
Also, an issue with the workaround is that the Circle object still appears as a bar on the legend.
plt.legend starts with a gca() (which returns the current axes):
# from pyplot.py:
def legend(*args, **kwargs):
ret = gca().legend(*args, **kwargs)
So calling plt.legend will only get you a legend on your last subplot. But it is also possible to call e.g. ax.legend(), or in your case subplots[0].legend(). Adding that to the end of your code gives me a legend for both subplots.
Sample:
for subplot in subplots:
subplot.legend(loc='center left', bbox_to_anchor=(1, 0.5))