I can create a scatter plot as follows:
fig, ax = plt.subplots()
x1 = [1, 1, 2]
y1 = [1, 2, 1]
x2 = [2]
y2 = [2]
ax.scatter(x1, y1, color="red", s=500)
ax.scatter(x2, y2, color="blue", s=500)
which gives
What I would like is something like the following (apologies for poor paint work):
I am plotting data that is all integer values, so they're all on a grid. I would like to be able to control the size of the scatter marker so that I could have white space around the points, or I could make the points large enough such that there would be no white space around them (as I have done in the above paint image).
Note - ideally the solution will be in pure matplotlib, using the OOP interface as they suggest in the documentation.
import matplotlib.pyplot as plt
import matplotlib as mpl
# X and Y coordinates for red circles
red_xs = [1,2,3,4,1,2,3,4,1,2,1,2]
red_ys = [1,1,1,1,2,2,2,2,3,3,4,4]
# X and Y coordinates for blue circles
blu_xs = [3,4,3,4]
blu_ys = [3,3,4,4]
# Plot with a small markersize
markersize = 5
fig, ax = plt.subplots(figsize=(3,3))
ax.plot(red_xs, red_ys, marker="o", color="r", linestyle="", markersize=markersize)
ax.plot(blu_xs, blu_ys, marker="o", color="b", linestyle="", markersize=markersize)
plt.show()
# Plot with a large markersize
markersize = 50
fig, ax = plt.subplots(figsize=(3,3))
ax.plot(red_xs, red_ys, marker="o", color="r", linestyle="", markersize=markersize)
ax.plot(blu_xs, blu_ys, marker="o", color="b", linestyle="", markersize=markersize)
plt.show()
# Plot with using patches and radius
r = 0.5
fig, ax = plt.subplots(figsize=(3,3))
for x, y in zip(red_xs, red_ys):
ax.add_patch(mpl.patches.Circle((x,y), radius=r, color="r"))
for x, y in zip(blu_xs, blu_ys):
ax.add_patch(mpl.patches.Circle((x,y), radius=r, color="b"))
ax.autoscale()
plt.show()
Related
I'm plotting 3 things at once: a multicolored line via a LineCollection (following this) and a scatter (for the markers to "cover" where the lines are joining) for an average value, and a fill_between for min/max. I get all the legend returns to plot a single legend handle. The graph looks like this:
As one can note, the circle marker is not aligned with the line. How can I adjust this?
The piece of the code that is plotting them and the legend looks like:
lc = LineCollection(segments, cmap='turbo',zorder=3)
p1 = ax.add_collection(lc)
p2 = ax.fill_between(x, errmin,errmax, color=colors[1],zorder=2)
ps = ax.scatter(x,y,marker='o',s=1,c=y,cmap='turbo',zorder=4)
ax.legend([(p2, p1, ps)], ["(min/avg/max)"],fontsize=tinyfont, facecolor='white', loc='lower right')
The legend has a parameter scatteryoffsets= which defaults to [0.375, 0.5, 0.3125]. As only one point is shown, setting it to [0.5] should show the dot in the center of the legend marker.
To change the color of the line in the legend, one could create a copy of the existing line, change its color and create the legend with that copy.
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
import numpy as np
from copy import copy
x = np.arange(150)
y = np.random.randn(150).cumsum()
y -= y.min()
y /= y.max()
errmin = 0
errmax = 1
segments = np.array([x[:-1], y[:-1], x[1:], y[1:]]).T.reshape(-1, 2, 2)
fig, ax = plt.subplots(figsize=(12, 3))
lc = LineCollection(segments, cmap='turbo', zorder=3)
lc.set_array(y[:-1])
p1 = ax.add_collection(lc)
p2 = ax.fill_between(x, errmin, errmax, color='lightblue', zorder=2, alpha=0.4)
ps = ax.scatter(x, y, marker='o', s=5, color='black', zorder=4)
p1copy = copy(p1)
p1copy.set_color('crimson')
leg = ax.legend([(p2, p1copy, ps)], ["(min/avg/max)"], fontsize=10, facecolor='white', loc='lower right',
scatteryoffsets=[0.5])
ax.margins(x=0.02)
plt.show()
I'm working on a 3D plot displayed by a wireframe, where 2D plots are projected on the x, y, and z surface, respectively. Below you can find a minimum example.
I have 2 questions:
With contourf, the 2D plots for every x=10, x=20,... or y=10, y=20,... are displayed on the plot walls. Is there a possibility to define for which x or y, respectively, the contour plots are displayed? For example, in case I only want to have the xz contour plot for y = 0.5 mirrored on the wall?
ADDITION: To display what I mean with "2D plots", I changed "contourf" in the code to "contour" and added the resulting plot to this question. Here you can see now the xz lines for different y values, all offset to y=90. What if I do not want to have all the lines, but only two of them for defined y values?
3D_plot_with_2D_contours
As you can see in the minimum example, the 2D contour plot optically covers the wireframe 3D plot. With increasing the transparency with alpha=0.5 I can increase the transparency of the 2D contours to at least see the wireframe, but it is still optically wrong. Is it possible to sort the objects correctly?
import matplotlib.pyplot as plt,numpy as np
import pylab as pl
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt,numpy as np
plt.clf()
fig = plt.figure(1,figsize=(35,17),dpi=600,facecolor='w',edgecolor='k')
fig.set_size_inches(10.5,8)
ax = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
Xnew = X + 50
Ynew = Y + 50
cset = ax.contourf(Xnew, Ynew, Z, zdir='z', offset=-100, cmap=plt.cm.coolwarm, alpha=0.5)
cset = ax.contourf(Xnew, Ynew, Z, zdir='x', offset=10, cmap=plt.cm.coolwarm, alpha=0.5)
cset = ax.contourf(Xnew, Ynew, Z, zdir='y', offset=90, cmap=plt.cm.coolwarm, alpha = 0.5)
ax.plot_wireframe(Xnew, Ynew, Z, rstride=5, cstride=5, color='black')
Z=Z-Z.min()
Z=Z/Z.max()
from scipy.ndimage.interpolation import zoom
Xall=zoom(Xnew,5)
Yall=zoom(Ynew,5)
Z=zoom(Z,5)
ax.set_xlim(10, 90)
ax.set_ylim(10, 90)
ax.set_zlim(-100, 100)
ax.tick_params(axis='z', which='major', pad=10)
ax.set_xlabel('X',labelpad=10)
ax.set_ylabel('Y',labelpad=10)
ax.set_zlabel('Z',labelpad=17)
ax.view_init(elev=35., azim=-70)
fig.tight_layout()
plt.show()
ADDITION 2: Here is the actual code I'm working with. However, the original data are hidden in the csv files which are too big to be included in the minimal example. That's why was initially replacing them by the test data. However, maybe the actual code helps nevertheless.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt,numpy as np
import pylab as pl
from matplotlib.markers import MarkerStyle
import csv
with open("X.csv", 'r') as f:
X = list(csv.reader(f, delimiter=";"))
import numpy as np
X = np.array(X[1:], dtype=np.float)
import csv
with open("Z.csv", 'r') as f:
Z = list(csv.reader(f, delimiter=";"))
import numpy as np
Z = np.array(Z[1:], dtype=np.float)
Y = [[7,7.1,7.2,7.3,7.4,7.5,7.6,7.7,7.8,7.9,8,8.1,8.2,8.3,8.4,8.5,8.6,8.7,8.8,8.9,9]]
Xall = np.repeat(X[:],21,axis=1)
Yall = np.repeat(Y[:],30,axis=0)
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt,numpy as np
plt.clf()
fig = plt.figure(1,figsize=(35,17),dpi=600,facecolor='w',edgecolor='k')
fig.set_size_inches(10.5,8)
ax = fig.gca(projection='3d')
cset = ax.contourf(Xall, Yall, Z, 2, zdir='x', offset=0, cmap=plt.cm.coolwarm, shade = False, edgecolor='none', alpha=0.5)
cset = ax.contourf(Xall, Yall, Z, 2, zdir='y', offset=9, cmap=plt.cm.coolwarm, shade = False, edgecolor='none', alpha=0.5)
ax.plot_wireframe(Xall, Yall, Z, rstride=1, cstride=1, color='black')
Z=Z-Z.min()
Z=Z/Z.max()
from scipy.ndimage.interpolation import zoom
Xall=zoom(Xall,5)
Yall=zoom(Yall,5)
Z=zoom(Z,5)
cset = ax.plot_surface(Xall, Yall, np.zeros_like(Z)-0,facecolors=plt.cm.coolwarm(Z),shade=False,alpha=0.5,linewidth=False)
ax.set_xlim(-0.5, 31)
ax.set_ylim(6.9, 9.1)
ax.set_zlim(0, 500)
labelsx = [item.get_text() for item in ax.get_xticklabels()]
empty_string_labelsx = ['']*len(labelsx)
ax.set_xticklabels(empty_string_labelsx)
labelsy = [item.get_text() for item in ax.get_yticklabels()]
empty_string_labelsy = ['']*len(labelsy)
ax.set_yticklabels(empty_string_labelsy)
labelsz = [item.get_text() for item in ax.get_zticklabels()]
empty_string_labelsz = ['']*len(labelsz)
ax.set_zticklabels(empty_string_labelsz)
import matplotlib.ticker as ticker
ax.xaxis.set_major_locator(ticker.MultipleLocator(5))
ax.xaxis.set_minor_locator(ticker.MultipleLocator(1))
ax.yaxis.set_major_locator(ticker.MultipleLocator(0.5))
ax.yaxis.set_minor_locator(ticker.MultipleLocator(0.25))
ax.zaxis.set_major_locator(ticker.MultipleLocator(100))
ax.zaxis.set_minor_locator(ticker.MultipleLocator(50))
ax.tick_params(axis='z', which='major', pad=10)
ax.set_xlabel('X',labelpad=5,fontsize=15)
ax.set_ylabel('Y',labelpad=5,fontsize=15)
ax.set_zlabel('Z',labelpad=5,fontsize=15)
ax.view_init(elev=35., azim=-70)
fig.tight_layout()
plt.show()
Alternate possible answer.
This code demonstrates
A plot of a surface and its correponding wireframe
The creation of data and its plot of 3d lines (draped on the surface in 1) at specified values of x and y
Projections of the 3d lines (in 2) on to the frame walls
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from scipy import interpolate
import numpy as np
# use the test data for plotting
fig = plt.figure(1, figsize=(6,6), facecolor='w', edgecolor='gray')
ax = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.1) #get 3d data at appropriate density
# create an interpolating function
# can take a long time if data is too large
f1 = interpolate.interp2d(X, Y, Z, kind='linear')
# in general, one can use a set of other X,Y,Z that cover a surface
# preferably, (X,Y) are in grid arrangement
# make up a new set of 3d data to plot
# ranges of x1, and y1 will be inside (X,Y) of the data obtained above
# related grid, x1g,y1g,z1g will be obtained from meshgrid and the interpolated function
x1 = np.linspace(-15,15,10)
y1 = np.linspace(-15,15,10)
x1g, y1g = np.meshgrid(x1, y1)
z1g = f1(x1, y1) #dont use (x1g, y1g)
# prep data for 3d line on the surface (X,Y,Z) at x=7.5
n = 12
x_pf = 7.5
x5 = x_pf*np.ones(n)
y5 = np.linspace(-15, 15, n)
z5 = f1(x_pf, y5)
# x5,y5,z5 can be used to plot 3d line on the surface (X,Y,Z)
# prep data for 3d line on the surface (X,Y,Z) at y=6
y_pf = 6
x6 = np.linspace(-15, 15, n)
y6 = x_pf*np.ones(n)
z6 = f1(x6, y_pf)
# x6,y6,z6 can be used to plot 3d line on the surface (X,Y,Z)
ax = fig.gca(projection='3d')
ax.plot_surface(x1g, y1g, z1g, alpha=0.25)
ax.plot_wireframe(x1g, y1g, z1g, rstride=2, cstride=2, color='black', zorder=10, alpha=1, lw=0.8)
# 3D lines that follow the surface
ax.plot(x5,y5,z5.flatten(), color='red', lw=4)
ax.plot(x6,y6,z6.flatten(), color='green', lw=4)
# projections of 3d curves
# project red and green lines to the walls
ax.plot(-15*np.ones(len(y5)), y5, z5.flatten(), color='red', lw=4, linestyle=':', alpha=0.6)
ax.plot(x6, 15*np.ones(len(x6)), z6.flatten(), color='green', lw=4, linestyle=':', alpha=0.6)
# projections on other sides (become vertical lines)
# change to if True, to plot these
if False:
ax.plot(x5, 15*np.ones(len(x5)), z5.flatten(), color='red', lw=4, alpha=0.3)
ax.plot(-15*np.ones(len(x6)), y6, z6.flatten(), color='green', lw=4, alpha=0.3)
ax.set_title("Projections of 3D lines")
# set limits
ax.set_xlim(-15, 15.5)
ax.set_ylim(-15.5, 15)
plt.show();
(Answer to question 1) To plot the intersections between the surface and the specified planes (y=-20, and y=20), one need to find what Y[?]=-20 and 20. By inspection, I found that Y[100]=20, Y[20]=-20.
The relevant code to plot the lines of intersection:
# By inspection, Y[100]=20, Y[20]=-20
ax.plot3D(X[100], Y[100], Z[100], color='red', lw=6) # line-1 at y=20
ax.plot3D(X[20], Y[20], Z[20], color='green', lw=6) # line-2 at y=-20
# Project them on Z=-100 plane
ax.plot3D(X[100], Y[100], -100, color='red', lw=3) # projection of Line-1
ax.plot3D(X[20], Y[20], -100, color='green', lw=3) # projection of Line-2
The output plot:
(Answer to question 2) To get better plot with the wireframe standout from the surface plot. The surface plot must be partially transparent, which is achieved by setting option alpha=0.6. The relevant code follows.
Z1 = Z-Z.min()
Z1 = Z1/Z.max()
Xall = zoom(X,3)
Yall = zoom(Y,3)
Zz = zoom(Z1, 3)
surf = ax.plot_surface(Xall, Yall, Zz, rstride=10, cstride=10,
facecolors = cm.jet(Zz/np.amax(Zz)),
linewidth=0, antialiased=True,
alpha= 0.6)
# Wireframe
ax.plot_wireframe(X, Y, Z, rstride=5, cstride=5, color='black', alpha=1, lw=0.8)
The plot is:
This is a very similar question to "How to plot pcolor colorbar in a different subplot - matplotlib". I am trying to plot a filled contour plot and a line plot with a shared axis and the colorbar in a separate subplot (i.e. so it doesn't take up space for the contourf axis and thus muck up the x-axis sharing). However, the x-axis in my code does not rescale nicely:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
z = np.random.rand(20, 20)
x, y = np.arange(20), np.arange(20)
y2 = np.random.rand(20)
fig = plt.figure(figsize=(8, 8))
gs = mpl.gridspec.GridSpec(2, 2, height_ratios=[1, 2], width_ratios=[2, 1])
ax1 = fig.add_subplot(gs[1, 0])
ax2 = fig.add_subplot(gs[0, 0], sharex=ax1)
ax3 = fig.add_subplot(gs[1, 1])
cont = ax1.contourf(x, y, z, 20)
plt.tick_params(which='both', top=False, right=False)
ax2.plot(x, y2, color='g')
plt.tick_params(which='both', top=False, right=False)
cbar = plt.colorbar(cont, cax=ax3)
cbar.set_label('Intensity', rotation=270, labelpad=20)
plt.tight_layout()
plt.show()
which produces an x-axis scaled from 0 to 20 (inclusive) rather than 0 to 19, which means there is unsightly whitespace in the filled contour plot. Commenting out the sharex=ax1 in the above code means that the x-axis for the contour plot is scaled nicely, but not for the line plot above it and the plt.tick_params code has no effect on either axis.
Is there a way of solving this?
You could also turn off the autoscaling of x-axis for all subsequent call of plot on this axis so that it keeps the range set by contourf and sharex=True :
ax2.set_autoscalex_on(False)
This comes even before your call to ax2.plot() and I think it is better than calling ax2.set_xlim(0, 19) since you do not need to know what are the actual limit of your x-axis that may be needed.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
z = np.random.rand(20, 20)
x, y = np.arange(20), np.arange(20)
y2 = np.random.rand(20)
fig = plt.figure(figsize=(8, 8))
gs = mpl.gridspec.GridSpec(2, 1, height_ratios=[1, 2], width_ratios=[2])
ax1 = fig.add_subplot(gs[1, 0])
ax2 = fig.add_subplot(gs[0, 0], sharex=ax1)
cont = ax1.contourf(x, y, z, 20)
plt.tick_params(which='both', top=False, right=False)
ax2.set_autoscalex_on(False)
ax2.plot(x, y2, color='g')
axins = inset_axes(ax1,
width="5%", # width = 10% of parent_bbox width
height="100%", # height : 50%
loc=6,
bbox_to_anchor=(1.05, 0., 1, 1),
bbox_transform=ax1.transAxes,
borderpad=0,
)
cbar = plt.colorbar(cont, cax=axins)
plt.show()
You can use inset_axes for this without added another axis.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
z = np.random.rand(20, 20)
x, y = np.arange(20), np.arange(20)
y2 = np.random.rand(20)
fig = plt.figure(figsize=(8, 8))
gs = mpl.gridspec.GridSpec(2, 2, height_ratios=[1, 2], width_ratios=[2, 1])
ax1 = fig.add_subplot(gs[1, 0])
ax2 = fig.add_subplot(gs[0, 0], sharex=ax1)
cont = ax1.contourf(x, y, z, 20)
plt.tick_params(which='both', top=False, right=False)
ax2.plot(x, y2, color='g')
plt.tick_params(which='both', top=False, right=False)
axins = inset_axes(ax1,
width="5%", # width = 10% of parent_bbox width
height="100%", # height : 50%
loc=6,
bbox_to_anchor=(1.05, 0., 1, 1),
bbox_transform=ax1.transAxes,
borderpad=0,
)
cbar = plt.colorbar(cont, cax=axins)
plt.savefig('figure.jpg',bbox_inches='tight',dpi=200)
I'm trying to generate two subplots side by side, sharing the y axis, with a single colorbar for both.
This is a MWE of my code:
import matplotlib.pyplot as plt
import numpy as np
def rand_data(l, h):
return np.random.uniform(low=l, high=h, size=(100,))
# Generate data.
x1, x2, y, z = rand_data(0., 1.), rand_data(100., 175.), \
rand_data(150., 200.), rand_data(15., 33.)
fig = plt.figure()
cm = plt.cm.get_cmap('RdYlBu')
ax0 = plt.subplot(121)
plt.scatter(x1, y, c=z, cmap=cm)
ax1 = plt.subplot(122)
# make these y tick labels invisible
plt.setp(ax1.get_yticklabels(), visible=False)
plt.scatter(x2, y, c=z, cmap=cm)
cbar = plt.colorbar()
plt.show()
what this returns is a left subplot slightly larger horizontally than the right one since this last includes the colorbar, see below:
I've tried using ax.set_aspect('equal') but since the x axis are not in the same range the result looks awful.
I need both these plots to be displayed squared. How can I do this?
To expend my comment that one can make 3 plots, plot the colorbar() in the 3rd one, the data plots in the 1st and 2nd. This way, if necessary, we are free to do anything we want to the 1st and 2nd plots:
def rand_data(l, h):
return np.random.uniform(low=l, high=h, size=(100,))
# Generate data.
x1, x2, y, z = rand_data(0., 1.), rand_data(100., 175.), \
rand_data(150., 200.), rand_data(15., 33.)
fig = plt.figure(figsize=(12,6))
gs=gridspec.GridSpec(1,3, width_ratios=[4,4,0.2])
ax1 = plt.subplot(gs[0])
ax2 = plt.subplot(gs[1])
ax3 = plt.subplot(gs[2])
cm = plt.cm.get_cmap('RdYlBu')
ax1.scatter(x1, y, c=z, cmap=cm)
SC=ax2.scatter(x2, y, c=z, cmap=cm)
plt.setp(ax2.get_yticklabels(), visible=False)
plt.colorbar(SC, cax=ax3)
plt.tight_layout()
plt.savefig('temp.png')
Updated - here is another option without using GridSpec.
import numpy as np
import matplotlib.pyplot as plt
N = 50
x_vals = np.random.rand(N)
y_vals = np.random.rand(N)
z1_vals = np.random.rand(N)
z2_vals = np.random.rand(N)
minimum_z = min(np.min(z1_vals), np.min(z2_vals))
maximum_z = max(np.max(z1_vals), np.max(z2_vals))
fig, axis_array = plt.subplots(1,2, figsize = (20, 10), subplot_kw = {'aspect':1})
ax0 = axis_array[0].scatter(x_vals, y_vals, c = z1_vals, s = 100, cmap = 'rainbow', vmin = minimum_z, vmax = maximum_z)
ax1 = axis_array[1].scatter(x_vals, y_vals, c = z2_vals, s = 100, cmap = 'rainbow', vmin = minimum_z, vmax = maximum_z)
cax = fig.add_axes([0.95, 0.05, 0.02, 0.95]) #this locates the axis that is used for your colorbar. It is scaled 0 - 1.
fig.colorbar(ax0, cax, orientation = 'vertical') #'ax0' tells it which plot to base the colors on
plt.show()
In Jfreechart there is a method called setQuadrantPaint which let's you set the background colour of a given quandrant in a plot.
How would you achieve the equivalent in matplotlib?
E.g.
You can plot a 2x2 array with imshow in the background. Giving it an extent will make the center of it always at 0,0.
import numpy as np
import matplotlib.pyplot as plt
x1, y1 = np.random.randint(-8,8,5), np.random.randint(-8,8,5)
x2, y2 = np.random.randint(-8,8,5), np.random.randint(-8,8,5)
vmax = np.abs(np.concatenate([x1,x2,y1,y2])).max() + 5
extent = [vmax*-1,vmax, vmax*-1,vmax]
arr = np.array([[1,0],[0,1]])
fig, ax = plt.subplots(1,1)
ax.scatter(x1,y1, marker='s', s=30, c='r', edgecolors='red', lw=1)
ax.scatter(x2,y2, marker='s', s=30, c='none', edgecolors='red', lw=1)
ax.autoscale(False)
ax.imshow(arr, extent=extent, cmap=plt.cm.Greys, interpolation='none', alpha=.1)
ax.axhline(0, color='grey')
ax.grid(True)
Setting the autoscale to False after the data points are plotted, but before the image is, makes sure that the axes scales only to the data points.