Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

plot 3D connected prism matplotlib based on vertices

I have a set of coordinates x, y, z from 4-6 vectors. I want to plot the corresponding prism. But my lines are crossed and do not look like a prism at all.

I assume I have to sort my dataset, but I am not sure how or if this is the correct answer.

My plot

This is clearly erroneous:

wrong crossed prism plot

Ideal prism

And this how it should look like:

rectangular prism plot

Real prism for a 3 coordinates dataset

3D prism for 3c-dataset

Script

from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection
import numpy as np

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
p = np.array([[-43.11150999, -118.14365791, -1100.99389988],
                [-27.97693445,-124.54828379, -1089.54038197],
                [-55.99892873, -120.42384095, -1084.32576297],
                [-40.75143664, -133.41566716, -1077.33745869],

              [-43.2165748, -34.770722, -1030.85272686],
              [-27.89568594, -43.06953117, -1021.03437003],
              [-56.072327, -44.66085799, -1019.15166512],
              [-40.75143814, -52.95966716, -1009.3333083]])

ax.scatter3D(p[:, 0], p[:, 1], p[:, 2])

x = np.array([[-43.11150999], [-27.97693445], [-55.99892873], [-40.75143664], [-43.2165748],[-27.89568594],[-56.072327],[-40.75143814]])
y = np.array([[-118.14365791], [-124.54828379], [-120.42384095], [-133.41566716], [-34.770722],[-43.06953117],[-44.66085799],[-52.95966716]])
z = np.array([[-1100.99389988], [-1089.54038197], [-1084.32576297], [-1077.33745869], [-1030.85272686],[-1021.03437003],[-1019.15166512],[-1009.3333083]])

labels = ['PT-EP-1n', 'PT-EP-2n', 'PT-EP-3n', 'PT-EP-4n', 'PT-TP-1n','PT-TP-2n','PT-TP-3n','PT-TP-4n']

x = x.flatten()
y = y.flatten()
z = z.flatten()

ax.scatter(x, y, z)
#give the labels to each point
for x, y, z, label in zip(x, y,z, labels):
    ax.text(x, y, z, label)

verts = [[p[0],p[1],p[2],p[3]],
          [p[1],p[2],p[6],p[5]],
          [p[2],p[3],p[7],p[6]],
          [p[3],p[0],p[4],p[7]],
          [p[0],p[1],p[5],p[4]],
          [p[4],p[5],p[6],p[7]]]

collection = Poly3DCollection(verts, linewidths=1, edgecolors='black', alpha=0.2, zsort='min')
face_color = "salmon"
collection.set_facecolor(face_color)
ax.add_collection3d(collection)

plt.show()
like image 689
Trii Avatar asked Dec 17 '18 13:12

Trii


People also ask

How do I plot 3D in Matplotlib?

The 3D plotting in Matplotlib can be done by enabling the utility toolkit. The utility toolkit can be enabled by importing the mplot3d library, which comes with your standard Matplotlib installation via pip.

How to plot polygons with 3-dimensional vertices in Python?

We can also plot polygons with 3-dimensional vertices in Python. magic command %matplotlib notebook at the beginning of the notebook. This enables us to interact with the 3D plots, by zooming in and out of the plot, as well as rotating them in any direction.

What is axes3d in Matplotlib?

The first one is a standard import statement for plotting using matplotlib, which you would see for 2D plotting as well. The second import of the Axes3D class is required for enabling 3D projections. It is, otherwise, not used anywhere else.

What version of Matplotlib has the mplot3d module?

Changed in version 3.2.0: Prior to Matplotlib 3.2.0, it was necessary to explicitly import the mpl_toolkits.mplot3d module to make the '3d' projection to Figure.add_subplot. See the mplot3d FAQ for more information about the mplot3d toolkit.


1 Answers

A very well-asked first question!

I think what you are looking for is the convex hull of your data points, which can be computed using scipy.spatial.ConvexHull. The problem with this approach is, however, that this function returns a set of triangles, which will not correspond to the set of faces of your prism in a 1-to-1 fashion. Rather, multiple co-planar triangles will often form a single face.

In a second step, you will hence need to simplify adjacent, co-planar triangles into a single face. If your vertices come from experimental data, then you might run into trouble in this step as the triangles may not actually be co-planar, and then the naive simplify procedure given below will fail.

Borrowing from @ImportanceOfBeingErnest's answer here (which is much simpler and faster than the accepted answer), you get:

#!/usr/bin/env python
# coding: utf-8

import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d as a3

from scipy.spatial import ConvexHull

class Faces():
    def __init__(self,tri, sig_dig=12, method="convexhull"):
        self.method=method
        self.tri = np.around(np.array(tri), sig_dig)
        self.grpinx = list(range(len(tri)))
        norms = np.around([self.norm(s) for s in self.tri], sig_dig)
        _, self.inv = np.unique(norms,return_inverse=True, axis=0)

    def norm(self,sq):
        cr = np.cross(sq[2]-sq[0],sq[1]-sq[0])
        return np.abs(cr/np.linalg.norm(cr))

    def isneighbor(self, tr1,tr2):
        a = np.concatenate((tr1,tr2), axis=0)
        return len(a) == len(np.unique(a, axis=0))+2

    def order(self, v):
        if len(v) <= 3:
            return v
        v = np.unique(v, axis=0)
        n = self.norm(v[:3])
        y = np.cross(n,v[1]-v[0])
        y = y/np.linalg.norm(y)
        c = np.dot(v, np.c_[v[1]-v[0],y])
        if self.method == "convexhull":
            h = ConvexHull(c)
            return v[h.vertices]
        else:
            mean = np.mean(c,axis=0)
            d = c-mean
            s = np.arctan2(d[:,0], d[:,1])
            return v[np.argsort(s)]

    def simplify(self):
        for i, tri1 in enumerate(self.tri):
            for j,tri2 in enumerate(self.tri):
                if j > i:
                    if self.isneighbor(tri1,tri2) and \
                       self.inv[i]==self.inv[j]:
                        self.grpinx[j] = self.grpinx[i]
        groups = []
        for i in np.unique(self.grpinx):
            u = self.tri[self.grpinx == i]
            u = np.concatenate([d for d in u])
            u = self.order(u)
            groups.append(u)
        return groups


if __name__ == '__main__':

    x = np.array([[-43.11150999], [-27.97693445], [-55.99892873], [-40.75143664], [-43.2165748],[-27.89568594],[-56.072327],[-40.75143814]])
    y = np.array([[-118.14365791], [-124.54828379], [-120.42384095], [-133.41566716], [-34.770722],[-43.06953117],[-44.66085799],[-52.95966716]])
    z = np.array([[-1100.99389988], [-1089.54038197], [-1084.32576297], [-1077.33745869], [-1030.85272686],[-1021.03437003],[-1019.15166512],[-1009.3333083]])

    verts = np.c_[x, y, z]

    # compute the triangles that make up the convex hull of the data points
    hull = ConvexHull(verts)
    triangles = [verts[s] for s in hull.simplices]

    # combine co-planar triangles into a single face
    faces = Faces(triangles, sig_dig=1).simplify()

    # plot
    ax = a3.Axes3D(plt.figure())
    pc = a3.art3d.Poly3DCollection(faces,
                                   facecolor="salmon",
                                   edgecolor="k", alpha=0.9)
    ax.add_collection3d(pc)

    # define view
    ax.set_xlim(np.min(x), np.max(x))
    ax.set_ylim(np.min(y), np.max(y))
    ax.set_zlim(np.min(z), np.max(z))
    ax.dist=10
    ax.azim=30
    ax.elev=10

    plt.show()

enter image description here

like image 164
Paul Brodersen Avatar answered Oct 22 '22 07:10

Paul Brodersen