Connecting points to a central point on 3D scatter Python - python

I'm making a molecular diagram of a tetrahedral molecule where the three outer points(or atoms) need to be connected by lines to the central point.
From How can I connect points on a 3D scatter plot? I was able to connect the dots, but it produces the incorrect lines.
Here is my code:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = [1, 2, 1.2, 1.5, 1.5]
y = [1, 1.2, 2, 1.5, 1.5]
z = [.5, .5, .5, 1.2, 2]
a = []
b = []
c = []
for item in x:
a.append(float(item))
for item in y:
b.append(float(item))
for item in z:
c.append(float(item))
r = np.array(a)
s = np.array(b)
t = np.array(c)
ax.set_xlabel("x axis")
ax.set_ylabel("y axis")
ax.set_zlabel("z axis")
ax.scatter(r,s,zs = t, s=200)
ax.plot3D(r,s,z)
plt.show()
I'd like all the points to connect to the central point(x=1.5, y=1.5, z=1.2). Here is what this looks like so far:

If you do ax.plot3D(r,s,z) you are plotting a line joining the 5 points one after another. What you need is to plot a line from each point to the point you want.
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = [1, 2, 1.2, 1.5, 1.5]
y = [1, 1.2, 2, 1.5, 1.5]
z = [.5, .5, .5, 1.2, 2]
# Change the way you create the array.
# You can do it in several ways.
r = np.array(x, dtype=np.float)
s = np.array([float(i) for i in y])
t = np.array(z) * 1.0
ax.scatter(r,s,zs = t, s=200, label='True Position')
# Iterate over each point, and plot the line.
for x, y, z in zip(r, s, t):
ax.plot3D([x, 1.5], [y, 1.5], [z, 1.2], 'b')
plt.show()

Related

Remove plot outer axises, without removing the subplot axis

I'm trying to have the following plot to appear like the second plot.
Without the axises (vertical, horizontal) that have no meaning for this plot and range from 0 to 1.
This is the code I'm using to generate to plot:
import matplotlib.pyplot as plt
import numpy as np
x_lim = (0, 1)
y_lim = (0, 1)
z_lim = (0, 1)
list_points = [[0.3, 0.3, 0], [0.4, 0.4, 0], [0, 0, 0], [.1, .1, .5], [0.3, 0.3, .2]]
def plot_tracking_map():
"""
Visualize all grapes centers on a 3d map.
This function generates a plot that represents the TB in 3D.
"""
x_cors, y_cors, z_cors = [], [], []
for i in range(len(list_points)):
x_cor, y_cor, z_cor = list_points[i][0], list_points[i][1], list_points[i][2]
x_cors.append(x_cor)
y_cors.append(y_cor)
z_cors.append(z_cor)
fig, ax = plt.subplots(figsize=(12, 12))
ax = fig.add_subplot(projection='3d')
yy, zz = np.meshgrid(range(2), range(2))
xx = yy
s = ax.scatter(x_cors, y_cors, z_cors, s=400, marker='o') # x,y,z coordinates, size of each point, colors.
# controls the alpha channel. all points have the same value, ignoring their distance
s.set_edgecolors = s.set_facecolors = lambda *args: None
ax.title.set_text(f'Imgae number 1')
plt.show()
plot_tracking_map()
edit
I changed the lines
fig, ax = plt.subplots(figsize=(12, 12))
ax = fig.add_subplot(projection='3d')
to
fig, ax = plt.subplots(figsize=(12, 12), subplot_kw={'projection': '3d'})
And it solved the problem.

How to add a single marker in a bar graph

I have the following code generating a bar graph. However, for the last bar, I need a star marker to show that there is no data for the last bar, here in the graph it's number 10.
data = pd.read_csv('data.csv')
df = pd.DataFrame(data)
plt.figure(figsize=(3,2))
X = list(df.iloc[:, 0])
Y = list(df.iloc[:, 1])
Z= list(df.iloc[:, 2])
X_axis = np.arange(len(X))
plt.bar(X_axis - 0.2, Y, 0.4, label='Actual',color='#436bad')
plt.bar(X_axis + 0.2, Z, 0.4, label='Predicted',color='#c5c9c7')
plt.legend(loc=2, prop={'size': 6.5})
labels=['1','2','3','4','5','6','7','8','9','10']
plt.xticks(X,labels,rotation=60)
plt.xlabel("Node no")
plt.ylabel("Accuracy (%)")
plt.ylim(60,95)
You can use plt.text and set * where do you want, like below:
(Because I can't run your code. I send an example)
import matplotlib.pyplot as plt
import numpy as np
x = np.array([1,3,7])
y = [2, 3, 2]
z = [1, 2, 3]
plt.bar(x-0.1, y, width=0.2, color='b', align='center')
plt.bar(x+0.1, z, width=0.2, color='g', align='center')
labels=[1,2,3,4,5,6,7,8,9,10]
plt.xticks(range(1,11),labels,rotation=60)
str_x = [l for l in labels if not l in x]
for s_x in str_x:
plt.text(s_x, 0.1, '*', ha='center', fontsize=26)
Output:

How do I smooth out the edges of a closed line similar to d3's curveCardinal method implementation?

I have a few data points that I am connecting using a closed line plot, and I want the line to have smooth edges similar to how the curveCardinal methods in d3 do it. Link Here
Here's a minimal example of what I want to do:
import numpy as np
from matplotlib import pyplot as plt
x = np.array([0.5, 0.13, 0.4, 0.5, 0.6, 0.7, 0.5])
y = np.array([1.0, 0.7, 0.5, 0.2, 0.4, 0.6, 1.0])
fig, ax = plt.subplots()
ax.plot(x, y)
ax.scatter(x, y)
Now, I'd like to smooth out/interpolate the line similar to d3's curveCardinal methods. Here are a few things that I've tried.
from scipy import interpolate
tck, u = interpolate.splprep([x, y], s=0, per=True)
xi, yi = interpolate.splev(np.linspace(0, 1, 100), tck)
fig, ax = plt.subplots(1, 1)
ax.plot(xi, yi, '-b')
ax.plot(x, y, 'k')
ax.scatter(x[:2], y[:2], s=200)
ax.scatter(x, y)
The result of the above code is not bad, but I was hoping that the curve would stay closer to the line when the data points are far apart (I increased the size of two such data points above to highlight this). Essentially, have the curve stay close to the line.
Using interp1d (has the same problem as the code above):
from scipy.interpolate import interp1d
x = [0.5, 0.13, 0.4, 0.5, 0.6, 0.7, 0.5]
y = [1.0, 0.7, 0.5, 0.2, 0.4, 0.6, 1.0]
orig_len = len(x)
x = x[-3:-1] + x + x[1:3]
y = y[-3:-1] + y + y[1:3]
t = np.arange(len(x))
ti = np.linspace(2, orig_len + 1, 10 * orig_len)
kind='cubic'
xi = interp1d(t, x, kind=kind)(ti)
yi = interp1d(t, y, kind=kind)(ti)
fig, ax = plt.subplots()
ax.plot(xi, yi, 'g')
ax.plot(x, y, 'k')
ax.scatter(x, y)
I also looked at the Chaikins Corner Cutting algorithm, but I don't like the result.
def chaikins_corner_cutting(coords, refinements=5):
coords = np.array(coords)
for _ in range(refinements):
L = coords.repeat(2, axis=0)
R = np.empty_like(L)
R[0] = L[0]
R[2::2] = L[1:-1:2]
R[1:-1:2] = L[2::2]
R[-1] = L[-1]
coords = L * 0.75 + R * 0.25
return coords
fig, ax = plt.subplots()
ax.plot(x, y, 'k', linewidth=1)
ax.plot(chaikins_corner_cutting(x, 4), chaikins_corner_cutting(y, 4))
I also, superficially, looked at Bezier curves, matplotlibs PathPatch, and Fancy box implementations, but I couldn't get any satisfactory results.
Suggestions are greatly appreciated.
So, here's how I ended up doing it. I decided to introduce new points between every two existing data points. The following image shows how I am adding these new points. Red are data that I have. Using a convex hull I calculate the geometric center of the data points and draw lines to it from each point (shown with blue lines). Divide these lines twice in half and connect the resulting points (green line). The center of the green line is the new point added.
Here are the functions that accomplish this:
def midpoint(p1, p2, sf=1):
"""Calculate the midpoint, with an optional
scaling-factor (sf)"""
xm = ((p1[0]+p2[0])/2) * sf
ym = ((p1[1]+p2[1])/2) * sf
return (xm, ym)
def star_curv(old_x, old_y):
""" Interpolates every point by a star-shaped curve. It does so by adding
"fake" data points in-between every two data points, and pushes these "fake"
points towards the center of the graph (roughly 1/4 of the way).
"""
try:
points = np.array([old_x, old_y]).reshape(7, 2)
hull = ConvexHull(points)
x_mid = np.mean(hull.points[hull.vertices,0])
y_mid = np.mean(hull.points[hull.vertices,1])
except:
x_mid = 0.5
y_mid = 0.5
c=1
x, y = [], []
for i, j in zip(old_x, old_y):
x.append(i)
y.append(j)
try:
xm_i, ym_i = midpoint((i, j),
midpoint((i, j), (x_mid, y_mid)))
xm_j, ym_j = midpoint((old_x[c], old_y[c]),
midpoint((old_x[c], old_y[c]), (x_mid, y_mid)))
xm, ym = midpoint((xm_i, ym_i), (xm_j, ym_j))
x.append(xm)
y.append(ym)
c += 1
except IndexError:
break
orig_len = len(x)
x = x[-3:-1] + x + x[1:3]
y = y[-3:-1] + y + y[1:3]
t = np.arange(len(x))
ti = np.linspace(2, orig_len + 1, 10 * orig_len)
kind='quadratic'
xi = interp1d(t, x, kind=kind)(ti)
yi = interp1d(t, y, kind=kind)(ti)
return xi, yi
Here's how it looks:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
from scipy.spatial import ConvexHull
x = [0.5, 0.13, 0.4, 0.5, 0.6, 0.7, 0.5]
y = [1.0, 0.7, 0.5, 0.2, 0.4, 0.6, 1.0]
xi, yi = star_curv(x, y)
fig, ax = plt.subplots()
ax.plot(xi, yi, 'g')
ax.plot(x, y, 'k', alpha=0.5)
ax.scatter(x, y, color='r')
The result is especially noticeable when the data points are more symmetric, for example the following x, y values give the results in the image below:
x = [0.5, 0.32, 0.34, 0.5, 0.66, 0.65, 0.5]
y = [0.71, 0.6, 0.41, 0.3, 0.41, 0.59, 0.71]
Comparison between the interpolation presented here, with the default interp1d interpolation.
I would create another array with the vertices extended in/out or up/down by about 5%. So if a point is lower than the average of the neighbouring points, make it a bit lower still.
Then do a linear interpolation between the new points, say 10 points/edge. Finally do a spline between the second last point per edge and the actual vertex. If you use Bezier curves, you can make the spline come in at the same angle on each side.
It's a bit of work, but of course you can use this anywhere.

How could I get the desired matplotlib 3d plot style?

I copied a snippet from here and run it but didn't get the desired style.
Code for reproduction
#!/usr/bin/evn python
import numpy as np
import scipy.linalg
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# some 3-dim points
mean = np.array([0.0, 0.0, 0.0])
cov = np.array([[1.0, -0.5, 0.8], [-0.5, 1.1, 0.0], [0.8, 0.0, 1.0]])
data = np.random.multivariate_normal(mean, cov, 50)
# regular grid covering the domain of the data
X, Y = np.meshgrid(np.arange(-3.0, 3.0, 0.5), np.arange(-3.0, 3.0, 0.5))
XX = X.flatten()
YY = Y.flatten()
order = 1 # 1: linear, 2: quadratic
if order == 1:
# best-fit linear plane
A = np.c_[data[:, 0], data[:, 1], np.ones(data.shape[0])]
C, _, _, _ = scipy.linalg.lstsq(A, data[:, 2]) # coefficients
# evaluate it on grid
Z = C[0] * X + C[1] * Y + C[2]
# or expressed using matrix/vector product
#Z = np.dot(np.c_[XX, YY, np.ones(XX.shape)], C).reshape(X.shape)
elif order == 2:
# best-fit quadratic curve
A = np.c_[np.ones(data.shape[0]), data[:, :2],
np.prod(data[:, :2], axis=1), data[:, :2]**2]
C, _, _, _ = scipy.linalg.lstsq(A, data[:, 2])
# evaluate it on a grid
Z = np.dot(np.c_[np.ones(XX.shape), XX, YY, XX * YY, XX**2, YY**2],
C).reshape(X.shape)
# plot points and fitted surface
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, alpha=0.2)
ax.scatter(data[:, 0], data[:, 1], data[:, 2], c='r', s=50)
plt.xlabel('X')
plt.ylabel('Y')
ax.set_zlabel('Z')
ax.axis('equal')
ax.axis('tight')
plt.show()
Actual outcome
see this link
Expected outcome
see this link
The two styles are very different: the grid color, the wireframe, the surface color, etc. Is the style of this image from previous version of matplotlib? If so, how could I get that style?
Matplotlib version
Operating system: Linux Mint 18.3
Matplotlib version: 2.2.2
Matplotlib backend: Qt4Agg
Python version: 2.7.12
I installed matplotlib via pip in a virtual environment.
in my Python 3.5, matplotlib 2.2.2 installation plt.style.use('classic') seems to work
Why matplotlib graphs and icons look different on two computers with the same OS? is similar but the Q was about icons

Get best linear function which approximate some dots in 3D

I have 4 dots which are represented with these coordinates:
X = [0.1, 0.5, 0.9, 0.18]
Y = [0.7, 0.5, 0.7, 0.3]
Z = [4.2, 3.3, 4.2, 2.5]
and I have to get the best linear function (plane) which approximate these 4 dots.
I'm aware of numpy.polyfit, but polyfitworks only with x and y (2D),
What can I do?
while not completely general, if the the data points can be reasonably represented as a surface relative to a coordinate plane, say z = ax + by + c then np.linalg.lstsq can be used
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
X = np.array([0.1, 0.5, 0.9, 0.18])
Y = np.array([0.7, 0.5, 0.7, 0.3])
Z = np.array([4.2, 3.3, 4.2, 2.5])
# least squares fit
A = np.vstack([X, Y, np.ones(len(X))]).T
a,b,c= np.linalg.lstsq(A, Z)[0]
# plots
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# plot data as big red crosses
ax.scatter(X, Y, Z, color='r', marker='+', linewidth=10)
# plot plane fit as grid of green dots
xs = np.linspace(min(X), max(X), 10)
ys = np.linspace(min(Y), max(Y), 10)
xv, yv = np.meshgrid(xs, ys)
zv = a*xv + b*yv + c
ax.scatter(xv, yv, zv, color = 'g')
# ax.plot_wireframe(xv, yv, zv, color = 'g') # alternative fit plane plot
plt.show()
plotting the data 1st, you could select a different coordinate pair for the "independent variable" plane to avoid ill conditioned result if necessary, if the data points appeared to lie in a plane containing the z axis, then use xz or yz
and of course you could have degenerate points on a line or the vertices of a regular tetrahedron
for a better "geometric fit" the 1st fitted plane could be used as the base for a 2nd least square fit of the data rotated into that coordinate system (if the data is "reasonably" plane like)

Categories