Line Filled Surfaces

The ColorLine3DCollection lines may be used to construct a variety of surface objects by utilizing the line as bounding edges of a surface. The methods below describe the various methods of setting the geometry and coloring of the surface. Any surface constructed using the methods may be further operated on using the Surface3DCollection methods.

Line Projections to a Cartesian Axis Plane

Any ColorLine3DCollection object may be projected to a Cartesian planar surface, cylindrical surface or spherical surface to create a surface object using the line object method:

surface = line.get_filled_surface(**kwargs)

where the keyword arguments are:

key assignment values control default
dist number projection surface from origin 0.0
direction 3 element list or tuple projection surface orientation dependent on coor
coor integer or string projection surface type 0
lrez integer >= 0 projection subdivisions 0
color Matplotlib format color surface color line color
alpha 0 <= number <= 1.0 surface opacity 1.0
name string identifier None

The coor argument is an integer or string that specifies the surface to which the line is projected, as indicated in the table below:

Projection Surface coor value direction (default) projection To
planar (default) 0, ‘p’, ‘P’, ‘planar’,’xyz’ [ 0, 0, 1 ] plane
cylindrical 1, ‘c’, ‘C’, ‘cylinder’, ‘cylindrical’ [ 0, 0, 1 ] vertical cylinder
spherical 2, ‘s’, ‘S’, ‘sphere’, ‘spherical’ not used sphere at origin
y-z plane 3, ‘x’, ‘X’ [ 1, 0, 0 ] constant x plane
x-z plane 4, ‘y’, ‘Y’ [ 0, 1, 0 ] constant y plane
x-y plane 5, ‘z’, ‘Z’ [ 0, 0, 1 ] constant z plane

The dist argument is the distance from the origin to the plane in the direction of the plane normal. The default vaule is 0.0, or at the origin. For cylindrical and spherical projections, the dist is the radius of the projection surfaces.

The direction argument is the planar surface normal, whereas for cylindrical surface projections, the direction is the axial direction of the cylindrical surface.

Unlike clipping, the surface which the line is being projected to does not need to contain the line. See for example Spherical Edge Filled Surface.

The following shows various projected line surface constructed from a simple 3D line normal to the x, y and z planes.

../../_images/filled_surface_0.png

For ‘c’ or ‘s’, the dist is the radial distance of the surface from the origin. The following shows a line projection for planar, cylindrical and spherical projections using the indicated values for direction and dist.

../../_images/filled_sph_cyl.png

Surface faces are 2D trapezoids with parallel sides along the projection direction. The surface face lengths are set by the distance from the segment vertices to the projection plane. Hence, the segment lengths of the line set the face widths. Note that for ParametricLine objects, the rez argument in the constructor sets the segment sizes. For the above examples, a rez of 3 was used for the ParametricLine object.

Surface face normals are dependent on the segment order and projection direction to the plane. The surface method evert can be used to change the resulting surface normals without requiring changes to the projection line.

Line projections are demonstrated in the Filled ParametricLine Surface 2 example.

Surface Color from Line Color

By default, face color of surfaces takes the color of the projected line. This is shown in the following example where the line is color mapped using the default colormap along the X or Z direction.

../../_images/filled_surface_1.png

The line segment color will be used for the color of the adjacent trapezoid surface face. This includes the line color resulting from using the shade and fade line color methods. In this example, the lines are projected along the z direction and as a result the surface is uniformly colored along the z direction.

Surface Color Mapping

Once a surface is constructed from projecting a line, any of the surface color mapping methods may be applied. For the following example, the surface is color mapped using the default colormap along the X or Z direction.

../../_images/filled_surface_2.png

For the line colors, color mapping uses the relative position of the segment centers. For surface colors, color mapping uses the relative position of the face centers. These surface plots are identical to the above line-colored plots since the relative position of the face centers have the same relative position as that for the line segment centers. Even the Z directions maps are identical since only one face extends along the Z surface direction.

Surface Subdivision for 3D Color Rendering, lrez

To provide color variations along the surface in the direction of the line projection, the single trapezoidal surface faces must be subdivided along the projection direction. Subdivisions are specified by the additional lrez parameter in the surface construction method.

The lrez parameter is an integer which specifies the number of recursive face divisions along the projection direction. The following figure shows the color distributions for various lrez values using colormapping in the Z direction.

../../_images/filled_surface_3.png

The surface with lrez=1 exemplifies the effect of face center positions identifying the coloring values for cmap surfaces. For this case, the face centers of the lower faces are lower than most of the upper faces of the bisected faces. Only until subdividing each into 8 faces, lrez=3, does the visual representation appear as smooth bands across the surface.

The previous example uses a ParametricLine object. For lines constructed directly from segments, the uniform color along each segment may be more visually pronounced, as shown below for a three-segment line object.

../../_images/filled_surface_3a.png

Again, as a result of face center position controlling color, there is a marked color transition between surface planes. This visual color transition is even apparent for higher face subdivisions shown for a lrez=6.

To smooth the transition, each line segment must be subdivided prior to creating a projected surface. For ParametricLine objects, increasing the number of subdivisions is made by increasing the rez argument in the constructor, i.e.

line = ParametricLine(rez,.....)

Further subdivisions of any line object can be made using the line shred method as:

line.shred(rez)

Using the shred method may be the only option to smooth the surface for lines other than surfaces projected from ParametricLine objects. In this case, line shred is analogous to a line rez. This is demonstrated in the following example using the previous two example surfaces.

../../_images/filled_surface_3b.png

Surface Rendering Control

Various surface Matplotlib renderings may develop anomalous visualizations due to surface face center locations relative to the view orientation. For example, consider a surface placed behind a surface relative to the view. As shown below, an incorrect render may occur.

../../_images/filled_surface_4a.png

For the left figure in this case, several of the rear surface face centers are relatively closer in the view than the front surface face centers. To eliminate this anomaly, the number of surface faces must be increased in the direction of the projection. This is accomplished by increasing the value for the lrez argument used to create the projected surface. This result is exemplified below by emphasizing the surfaces edges.

../../_images/filled_surface_4b.png

In other cases when flat surface planes intersect, the number of surface faces along the segments must be increased. This is exemplified below for two projected surfaces which intersect due to the line geometries.

../../_images/filled_surface_5a.png

Subdivisions along the line segments using the shred line method may correct this anomaly. Changes in the view orientation may provide a further correction. Finally, further adjustments may be needed for the surface using the lrez surface construction parameter.

../../_images/filled_surface_5b.png

In summary, surface grid constructed from projecting lines should be sufficiently detailed to provide the required distribution of surface color and to allow visually correct rendering of overlapping surface faces. These are controlled by:

parameter method subdivisions along
rez ParametricLine constructor line
rez shred line
lrez get_filled_surface projected surface

Note

These intersecting surface face anomalies will depend on the view orientation, which is set by the axis elev and azim values. These values may be changed from the default values using the view_init axis method.

Increasing the number of surface faces will, in general, eliminate this type of anomaly. However, for rendering multiple surfaces, the surfaces must be added together to create one composite surface where the Z-order is set for all faces of a single surface.

Line Projections to a Line

Surfaces may be constructed from projections between two ColorLine3DCollection objects connecting the line vertices. For example, using ParametricLine lines:

lineA = ParametricLine(REZ,....)
lineB = ParametricLine(REZ,....)
surface = lineA.get_surface_to_line(lineB,lrez)

The two lines must have the same number of segments, i.e., the same REZ or number of segment indices and, if used, the same shred method argument value. Surface face vertices are constructed in the same sequential vertex order of the two lines. Surface color will be taken from the segment coloring of object which calls the method, in this case lineA.

../../_images/filled_surface_6.png

Line to line projection surfaces should be subdivided into smaller faces prior to rendering the surface using the shred or setting the lrez parameter during surface construction.

Warning

Depending on the lines being projected, quadrilateral faces may be created which are not planar. These warped faces may produce anomalies which may ‘possibly’ be eliminated by triangulating the surface or using the rez or lrez parmeters.

This method of surface construction is particularly useful for ruled surface geometries. The Hyperboloid of Revolution and Mobius From Lines are two examples of using this surface construction method.

Source Code

Line-Plane Surface

The following is the script for generating the surface from projections of a line to a plane, cylinder or sphere.

import numpy as np
from matplotlib import pyplot as plt
import s3dlib.surface as s3d
import copy

# 1. Define functions to examine .....................................

def parametric_curve(t) :
    x = t
    y = 0.73*t**6 -1.5*t**2 + 1.5*t + .25  
    z = -0.2*t + .3*np.sin(1.5*np.pi*t)  + .7
    return x,y,z

def deflate(rtp) :
    r,t,p = rtp
    scale = 0.2
    Rz = np.cos(p)
    Rxys = (1-scale)*np.sin(p) + scale*np.cos(4*t)
    R = np.sqrt( Rz**2 + Rxys**2)
    return R,t,p

def cont_plane(xyz,direction, dist ) :
    x,y,z = xyz
    u,v,w = np.divide( direction, np.linalg.norm(direction) )
    Z = ( dist - u*x - v*y ) / w
    return x,y,Z

def z_2elev_azim(vector) :
    x,y,z = vector
    R = np.sqrt( x*x + y*y + z*z )
    phi = np.nan_to_num(np.arccos(z/R))
    theta = np.arctan2(y,x)
    theta = np.where(theta<0,theta+2*np.pi,theta)
    phi *= 180/np.pi
    theta *= 180/np.pi
    return theta,phi

# Figure A : Four Surfaces from Filled line planar projections ===========================
rez=3

line = s3d.ParametricLine(rez,parametric_curve, color='firebrick',lw=4)
lines = [ line, copy.copy(line), copy.copy(line), copy.copy(line) ]
lines[1].transform(translate=[0,0,-.01])      # improve visibility
lines[3].transform(translate=[0,0.01,0.01])   # improve visibility

names = [ 'Z filled surface\n(default)','Z filled surface\ndist =1',
          'X filled surface','Y filled surface']
coors = [ 'z','z','x','y']

fig = plt.figure(figsize=(6,6))
fig.text(0.95,0.01,'Figure A', ha='right', fontsize='x-small')
minmax=(0,1)
for i in range(4) :
    ax = fig.add_subplot(221+i, projection='3d')
    ax.set(xlim=minmax, ylim=minmax, zlim=minmax )
    ax.set_title(names[i])
    ax.set(
        xlabel='X', ylabel='Y', zlabel='Z'
        ,xticks=[0,0.5,1], yticks=[0,0.5,1], zticks=[0,0.5,1]
    )
    ax.set_proj_type('ortho')
    pdist = 1 if i==1 else None
    surface= line.get_filled_surface(dist=pdist,coor=coors[i], color='khaki')
    if i==1 or i==2 : surface.evert()              # improve shading
    if i==3 : surface.shade(direction=[1,0.5,0])   # improve shading
    else: surface.shade()
    ax.add_collection3d(surface)
    ax.add_collection3d(lines[i])
fig.tight_layout(pad=2.5)

# Figure B : Three filled line projections ===============================================
rez = 5
pdirection, pdist = [  0.5,  0.5,  1.0 ] ,  0.2
cdirection, cdist = [  -1,  -1.0,  1.0 ] ,  .35
sdist = 0.75

surface = s3d.SphericalSurface(rez,basetype='octa',color='khaki')
surface.map_geom_from_op(deflate)
surface.transform(s3d.eulerRot(-100,35,25,useXconv=False),scale=1.35)
surface.set_surface_alpha(0.35,constant=True)

cont_direction,cont_dist = [.5,0,1], 1.1
line = surface.contourLines(cont_dist,direction=cont_direction,coor='p',color='firebrick')

lines = [ line, copy.copy(line), copy.copy(line), copy.copy(line) ]
dists =      [ None,pdist,     cdist,     sdist ]
directions =  [None,pdirection,cdirection,None ]

pplane = s3d.PlanarSurface(rez,color=[0,0.5,0.5,0.15],lw=0)
p_plane = lambda c : cont_plane(c, pdirection, pdist )
pplane.map_geom_from_op(p_plane).shade()
cplane = s3d.CylindricalSurface(rez,color=[0,0.5,0.5,0.15],lw=0 )
cplane.transform(scale=[cdist,cdist,1.4])
th,ph = z_2elev_azim(cdirection)
eRot = s3d.eulerRot(th,ph,useXconv=False)
cplane.transform( eRot ).shade()
splane = s3d.SphericalSurface(rez,color=[0,0.5,0.5,0.15],lw=0)
splane.transform(scale=.75).shade()

projPlanes = [ None, pplane, cplane, splane]

title = ['example line constructed\nfrom a surface contour',
         'Planar Line Projection\n'+str(pdirection)+'  '+str(pdist),
         'Cylindrical Line Projection\n'+str(cdirection)+'  '+str(cdist),
         'Spherical Line Projection\n'+str(sdist)]

fig = plt.figure(figsize=(6,6))
fig.text(0.95,0.01,'Figure B', ha='right', fontsize='x-small')
minmax, ticks =(-1,1) , [-1,0,1]
for i in range(4) :
    ax = fig.add_subplot(221+i, projection='3d', aspect='equal')
    ax.set(xlim=minmax, ylim=minmax, zlim=minmax )
    ax.set_title(title[i], fontsize='small')
    ax.set(
        xlabel='X', ylabel='Y', zlabel='Z'
        ,xticks=ticks, yticks=ticks, zticks=ticks)
    ax.set_proj_type('ortho')
    ax.view_init(30,-11)
    if i == 0 :
        ax.add_collection3d(surface.shade())
    else :
        pline = copy.copy(lines[i]).map_to_plane(dists[i], coor=i-1, direction=directions[i])
        pline.set_color('b')
        fill_surf = line.get_filled_surface(dist=dists[i], coor=i-1, direction=directions[i])
        fill_surf.set_color('khaki')
        ax.add_collection3d(pline)
        ax.add_collection3d(fill_surf.shade(0.43))
        ax.add_collection3d(projPlanes[i])
    ax.add_collection3d(lines[i])
fig.tight_layout(pad=3)

# =======================================================================================

plt.show()

Colormapping Line-Plane Surface

The following is the script for colormapping the surface from projections of a line.

import numpy as np
from matplotlib import pyplot as plt
import s3dlib.surface as s3d

#.. Filled surface 2 : Line controls geometry & surface color.

# 1. Define function to examine .....................................

def parametric_curve(t) :
    x = t
    y = 0.73*t**6 -1.5*t**2 + 1.5*t + .25  
    z = -0.2*t + .3*np.sin(1.5*np.pi*t)  + .7
    return x,y,z

rez, minmax, ticks = 3, (0,1), [0,0.5,1]
v = [ [0.0,0.25,0.75], [0.3,0.6,0.9], [0.75,0.7,0.35], [1.0,1.0,0.2]  ]

# Figure 1 : Color from Line ==================================================
line = [None]*2

fig = plt.figure(figsize=(6,6))
fig.text(0.98,0.01,'Figure 1', ha='right', fontsize='x-small')
for i in range(4) :
    ax = fig.add_subplot(221+i, projection='3d')
    ax.set(xlim=minmax, ylim=minmax, zlim=minmax )
    ax.set( xlabel='X', ylabel='Y', zlabel='Z',
            xticks=ticks, yticks=ticks, zticks=ticks )
    ax.set_proj_type('ortho')
    prefix = 'X' if i%2 == 0 else 'Z'
    if i<2 :
        line[i] = s3d.ParametricLine(rez,parametric_curve, lw=5)
        line[i].map_cmap_from_op(lambda xyz: xyz[i*2])
        axobj = line[i]
        suffix = ' cmap line'
    else :
        axobj = line[i-2].get_filled_surface()
        suffix = ' cmap line to surface'
    ax.set_title(prefix + suffix)
    ax.add_collection3d(axobj)
fig.tight_layout(pad=2.5)

# Figure 2 : Color from Line ==================================================
line = s3d.ParametricLine(rez,parametric_curve)

fig = plt.figure(figsize=(6,3))
fig.text(0.98,0.01,'Figure 2', ha='right', fontsize='x-small')
for i in range(2) :
    ax = fig.add_subplot(121+i, projection='3d')
    ax.set(xlim=minmax, ylim=minmax, zlim=minmax )
    ax.set( xlabel='X', ylabel='Y', zlabel='Z',
            xticks=ticks, yticks=ticks, zticks=ticks )
    ax.set_proj_type('ortho')
    surface = line.get_filled_surface( )
    surface.map_cmap_from_op(lambda xyz: xyz[i*2])
    prefix = 'X' if i==0 else 'Z'
    ax.set_title( prefix +' cmap surface')
    ax.add_collection3d(surface)
fig.tight_layout(pad=2.5)

# Figure 3 : lrez surface subdivisions ========================================
lrezArr = [0,1,3,6]

fig = plt.figure(figsize=(6,6))
fig.text(0.5,0.975,'rez=3' , ha='center', va='top', fontsize='x-large', fontweight='bold')
fig.text(0.98,0.01,'Figure 3', ha='right', fontsize='x-small')
for i,lrez in enumerate(lrezArr) :
    ax = fig.add_subplot(221+i, projection='3d')
    ax.set(xlim=minmax, ylim=minmax, zlim=minmax )
    ax.set_title('lrez = '+ str(lrez), fontsize='medium' )
    ax.set( xlabel='X', ylabel='Y', zlabel='Z',
            xticks=ticks, yticks=ticks, zticks=ticks )
    ax.set_proj_type('ortho')
    surface = line.get_filled_surface( lrez=lrez )
    surface.map_cmap_from_op(lambda xyz: xyz[2])
    ax.add_collection3d(surface)
fig.tight_layout(pad=2.5)

# Figure 4 : 3 segment line plots =============================================
line,lrez = s3d.SegmentLine(v), [0,3,6]

fig = plt.figure(figsize=(9,3))
fig.text(0.98,0.01,'Figure 4', ha='right', fontsize='x-small')
minmax=(0,1)
for i in range(3) :
    ax = fig.add_subplot(131+i, projection='3d')
    ax.set(xlim=minmax, ylim=minmax, zlim=minmax )
    ax.set( xlabel='X', ylabel='Y', zlabel='Z',
            xticks=ticks, yticks=ticks, zticks=ticks )
    ax.set_proj_type('ortho')
    surface = line.get_filled_surface( lrez=lrez[i])
    surface.map_cmap_from_op(lambda xyz: xyz[2])
    ax.set_title('lrez='+str(lrez[i]))
    ax.add_collection3d(surface)
fig.tight_layout(pad=2.5)

# Figure 5 : shredded line projection surface ================================
rez = 5
line = [ s3d.ParametricLine(rez,parametric_curve), s3d.SegmentLine(v).shred(4) ]

fig = plt.figure(figsize=(6,6))
fig.text(0.98,0.01,'Figure 5', ha='right', fontsize='x-small')
for i in range(4) :
    ax = fig.add_subplot(221+i, projection='3d')
    ax.set(xlim=minmax, ylim=minmax, zlim=minmax )
    ax.set( xlabel='X', ylabel='Y', zlabel='Z',
            xticks=[0,0.5,1], yticks=[0,0.5,1], zticks=[0,0.5,1] )
    ax.set_proj_type('ortho')
    lrez = 3+3*(i%2)
    prefix = 'rez=6' if i<2 else 'shred(4)'
    surface = line[int(i/2)].get_filled_surface( lrez=lrez,name='rez=6 lrez=3')
    surface.map_cmap_from_op(lambda xyz: xyz[2])
    ax.set_title(prefix + ' lrez=' + str(lrez))
    ax.add_collection3d(surface.shade(.75))
fig.tight_layout(pad=2.5)

# =============================================================================

plt.show()

Line Plane Surface

The following is the script demonstrating surface rendering control.

import numpy as np
from matplotlib import pyplot as plt
import s3dlib.surface as s3d
from matplotlib.ticker import LinearLocator
import copy

#.. Filled surface 3 : Anomalous visualization and lrez.
figNo = 1   # control which figure in surface or line set is rendered.

# 1. Define function to examine .....................................

def parametric_curve(t) :
    x = t
    y = 0.73*t**6 -1.5*t**2 + 1.5*t + .25  
    z = -0.2*t + .3*np.sin(1.5*np.pi*t)  + .7
    return x,y,z

def parametric_curve2(t) :
    x = 0.1 + 0.9*t
    y = 0.5*t + 0.2 + 0.1*np.sin(t*2*np.pi)
    z = t**0.75
    return x,y,z

# FIGURE I & II ===============================================================
rez, lrez, minmax, ticks = 3,3, (0,1), [0,0.5,1]

line1 = s3d.ParametricLine(rez,parametric_curve, lw=5)
line1.map_cmap_from_op( lambda xyz: xyz[1])
line2 = s3d.ParametricLine(rez,parametric_curve2, lw=5)
line2.map_cmap_from_op( lambda xyz: xyz[1],'plasma_r')
lines = line1 + line2
label = ['Anomalous', 'Correct, lrez=3']

figId = 'II' if figNo == 2 else 'I'
fig = plt.figure(figsize=(6,3))
fig.text(0.98,0.01,'Figure '+figId, ha='right', fontsize='x-small')
minmax=(0,1)
for i in range(2) :
    ax = fig.add_subplot(121+i, projection='3d')
    ax.set(xlim=minmax, ylim=minmax, zlim=minmax )
    color = 'red'     if i==0 else 'g'
    weight = 'normal' if i==0 else 'bold'
    ax.set_title(label[i], color=color, weight=weight)
    ax.set( xlabel='X', ylabel='Y', zlabel='Z',
            xticks=[0,0.5,1], yticks=[0,0.5,1], zticks=[0,0.5,1] )
    ax.set_proj_type('ortho')
    if i==0 :
        surface = lines.get_filled_surface()
    else :
        surface = lines.get_filled_surface(lrez=lrez)
    if figNo == 2 :surface.set_edgecolor('k')
    ax.add_collection3d(surface)
fig.tight_layout(pad=2.5)

# FIGURE III & IV =============================================================
rez, lrez, minmax, ticks = 3,2, (-1,1), [-1,0,1]

verts = [ [-1,1,1], [1,1,-1], [1,-0.5,0.5] , [0,-1,-1], [-1,0,0]  ]
lineA = s3d.ColorLine3DCollection(verts,[[1,4]],color='tomato')
lineB = s3d.ColorLine3DCollection(verts,[[0,2],[2,3]],color='royalblue')
lineAB = lineA+lineB
lineAB.set_linewidth(5)

figId    = 'III' if figNo==1 else 'IV'
lineC = lineAB if figNo==1 else copy.copy(lineAB).shred(rez)
surface = lineC.get_filled_surface(dist=-1.001,lrez=0 if figNo==1 else lrez)
title = 'shred('+str(rez)+'), lrez='+str(lrez)
fig = plt.figure(figsize=(6,3))
fig.text(0.98,0.01,'Figure '+ figId, ha='right', fontsize='small')

for i in range(2) :
    ax = fig.add_subplot(121+i, projection='3d')
    ax.view_init(20,-75)
    ax.set( xlabel='X', ylabel='Y', zlabel='Z',
            xticks=ticks, yticks=ticks, zticks=ticks )
    ax.set_aspect('equal')
    if figNo==2 : ax.set_title(title, fontsize='medium' )
    if i==0 :
        edges = surface.edges
        edges.set_color('k')
        ax.add_collection3d(edges)
        ax.add_collection3d(lineAB)
    else :
        if figNo==1 : ax.set_title('Anomalous', color='red', weight='normal' )        
        ax.add_collection3d(surface.shade(direction=[-.5,0.5,0.0]))
fig.tight_layout(pad=2.5)

# =============================================================================
plt.show()