8

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()
Community
  • 1
  • 1
Trii
  • 81
  • 4
  • Obviously you mixed up some coordinates in the vertices. You should draw them all on paper and see how you should connect them – user8408080 Dec 17 '18 at 13:31
  • Or just plot the points and plot the index numbers next to them – user8408080 Dec 17 '18 at 13:39
  • 1
    yes, but my problem is with the coordinates not with the vertices. I know that the vertices are good because I already draw them on paper, and this is how should look like (second picture). My problem is how to sort the coordinates for the vertices takes in the correct way. Also, I have hundreds of pairs of coordinates and I don't want to do it manually for each case. – Trii Dec 17 '18 at 13:42
  • For the prism type with parallel planes which you want, you can only have two distinct values of x, two distinct values of y and two distinct values of z. Only then you can have a parallelepiped (3D parallelogram). Look at your `p` array. All your 8 values of x, y and z are different – Sheldore Dec 17 '18 at 13:44
  • Thank you both for your answers, but my ideal prism should be like the second picture but not an ideal parallelepiped. I want to make a prism with my data but, obviously, my data are not parallel so, in the end, this wouldn't be a perfect parallelogram but more or less, it would have the same structure. I know that in the way that I order my data will be like that, but I don't know in which order I need to sort my data – Trii Dec 17 '18 at 13:49
  • Of course `-43.2165748` is slightly different than `-43.11150999` but the point here is that out of 8 x-values 4 of them are completely different. So no way you can have anything close to what you want. Just think about it. After all you should have only 2 distinct x-values – Sheldore Dec 17 '18 at 14:12
  • Aaah now I get it, sorry for my dumb comment. Where does the data come from? Do you how it was calculated and are you sure, that you took the right parts of the data to plot the prism? (I thought it was okay, that it's not vertical, but you just mixed up the vertices for the faces) – user8408080 Dec 17 '18 at 14:21
  • ok, forget the 2nd image. I've added it just to get an idea, maybe my question was wrongly formulated. I know that my final result will be similar to a quadrangular prism, but not exactly the same since my data is not equal for values of x, y, and z. To give you an idea, I can attach an image of how it'd look in a case of 3 coordinates. With 4 coordinates it'd look the same, not perfect, but approximate to a quadrangular prism. – Trii Dec 17 '18 at 14:28
  • The problem is I am not taking the input data in the correct order. For 3 coordinates the order doesn't matter, but for 4, it does. That's my problem, the order in which I take the coordinates. I have the same problem for sets of 5 or + coordinates, so I look for a solution to sort the input data. I hope that I have now clarified it. Thank you – Trii Dec 17 '18 at 14:28
  • Okay, so to summarize: 1) you have a dataset of vertices (could be 6,8,10,..) 2) you need to find groups of vertices, that span a face, which lies on the surface of a prism made by these vertices. Correct? – user8408080 Dec 17 '18 at 14:53
  • exactly, but also can be 5,7, 9... I'm taking the data from an external excel and looks like that and I want to represent them in a 3d prism and I know, if the data are on correct order, the prism looks nice, not perfect one, but nice, but if the data aren't I have a mass like in the first picture – Trii Dec 17 '18 at 14:56

1 Answers1

4

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

Paul Brodersen
  • 11,221
  • 21
  • 38
  • Thank you for the answer but this is not the answer that I'm looking for. Plotting the faces is ok as long as the algorithm takes well the data. So for example, I know if a take the verts in the correct order will be perfect, but the correct order is what I'm looking for. How I should know in 3D what is the correct order to plot a good combination of coordinates... probably the first coordinate that I introduce is not the first one, it corresponds to the second one... or the third one. This is my question. – Trii Dec 17 '18 at 17:09
  • 1
    To compute the "order" of points, you have to compute the convex hull first. That's the criterion you (implicitly) use to define a "natural" order. The convex hull is only uniquely defined by triangles. So that part of the problem will always result in some collection of triangles. – Paul Brodersen Dec 17 '18 at 17:41
  • 1
    Once you have a collection of triangles, you probably want to simplify them to single faces where possible, hence my approach. Once you have faces, you can think about how to map those to an ordering of points, but if your end goal is visualisation, I don't see the point (unless visualisation is not the goal, in which case you should maybe amend your question). – Paul Brodersen Dec 17 '18 at 17:41