Related
I'm looking for a way to assign color to line plots in matplotlib in a way that's responsive to the line's angle. This is my current code:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
horz = [[0.5,0.6,0.8],[0.1,0.8,0.9],[0.2,0.5,0.9]]
vert = [[0.1,0.2,0.3],[0.05,0.1,0.15],[0.2,0.3,0.35]]
f = plt.figure(figsize=(6,6))
ax = plt.axes()
for column in range(0,len(horz)):
x = np.array(horz[column])
y = np.array(vert[column])
#LINEAR TRENDLINE
z = np.polyfit(horz[column], vert[column], 1)
p = np.poly1d(z)
ax.plot(horz[column],p(horz[column]),"-")
plt.arrow(x=horz[column][-2],y=p(horz[column])[-2],dx=(horz[column][-1]-horz[column][-2]),dy=(p(horz[column])[-1]-p(horz[column])[-2]), shape='full', lw=.01,
length_includes_head=True, head_width=.012, head_length=0.02, head_starts_at_zero=False, overhang = 0.5)
#FIG SETTINGS
plt.xlim([0, 1])
plt.ylim([0.1,0.5])
ax.set_title('Title',
fontsize = 14)
The idea here would be that if the line is at 0 degrees, it would be at one end of a given gradient, and if it were at 90 degrees, at the other end. Additionally, I'd like the line length to be taken as the intensity of the color. So if the line is short, it'd be closer to white, and if the line is long, it'd be closer to the raw color from the gradient.
Managed to solve it myself. Used pretty simple formulas for calculating the lines' slopes and distances and then used these as input for the color mapping and alpha transparency attribute.
import geopandas as gpd
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib.colors as colors
import numpy as np
%matplotlib inline
#Data
horz = [[0.5,0.6,0.8],[0.1,0.3,0.4],[0.2,0.5,0.9],[0.9,0.95,0.95]]
vert = [[0.1,0.2,0.45],[0.05,0.1,0.15],[0.2,0.3,0.35],[0.1,0.3,0.5]]
#Slope calculation
def slopee(x1,y1,x2,y2):
x = (y2 - y1) / (x2 - x1)
return x
#Color set up
cmap = plt.cm.coolwarm_r
#0 means a horizontal line, 1 means a line at 45 degrees, infinite means a vertical line (2 is vertical enough)
cNorm = colors.Normalize(vmin=0, vmax=2)
scalarMap = cm.ScalarMappable(norm=cNorm,cmap=cmap)
#Fig settings
f = plt.figure(figsize=(6,6))
ax = plt.axes()
for column in range(0,len(horz)):
x = np.array(horz[column])
y = np.array(vert[column])
#LINEAR TRENDLINE
# 1 LINEAR
# >=2 POLINOMIAL
z = np.polyfit(horz[column], vert[column], 1)
p = np.poly1d(z)
#Distance calc formula
def calculateDistance(x1,y1,x2,y2):
dist = np.sqrt((x2 - x1)**2 + (y2 - y1)**2)
return dist
#Set up max an min distances
maxdist = calculateDistance(0,0,0,0.9)
mindist = calculateDistance(0,0,0,0)
#Calculate line slope
slope = slopee(horz[column][0],p(horz[column])[0],horz[column][-1],p(horz[column])[-1])
#Not interested in any slopes going "down"
if slope >=0:
#Map colors based on slope (0-2)
colorVal = scalarMap.to_rgba(slope)
#Map transparency based on distance
transparency = (calculateDistance(horz[column][0],p(horz[column])[0],horz[column][-1],p(horz[column])[-1])-mindist)/(maxdist-mindist)
#Set up minimun transparency to be 50% instead of 0%
transparency = (0.5*transparency) + 0.5
#The actual arrow plot
plt.arrow(x=horz[column][0],y=p(horz[column])[0],dx=(horz[column][-1]-horz[column][0]),dy=(p(horz[column])[-1]-p(horz[column])[0]), shape='full',length_includes_head=True, head_starts_at_zero=False, lw=.5, head_width=.011, head_length=0.01, overhang = 0.5, color=colorVal,alpha=transparency)
#FIG SETTINGS
plt.xlim([0, 1])
plt.ylim([0,0.5])
ax.set_title('Title',fontsize = 14)
Congrats on solving it yourself. I had put this together before I realized you had posted your answer. Very similar approach:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm, colors
from math import sqrt
plt.rcParams["figure.figsize"] = (15,15)
# Create a color mapper for degress to color
values = np.linspace(1.0, 90.0, 90)
norm = colors.Normalize(vmin=0.0, vmax=90.0, clip=True)
mapper = cm.ScalarMappable(norm=norm, cmap=cm.coolwarm_r)
horz = [[0.5,0.6,0.8],[0.1,0.3,0.4],[0.2,0.5,0.9],[0.9,0.95,0.95]]
vert = [[0.1,0.2,0.45],[0.05,0.1,0.15],[0.2,0.3,0.35],[0.1,0.3,0.5]]
f = plt.figure(figsize=(15,15))
ax = plt.axes()
# Calculate lengths of each line
lengths = [sqrt((x[-1]-x[0])**2 + (y[-1]-y[0])**2) for x,y in zip(horz, vert)]
for x,y,length in zip(horz,vert, lengths):
alpha = length / sum(lengths)
angle = np.rad2deg(np.arctan2(y[-1] - y[0], x[-1] - x[0]))
color = mapper.to_rgba(angle)
plt.arrow(x[0],y[0],x[-1]-x[0], y[-1]-y[0],shape='full', lw=2,
length_includes_head=True, head_width=.012, head_length=0.02, head_starts_at_zero=False, overhang = 0.5, alpha=alpha, color=color)
I would like to make a 3D plot with several 2D line plot "slices" and shade the area between the x-axis and the curve (i.e. under the curve). When trying to do this with polygons I am getting filling but the correct areas are not being filled. Any help would be most appreciated!
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(15,15))
ax = fig.add_subplot(111, projection='3d')
colors = ['r','b','g','m']
phi = [0,np.pi/4,np.pi/3, np.pi/2]
for c, k in zip(colors, phi):
eps2 = 0.001j
eps = np.linspace(-3,3,10000)
E = eps + eps2
gR = ((1-(((np.cos(k)+np.sin(k)*1j)**2)/((E+np.sqrt(1-E**2)*1j)**4)))/(1+(((np.cos(k)+np.sin(k)*1j)**2)/((E+np.sqrt(1-E**2)*1j)**4))))*1j
N = gR.imag
utol = 2
N[N>utol] = 2
ax.plot(eps, N, k,zdir='y', color=c)
verts = [list(zip(eps,N))]
poly = PolyCollection(verts, facecolors=c)
poly.set_alpha(1)
ax.add_collection3d(poly, zs=k,zdir='y')
ax.set_xlabel('Energy')
ax.set_ylabel('Phi')
ax.set_zlabel('DOS')
ax.set_yticks(phi)
ax.set_zlim(0,2)
ax.set_ylim(0,2)
plt.show()
Incorrect Plot for reference:
You created a polygon by connecting the first and last vertex of your curves. As these vertices have y = 2 everything gets connected with the horizontal line at that y-value.
To close the polygon at zero, repeat the first and the last x-value (np.pad(eps, 1, mode='edge')) and pad the y-values with a zero at both ends (np.pad(N, 1)).
If desired, ax.set_yticklabels(...) can show the y-ticks as a formula with pi.
Further, matplotlib seems to have a serious problem about deciding the relative depth of each polygon, showing them all mixed up. A workaround could be to rotate everything 180 degrees, e.g. by setting ax.view_init(elev=22, azim=130).
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(15, 15))
ax = fig.add_subplot(111, projection='3d')
colors = ['r', 'b', 'g', 'm']
phi = [0, np.pi / 4, np.pi / 3, np.pi / 2]
for c, k in zip(colors, phi):
eps2 = 0.001j
eps = np.linspace(-3, 3, 10000)
E = eps + eps2
gR = ((1 - (((np.cos(k) + np.sin(k) * 1j) ** 2) / ((E + np.sqrt(1 - E ** 2) * 1j) ** 4))) / (
1 + (((np.cos(k) + np.sin(k) * 1j) ** 2) / ((E + np.sqrt(1 - E ** 2) * 1j) ** 4)))) * 1j
N = gR.imag
utol = 2
N[N > utol] = 2
ax.plot(eps, N, k, zdir='y', color=c)
verts = [list(zip(np.pad(eps, 1, mode='edge'), np.pad(N, 1)))]
poly = PolyCollection(verts, facecolors=c)
poly.set_alpha(1)
ax.add_collection3d(poly, zs=k, zdir='y')
ax.set_xlabel('Energy')
ax.set_ylabel('Phi')
ax.set_zlabel('DOS')
ax.set_yticks(phi)
ax.set_yticklabels(['$0$' if k == 0 else f'$\pi / {np.pi / k:.0f}$' for k in phi])
ax.set_zlim(0, 2)
ax.set_ylim(0, 2)
ax.view_init(elev=22, azim=130)
plt.show()
I'm trying to create 10 points distributed on a circle, just like a watch but with 10 numbers instead of 12. and also be able to use the dots/points to plot lines between them.
This is the code I used to create a circle but can't figure out how to make them 10 points, and also how to use the points' coordinates in my code.
import matplotlib.pyplot as plt
import numpy as np
# T = testing
# myList = testing.digitcounter(18, 20)
def circle_points(r, n):
circles = []
for r, n in zip(r, n):
t = np.linspace(0, 2*np.pi, n)
x = r * np.cos(t)
y = r * np.sin(t)
circles.append(np.c_[x, y])
return circles
r = [0.15]
n = [15]
circles = circle_points(r, n)
fig, ax = plt.subplots()
for circle in circles:
ax.scatter(circle[:, 0], circle[:, 1])
ax.set_aspect('equal')
plt.show()
With t = np.linspace(0, 2*np.pi, 10, endpoint=False), you can create 10 angles equally distributed over a circle. The default endpoint=True would have the first and last angle coincide. To have the first point at the top, and going clockwise, interchange cos and sin in the formula.
To plot a continuous line between digits, you could use ax.plot(circle[digits, 0], circle[digits, 1]), with digits a numpy array of integers between 0 and 1. Note that this will contain a line of zero length when two subsequent digits would be equal.
import matplotlib.pyplot as plt
import numpy as np
def circle_points(r, n):
circles = []
for r, n in zip(r, n):
t = np.linspace(0, 2 * np.pi, n, endpoint=False)
x = r * np.sin(t)
y = r * np.cos(t)
circles.append(np.c_[x, y])
return circles
digits = np.random.randint(0, 10, 7) # 7 random digits between 0 and 9
circle10 = circle_points([0.15], [10])[0] # 10 points on a circle
circle10 = circle10[(np.arange(10) - 3) % 10, :] # with 3 at the top
fig, ax = plt.subplots()
ax.scatter(circle10[:, 0], circle10[:, 1], color='crimson')
ax.plot(circle10[digits, 0], circle10[digits, 1], color='dodgerblue', lw=3)
for i, (x, y) in enumerate(circle10):
ax.text(x * 1.1, y * 1.1, i, ha='center', va='center', color='crimson')
ax.set_aspect('equal')
ax.margins(x=0.1, y=0.1) # extra margins, because the text isn't taken into account for the default margins
ax.set_title("".join([f'{d}' for d in digits]), size=16)
ax.axis('off')
plt.show()
Similarly, a 17-pointed star could be drawn with:
N = 17
circle = circle_points([0.15], [N])[0]
for i in range(N):
ax.plot([circle[i, 0], circle[(i + 6) % N, 0]],
[circle[i, 1], circle[(i + 6) % N, 1]],
color='dodgerblue', lw=3)
I have the following code which produces a cylinder-like object using matplotlib:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
nphi,nz=7,20
r=1 # radius of cylinder
phi = np.linspace(0,360, nphi)/180.0*np.pi
z= np.linspace(0,1.0,nz)
print z
cols=[]
verts2 = []
for i in range(len(phi)-1):
cp0= r*np.cos(phi[i])
cp1= r*np.cos(phi[i+1])
sp0= r*np.sin(phi[i])
sp1= r*np.sin(phi[i+1])
for j in range(len(z)-1):
z0=z[j]
z1=z[j+1]
verts=[]
verts.append((cp0, sp0, z0))
verts.append((cp1, sp1, z0))
verts.append((cp1, sp1, z1))
verts.append((cp0, sp0, z1))
verts2.append(verts)
value=np.random.rand()
#print value
col=plt.cm.rainbow(0.9)
#print col
cols.append(col)
poly3= Poly3DCollection(verts2, facecolor=cols,edgecolor = "none" )
poly3.set_alpha(0.8)
ax.add_collection3d(poly3)
ax.set_xlabel('X')
ax.set_xlim3d(-1, 1)
ax.set_ylabel('Y')
ax.set_ylim3d(-1, 1)
ax.set_zlabel('Z')
ax.set_zlim3d(0, 1)
plt.show()
This code produces the following image:
However as you can see the are sharp corners in the figure. Is there anyway to make these edges rounder so that the figure looks like a proper cylinder with a circular cross-section as opposed to a hexagonal cross-section?
The third argument to
np.linspace
controls how many values you want it to generate. Thus, nphi controls the
number of values in phi, and nz controls the number of values in z:
phi = np.linspace(0,360, nphi)/180.0*np.pi
z = np.linspace(0,1.0,nz)
So if you increase nphi, then you'll get more points along the circle:
cp0 = r*np.cos(phi[i])
sp0 = r*np.sin(phi[i])
For example, try changing nphi, nz = 7,20 to nphi, nz = 70, 2.
Note that there is no need for nz to be greater than 2 since the sides of the
cylinder are flat in the z direction.
By the way, the double for-loop can be replaced by:
PHI, Z = np.meshgrid(phi, z)
CP = r * np.cos(PHI)
SP = r * np.sin(PHI)
XYZ = np.dstack([CP, SP, Z])
verts = np.stack(
[XYZ[:-1, :-1], XYZ[:-1, 1:], XYZ[1:, 1:], XYZ[1:, :-1]], axis=-2).reshape(-1, 4, 3)
So, for example,
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
nphi, nz = 70, 2
r = 1 # radius of cylinder
phi = np.linspace(0, 360, nphi) / 180.0 * np.pi
z = np.linspace(0, 1.0, nz)
PHI, Z = np.meshgrid(phi, z)
CP = r * np.cos(PHI)
SP = r * np.sin(PHI)
XYZ = np.dstack([CP, SP, Z])
verts = np.stack(
[XYZ[:-1, :-1], XYZ[:-1, 1:], XYZ[1:, 1:], XYZ[1:, :-1]], axis=-2).reshape(-1, 4, 3)
cmap = plt.cm.rainbow
cols = cmap(np.random.random())
poly3 = Poly3DCollection(verts, facecolor=cols, edgecolor="none")
poly3.set_alpha(0.8)
ax.add_collection3d(poly3)
ax.set_xlabel('X')
ax.set_xlim3d(-1, 1)
ax.set_ylabel('Y')
ax.set_ylim3d(-1, 1)
ax.set_zlabel('Z')
ax.set_zlim3d(0, 1)
plt.show()
yields
I have irregularly spaced mesh points data in the form [[xi1,yi1,zi1], [xi2,yi2,zi2],....]. They form a part of a sphere
I also have data [[x1,y1,z1,n1],[x2,y2,z2,n2]....] where (x1,y1,z1) etc tells the coordinate of the midpoint of each mesh bin and ni are the densities at the corresponding locations. The 3d scatter plot with square markers of the data look like this (where the colors show the value of n)
its side view showing the curvature
I am trying to make this into a smooth surface plot. I have looked into this example matplotlib color but here the gridpoints are equally spaced while in my case they are not, also how would one represent the densities using color in such an irregular grid. I am open to trying other packages other than matplotlib.
Thanks
One method is to manually create and plot a collection of triangles:
(Edit: manually creating and coloring triangles around bin midpoints)
import numpy
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.tri import Triangulation
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import matplotlib.cm as cm
# Generate a dataset
R = 1
# bin midpoints
theta = numpy.linspace(numpy.pi/6, numpy.pi/3, 20) + numpy.pi / 2
phi = numpy.linspace(numpy.pi/6, numpy.pi/3, 20)
ttheta, pphi = numpy.meshgrid(theta, phi)
x = R * numpy.sin(ttheta) * numpy.cos(pphi)
y = R * numpy.sin(ttheta) * numpy.sin(pphi)
z = R * numpy.cos(ttheta)
n = numpy.exp(-(ttheta - numpy.pi/4 - numpy.pi/2)**2 * 20 - (pphi - numpy.pi/4)**2 * 20)
mappable = cm.ScalarMappable(cmap=cm.coolwarm, norm=matplotlib.colors.Normalize(vmin=0, vmax=1))
colors = mappable.to_rgba(n)
# Scatter plot
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax.scatter(x.flatten(), y.flatten(), z.flatten(), c=colors.reshape(x.size, 4))
ax.set_xlim(0.2, 0.8)
ax.set_ylim(0.2, 0.8)
ax.set_zlim(-0.9, -0.45)
ax.elev = 50
fig.savefig('t.png')
# Surface plot
# bin vertex spherical coordinates
dtheta = theta[1] - theta[0]
dphi = phi[1] - phi[0]
v_theta = numpy.concatenate([theta - dtheta/2, numpy.array([theta[-1] + dtheta/2])])
v_phi = numpy.concatenate([phi - dphi/2, numpy.array([phi[-1] + dphi/2])])
# bin vertex Cartesian coordinates
v_ttheta, v_pphi = numpy.meshgrid(v_theta, v_phi)
vx = R * numpy.sin(v_ttheta) * numpy.cos(v_pphi)
vy = R * numpy.sin(v_ttheta) * numpy.sin(v_pphi)
vz = R * numpy.cos(v_ttheta)
# Creating triangles and corresponding face colors
triangles = []
facecolors = []
for i in range(v_theta.size - 1):
for j in range(v_phi.size - 1):
triangles.extend([
[(i, j), (i + 1, j), (i, j + 1)],
[(i + 1, j), (i + 1, j + 1), (i, j + 1)]])
facecolors.extend([
colors[i, j],
colors[i, j]
])
triangle_vertices = numpy.array(
[[[vx[i,j], vy[i,j], vz[i,j]] for i, j in t] for t in triangles])
coll = Poly3DCollection(triangle_vertices, facecolors=facecolors, edgecolors=(0,0,0,0))
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax.add_collection(coll)
ax.set_xlim(0.2, 0.8)
ax.set_ylim(0.2, 0.8)
ax.set_zlim(-0.9, -0.45)
ax.elev = 50
fig.savefig('t2.png')
The scatter plot:
The surface plot: