Solution with matplotlib:
#!/usr/bin/python3
import sys
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import numpy
from numpy.random import randn
from scipy import array, newaxis
# ======
## data:
DATA = array([
[-0.807237702464, 0.904373229492, 111.428744443],
[-0.802470821517, 0.832159465335, 98.572957317],
[-0.801052795982, 0.744231916692, 86.485869328],
[-0.802505546206, 0.642324228721, 75.279804677],
[-0.804158144115, 0.52882485495, 65.112895758],
[-0.806418040943, 0.405733109371, 56.1627277595],
[-0.808515314192, 0.275100227689, 48.508994388],
[-0.809879521648, 0.139140394575, 42.1027499025],
[-0.810645106092, -7.48279012695e-06, 36.8668106345],
[-0.810676720161, -0.139773175337, 32.714580273],
[-0.811308686707, -0.277276065449, 29.5977405865],
[-0.812331692291, -0.40975978382, 27.6210856615],
[-0.816075037319, -0.535615685086, 27.2420699235],
[-0.823691366944, -0.654350489595, 29.1823292975],
[-0.836688691603, -0.765630198427, 34.2275056775],
[-0.854984518665, -0.86845932028, 43.029581434],
[-0.879261949054, -0.961799684483, 55.9594146815],
[-0.740499820944, 0.901631050387, 97.0261463995],
[-0.735011699497, 0.82881933383, 84.971061395],
[-0.733021568161, 0.740454485354, 73.733621269],
[-0.732821755233, 0.638770044767, 63.3815970475],
[-0.733876941678, 0.525818698874, 54.0655910105],
[-0.735055978521, 0.403303715698, 45.90859502],
[-0.736448900325, 0.273425879041, 38.935709456],
[-0.737556181137, 0.13826504904, 33.096106049],
[-0.738278724065, -9.73058423274e-06, 28.359664343],
[-0.738507612286, -0.138781586244, 24.627237837],
[-0.738539663773, -0.275090412979, 21.857410904],
[-0.739099040189, -0.406068448513, 20.1110519655],
[-0.741152200369, -0.529726022182, 19.7019157715],
])
Xs = DATA[:,0]
Ys = DATA[:,1]
Zs = DATA[:,2]
# ======
## plot:
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_trisurf(Xs, Ys, Zs, cmap=cm.jet, linewidth=0)
fig.colorbar(surf)
ax.xaxis.set_major_locator(MaxNLocator(5))
ax.yaxis.set_major_locator(MaxNLocator(6))
ax.zaxis.set_major_locator(MaxNLocator(5))
fig.tight_layout()
plt.show() # or:
# fig.savefig('3D.png')
Result:

Probably not very beautiful. But it will be, if You provide more points.
Answer from Adobe on Stack OverflowSolution with matplotlib:
#!/usr/bin/python3
import sys
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import numpy
from numpy.random import randn
from scipy import array, newaxis
# ======
## data:
DATA = array([
[-0.807237702464, 0.904373229492, 111.428744443],
[-0.802470821517, 0.832159465335, 98.572957317],
[-0.801052795982, 0.744231916692, 86.485869328],
[-0.802505546206, 0.642324228721, 75.279804677],
[-0.804158144115, 0.52882485495, 65.112895758],
[-0.806418040943, 0.405733109371, 56.1627277595],
[-0.808515314192, 0.275100227689, 48.508994388],
[-0.809879521648, 0.139140394575, 42.1027499025],
[-0.810645106092, -7.48279012695e-06, 36.8668106345],
[-0.810676720161, -0.139773175337, 32.714580273],
[-0.811308686707, -0.277276065449, 29.5977405865],
[-0.812331692291, -0.40975978382, 27.6210856615],
[-0.816075037319, -0.535615685086, 27.2420699235],
[-0.823691366944, -0.654350489595, 29.1823292975],
[-0.836688691603, -0.765630198427, 34.2275056775],
[-0.854984518665, -0.86845932028, 43.029581434],
[-0.879261949054, -0.961799684483, 55.9594146815],
[-0.740499820944, 0.901631050387, 97.0261463995],
[-0.735011699497, 0.82881933383, 84.971061395],
[-0.733021568161, 0.740454485354, 73.733621269],
[-0.732821755233, 0.638770044767, 63.3815970475],
[-0.733876941678, 0.525818698874, 54.0655910105],
[-0.735055978521, 0.403303715698, 45.90859502],
[-0.736448900325, 0.273425879041, 38.935709456],
[-0.737556181137, 0.13826504904, 33.096106049],
[-0.738278724065, -9.73058423274e-06, 28.359664343],
[-0.738507612286, -0.138781586244, 24.627237837],
[-0.738539663773, -0.275090412979, 21.857410904],
[-0.739099040189, -0.406068448513, 20.1110519655],
[-0.741152200369, -0.529726022182, 19.7019157715],
])
Xs = DATA[:,0]
Ys = DATA[:,1]
Zs = DATA[:,2]
# ======
## plot:
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_trisurf(Xs, Ys, Zs, cmap=cm.jet, linewidth=0)
fig.colorbar(surf)
ax.xaxis.set_major_locator(MaxNLocator(5))
ax.yaxis.set_major_locator(MaxNLocator(6))
ax.zaxis.set_major_locator(MaxNLocator(5))
fig.tight_layout()
plt.show() # or:
# fig.savefig('3D.png')
Result:

Probably not very beautiful. But it will be, if You provide more points.
Please have a look at Axes3D.plot_surface or at the other Axes3D methods. You can find examples and inspirations here, here, or here.
Edit:
Z-Data that is not on a regular X-Y-grid (equal distances between grid points in one dimension) is not trivial to plot as a triangulated surface. For a given set of irregular (X, Y) coordinates, there are multiple possible triangulations. One triangulation can be calculated via a "nearest neighbor" Delaunay algorithm. This can be done in matplotlib. However, it still is a bit tedious:
http://matplotlib.1069221.n5.nabble.com/Plotting-3D-Irregularly-Triangulated-Surfaces-An-Example-td9652.html
It looks like support will be improved:
http://matplotlib.org/examples/pylab_examples/tripcolor_demo.html http://matplotlib.1069221.n5.nabble.com/Custom-plot-trisurf-triangulations-tt39003.html
With the help of http://docs.enthought.com/mayavi/mayavi/auto/example_surface_from_irregular_data.html I was able to come up with a very simple solution based on mayavi:
import numpy as np
from mayavi import mlab
X = np.array([0, 1, 0, 1, 0.75])
Y = np.array([0, 0, 1, 1, 0.75])
Z = np.array([1, 1, 1, 1, 2])
# Define the points in 3D space
# including color code based on Z coordinate.
pts = mlab.points3d(X, Y, Z, Z)
# Triangulate based on X, Y with Delaunay 2D algorithm.
# Save resulting triangulation.
mesh = mlab.pipeline.delaunay2d(pts)
# Remove the point representation from the plot
pts.remove()
# Draw a surface based on the triangulation
surf = mlab.pipeline.surface(mesh)
# Simple plot.
mlab.xlabel("x")
mlab.ylabel("y")
mlab.zlabel("z")
mlab.show()
This is a very simple example based on 5 points. 4 of them are on z-level 1:
(0, 0) (0, 1) (1, 0) (1, 1)
One of them is on z-level 2:
(0.75, 0.75)
The Delaunay algorithm gets the triangulation right and the surface is drawn as expected:

I ran the above code on Windows after installing Python(x,y) with the command
ipython -wthread script.py
For surfaces it's a bit different than a list of 3-tuples, you should pass in a grid for the domain in 2d arrays.
If all you have is a list of 3d points, rather than some function f(x, y) -> z, then you will have a problem because there are multiple ways to triangulate that 3d point cloud into a surface.
Here's a smooth surface example:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
# Axes3D import has side effects, it enables using projection='3d' in add_subplot
import matplotlib.pyplot as plt
import random
def fun(x, y):
return x**2 + y
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = y = np.arange(-3.0, 3.0, 0.05)
X, Y = np.meshgrid(x, y)
zs = np.array(fun(np.ravel(X), np.ravel(Y)))
Z = zs.reshape(X.shape)
ax.plot_surface(X, Y, Z)
ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')
plt.show()

You can read data direct from some file and plot
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
from sys import argv
x,y,z = np.loadtxt('your_file', unpack=True)
fig = plt.figure()
ax = Axes3D(fig)
surf = ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.1)
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.savefig('teste.pdf')
plt.show()
If necessary you can pass vmin and vmax to define the colorbar range, e.g.
surf = ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.1, vmin=0, vmax=2000)

Bonus Section
I was wondering how to do some interactive plots, in this case with artificial data
from __future__ import print_function
from ipywidgets import interact, interactive, fixed, interact_manual
import ipywidgets as widgets
from IPython.display import Image
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits import mplot3d
def f(x, y):
return np.sin(np.sqrt(x ** 2 + y ** 2))
def plot(i):
fig = plt.figure()
ax = plt.axes(projection='3d')
theta = 2 * np.pi * np.random.random(1000)
r = i * np.random.random(1000)
x = np.ravel(r * np.sin(theta))
y = np.ravel(r * np.cos(theta))
z = f(x, y)
ax.plot_trisurf(x, y, z, cmap='viridis', edgecolor='none')
fig.tight_layout()
interactive_plot = interactive(plot, i=(2, 10))
interactive_plot
It is possible to plot the 3D surface over your scatter plot using the plt.plot_trisurf(...) function as long as you find the right ordering of vertices for the triangles. There is a function from SciPy called ConvexHull which finds the simplices of the points on the outside of the data set. This is very handy, but does not immediately work on this example because your data set is not convex!
The solution is to make the data convex by expanding the points away from the center until they form a sphere. See below for a visualization of this.

After turning the head into a sphere it is now possible to call ConvexHull(...) to get the desired triangulation. This triangulation can be applied to the spherical head first (see below). Then, the head can be shrunk back into its original form, and the triangulation's vertices are still valid!

This is the final product!

Code
import numpy as np
import matplotlib.pyplot as plt
import csv
from scipy.spatial import KDTree
from scipy.spatial import ConvexHull
from matplotlib import cm
from matplotlib import animation
plt.style.use('dark_background')
# Data reader from a .csv file
def getData(file):
lstX = []
lstY = []
lstZ = []
with open(file, newline='\n') as f:
reader = csv.reader(f, quoting=csv.QUOTE_NONNUMERIC)
for row in reader:
lstX.append(row[0])
lstY.append(row[1])
lstZ.append(row[2])
return lstX, lstY, lstZ
# This function gets rid of the triangles at the base of the neck
# It just filters out any triangles which have at least one side longer than toler
def removeBigTriangs(points, inds, toler=35):
newInds = []
for ind in inds:
if ((np.sqrt(np.sum((points[ind[0]]-points[ind[1]])**2, axis=0))<toler) and
(np.sqrt(np.sum((points[ind[0]]-points[ind[2]])**2, axis=0))<toler) and
(np.sqrt(np.sum((points[ind[1]]-points[ind[2]])**2, axis=0))<toler)):
newInds.append(ind)
return np.array(newInds)
# this calculates the location of each point when it is expanded out to the sphere
def calcSpherePts(points, center):
kdtree = KDTree(points) # tree of nearest points
# d is an array of distances, i is array of indices
d, i = kdtree.query(center, points.shape[0])
spherePts = np.zeros(points.shape, dtype=float)
radius = np.amax(d)
for p in range(points.shape[0]):
spherePts[p] = points[i[p]] *radius /d[p]
return spherePts, i # points and the indices for where they were in the original lists
x,y,z = getData(".\coords3Ddetailed.csv")
pts = np.stack((x,y,z), axis=1)
# generating data
spherePts, sphereInd = calcSpherePts(pts, [0,0,0])
hull = ConvexHull(spherePts)
triangInds = hull.simplices # returns the list of indices for each triangle
triangInds = removeBigTriangs(pts[sphereInd], triangInds)
# plotting!
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.scatter3D(pts[:,0], pts[:,1], pts[:,2], s=2, c='r', alpha=1.0)
ax.plot_trisurf(pts[sphereInd,0], pts[sphereInd,1], pts[sphereInd,2], triangles=triangInds, cmap=cm.Blues, alpha=1.0)
plt.show()
A constant radial position for using the convex hull method by trent is excellent and should be included in your solution methods toolkit. The visual explanation in the answer is also excellent. Very useful for creating a closed surface from a dataset dependent on polar and azimuthal positions.
However, if the dataset represents a concave surface, this method may not work. For example a dataset for a hand instead of a head. Even the head example has a small problem with this since the ears fold over, as shown below:

The simple 3-step process of transforming the data to a sphere, creating a convex hull surface, then finally transforming the surface vertices back to the original values is summarized in the code below using the S3Dlib package. The code is basic, not optimizing or removing triangles.
import csv
import numpy as np
import matplotlib.pyplot as plt
import s3dlib.surface as s3d
csv_data = []
with open('data/head.csv') as csv_file:
csv_reader = csv.reader(csv_file, delimiter=' ')
for row in csv_reader: csv_data.append(row)
geom = np.array([ c[1:4] for c in csv_data ]).astype(float).T #.. shape: (3,N)
def toSphere(xyz) :
rtp = s3d.SphericalSurface.coor_convert(xyz,False)
rtp[0] = np.ones(len(rtp[0]))
return s3d.SphericalSurface.coor_convert(rtp,True)
surface = s3d.Surface3DCollection.chull( toSphere(geom).T, color='w' )
surface.map_geom_from_op(lambda rtp: geom)
fig = plt.figure(figsize=plt.figaspect(1))
ax = plt.axes(projection='3d')
s3d.auto_scale(ax,surface,rscale=0.55).add_collection3d(surface.shade())
ax.set_axis_off()
ax.view_init(20,-105)
fig.tight_layout(pad=0)
plt.show()
Also, when visualizing physical surfaces, set the Matplotlib axes limits to the same scale for a more realistic view. The following shows the comparison:

