# Contours, Clipped Surfaces and Line Projection and Slices¶

The following methods use a common definition for

- a vector direction, and
- a distance

to control surface clipping and line construction. Often there is no ‘one-way’ of creating a particular plot. Complex visualizations are a result of sequential applications of these methods. Numerous Lines and Surface Contours examples demonstrate that various combinations of these methods may be used to produce a similar visualization.

In this section, *line* refers to an object of, or derived from, the class *ColorLine3DCollection*.

## Surface Contours¶

Planar contour line objects which intersect a *surface* object with a plane are constructed
using the surface object method:

```
line = surface.contourLines(*dist,**kwargs)
```

Multiple planes in the direction may be passed using the *dist *distance* parameter.
The intersection plane is defined by the *distance* from the origin to the plane in
the direction of the plane normal. The keyword arguments are:

key | assignment values | control |
---|---|---|

direction | 3 element list or tuple | intersection surface orientation |

coor | integer or string | intersection surface type |

color | Matplotlib format color | default to surface color |

name | string | identifier |

where *direction* is a normal vector of the intersection plane. When direction is *None*,
the default direction is normal to the x-y plane, pointing in the z direction.

In this figure, the blue vector is normal to the cyan intersection plane. The intersection plane distance from the origin is indicated by the red point. The red line is the intersecting contour of the surface object and the plane.

A contour line is composed of line segments, each one intersecting surface faces.
As a result, the resolution of the contour line is a direct result of the surface *rez* from which
the line is constructed.

Construction of planar contours on a conical surface is shown in the Surface Contours example.

In addition to planar contours, radial contours may be constructed by intersecting
a surface object with a cylinder or sphere of a given radial distance using the same method.
The additional named argument *coor*, indicates the type of intersection surface, as listed in the following
table. When the direction is not specified, the default value is used.

Intersection Surface | coor value | direction (default) | contour On |
---|---|---|---|

planar (default) | 0, ‘p’, ‘P’, ‘planar’,’xyz’ | [ 0, 0, 1 ] | plane |

cylindrical | 1, ‘c’, ‘C’, ‘cylinder’, ‘cylinderical’ | [ 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 |

Example radial contours are shown in the following figure, including the intersecting surfaces. The orientation of the cylindrical intersecting surface is indicated by the blue vector.

For radial and cylindrical contours, the *dist* is a positive number indicating the radial distance to the intersection.

Evenly space contours on a surface object along a *direction* are constructed using the method:

```
line = surface.contourLineSet(numb, **kwargs)
```

where the parameter *numb* is an integer for the number of contours to be constructed. The spacing
of the intersection planes are set between the minimum to maximum surface coordinates along the *direction*.
The following figure shows a set of 5 planar contours for the example surface.

Construction of the three types of contour sets on a icosahedron is shown in the Planar and Radial Contours example.

Contours take the color of the surface from which they are constructed. Hence, contour object colors can be set by first coloring the surface, or alternatively, may be set by coloring the resulting line object.

## Surface Clipping¶

Surface objects may be clipped using several object methods based on:

- Functional boolean criteria of face position.
- Color channel value of face color
- Surface boundary intersection of a 2D plane, cylinder or sphere.

Clipping a surface removes faces from the surface. The *restore_from_clip* surface method will set
the surface geometry and color prior to clipping, negating any operations on the surface after
clipping. Clipping can result in jagged surface edges which are the edge faces of the remaining surface.
This jagged appearance is reduced by increasing the *rez* of the surface.

### Functional¶

A function of surface coordinates may be used to clip the surface faces using the surface object method:

```
surface.clip(operation, usexyz)
```

where ‘operation’ is a function of the surface native coordinates which returns a boolean value.
If xyz Cartesian coordinates are to be passed to the function, the boolean parameter *usexyz* is set to True.
The default for usexyz is False.

Functional clipping is shown in the examples Clipping a Surface and Texture Surface with Geometric Mapping. Also, Earth Interior demonstrates using clipping for an animation.

### Color Opacity¶

The surface face color can be used to indicate faces to be clipped using the method:

```
surface.clip_alpha(alphaCut,useval)
```

For colored surfaces with variations in the alpha color channel, *alphaCut* is a number between
0 and 1 indicating faces with alpha values below *alphaCut* which are to be removed. Alternatively, the
boolean *useval* is set to True if, instead of using alpha value of the face color, the
HSV V-value is used to remove surface faces. The default for *useval* is False.

The use of alpha clipping based on an image HSV values is shown in the Image Value Clipping example. Alpha clipping using colormapping is shown in the Geometric and Alpha Clipping example, which also illustrates using functional clipping.

### Surface¶

Planar clipping uses a plane similarly defined as contour definitions. The method for plane clipping is:

```
surface.clip_plane(dist, **kwargs)
```

where the keyword arguments are:

key | control |
---|---|

direction | intersection surface orientation |

coor | projection surface |

Consider the following example of contours on a sphere defined by the *dist* and *direction* values shown in
following figure.

The combination of direction and distance defines the contour. For example, a ‘negative’ direction and ‘negative’ distance define an identical plane with both ‘positive’ direction and distance. For example, the following two definitions of direction and distance are equivalent to define a plane for contours:

```
direction, dist = [ 1.0, 0.5, 1.0 ] , 0.35
direction, dist = [ -1.0, -0.5, -1.0 ] , -0.35
```

However, for clipping, the sign of these values defines
not only the clipping plane, but which side of the surface is clipped.
When the *dist* is positive, the side near the origin is retained.

Planar surface clipping is shown in the Clipped Surface example.

As with planar contours, *coor* values of 1 or 2 will use cylindrical or spherical clipping,
respectively. Examples of the three types of coordinate clipping are shown below.

Note

When the dist is assigned a value beyond the domain of the surface boundary, an error will be thrown.

## Line Clipping¶

Line objects may be clipped using line object methods based on:

- Functional boolean criteria of the line segment positions.
- Segment intersection of a 2D plane, cylinder or sphere.

Clipping a line object removes line segments. Line clipping is similar to surface clipping, with object methods:

```
line.clip(operation)
```

Note that the line object native coordinate system is Cartesian. As a result, unlike
the functional clipping of a surface, the line *clip* method does not have the
argument *usexyz*.

As with surface *clip_plane* method, clipping may be based on a plane,
a cylinder or sphere by using the *coor* argument.:

```
line.clip_plane(dist,**kwargs)
```

which is similar to the surface plane clipping, where the keyword arguments are:

key | control |
---|---|

direction | intersection surface orientation |

coor | projection surface |

Functional line clipping of contours lines, along with surface clipping, is shown in the Clipping Surface and Contours example.

## Line Projection¶

Any line object may be projected onto a plane, cylinder or sphere using the line method:

```
line.map_to_plane(dist,**kwargs)
```

where the arguments are similar to the plane *clip_plane* method argument definitions.

Projections of lines to a plane and sphere are shown in the Contour Projections and Earth Elevation Projected Contours examples.

## Line Slices¶

Line slices are contours in x-z or y-z planes created using **ParametricLine** object methods.

Visualizations may be similar in appearance to planar surface contours in directions [1,0,0] and [0,1,0] however:

- Line slices may only be in planes normal to [1,0,0] and [0,1,0].
- Line slice Z coordinates must be uniquely defined by the x,y coordinate, z=f(x,y) .
- Functions, not surfaces define the contour, as a result
- resolution is controlled by the line object, not a surface.
- line color is not a default surface color. Since slices are a line object, all coloring methods may be applied to the slices.
- line ‘shading’ is dependent on alignment with light direction, not surface normals (line normals are not unique)

The advantage of using a line slice compared to a surface contour is that a higher resolutions can be used without creating a high rez planar surface. This is particularly useful for datagrids with a large resolution.

Starting by instantiating a ParametricLine object, with the *operation=None*, the object is mapped to slices as:

```
line.map_xySlice_from_op(operation, **kargs)
```

where *operation* is a function that takes one 3XN array of x,y,z coordinates (z value is ignored) and returns
a 3XN array of coordinate values (only z value is used). This definition argument and return value is consistent
with the use of operations for geometric mapping of surfaces.

The keyword arguments set the lines in the line collection that are constructed:

key | value | meaning |
---|---|---|

xplane | x | line of constant x |

yplane | y | line of constant y |

xset | N | N number of lines of constant x from the min to max X domain |

yset | N | N number of lines of constant y from the min to max Y domain |

xlim | [min,max] | X domain of operation ( default: [-1,1] ) |

ylim | [min,max] | Y domain of operation ( default: [-1,1] ) |

The *xlim* and *ylim* arguments may be a single float value to set domains centered at the origin.

Slices are constructed using the method:

```
line.map_xySlice_from_dadtagrid(datagrid, **kargs)
```

where the datagrid sets the Z values of the lines. The *kargs* is similar to the above with the
exception that the *xlim* and *ylim* keys are not available. Similar to that for surface, datagrid
slices may be scaled using the transform method.

Numerous examples of slices are given in the Line Slices examples, including various comparisons to contours constructed from surfaces.

## Source Code¶

### Contours¶

The following is the script for generating the contour figures.

```
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import s3dlib.surface as s3d
#.. Guide Surface Contour Figures
# # 1. Define functions to examine ....................................
rez = 5
illum = [-.5,1,1]
direction, dirRot, dist = [0,.5,1], -26.565, 0.4
unitDir = np.divide( direction, np.linalg.norm(direction) )
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) :
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 getSurface(rez) :
surface = s3d.SphericalSurface(rez,basetype='octa',color=[1,.9,.7,0.35],lw=0)
surface.map_geom_from_op(deflate)
surface.transform(s3d.eulerRot(-10,35,25,useXconv=False))
return surface
# Figure 1 : Planar Contour =============================================================
surface = getSurface(rez).shade(.0,illum)
plane = s3d.PlanarSurface(rez,color=[0,0.35,0.35,0.5],lw=0)
plane.map_geom_from_op(cont_plane)
composite = surface+plane
composite.set_linewidth(0)
contour = surface.contourLines(dist,direction=direction,color='r')
contour.set_linewidth(3)
xyz= unitDir*dist
uvw=np.array( [direction])
vector = s3d.Vector3DCollection(xyz,uvw,alr=0.15,color='b')
line = s3d.SegmentLine([[0,0,0],xyz],color='k',lw=3)
fig = plt.figure(figsize=(3.5,3.5))
fig.text(0.95,0.01,'Figure 1', ha='right', fontsize='x-small')
ax = plt.axes(projection='3d')
ax.set(xlim=(-1,1), ylim=(-1,1), zlim=(-1,1))
s3d.standardAxis( ax )
ax.set_title('Planar Contour')
ax.add_collection3d(composite)
ax.add_collection3d(contour.fade(0,30,30))
ax.add_collection3d(vector)
ax.add_collection3d(line)
ax.scatter(*xyz.T, c='r', edgecolor='k', s=50)
fig.tight_layout()
# Figure 2: Cylinderical & Spherical Contours ===========================================
cdist,sdist = 0.6, 0.8
uvw=np.array( [unitDir])*2
vector = s3d.Vector3DCollection(-uvw,2*uvw,alr=0.025,color='b')
planes = [None]*2
planes[0] = s3d.CylindricalSurface(rez,color=[0,0.5,0.5,0.5],lw=0 )
planes[0].transform(scale=[cdist,cdist,1])
planes[0].transform( s3d.eulerRot(0,dirRot))
planes[1] = s3d.SphericalSurface(rez,color=[0,0.5,0.5,0.5],lw=0).domain(sdist)
dist = [cdist, sdist]
directions = [direction,None]
stype = ['Cylindrical', 'Spherical']
fig = plt.figure(figsize=(7,3.5))
fig.text(0.95,0.01,'Figure 2', ha='right', fontsize='x-small')
for i,plane in enumerate(planes) :
surface = getSurface(rez)
composite = surface + plane
composite.set_linewidth(0.01)
contour = surface.contourLines(dist[i],direction=directions[i],coor=i+1,color='r')
contour.set_linewidth(3)
ax = fig.add_subplot(121+i, projection='3d')
ax.set(xlim=(-1,1), ylim=(-1,1), zlim=(-1,1))
s3d.standardAxis( ax, offset=0.0 )
ax.set_title(stype[i] + ' Contours')
ax.add_collection3d(composite.shade(.0,illum))
ax.add_collection3d(contour.fade(ax=ax))
if i == 0 : ax.add_collection3d(vector)
fig.tight_layout()
# Figure 3 : Planar Contour Set =========================================================
surface = getSurface(rez)
uvw=np.array( [unitDir])*1.5
vector = s3d.Vector3DCollection([[0,0,0]],uvw,alr=0.15,color='b')
contour = surface.contourLineSet(rez,direction=direction, color='r')
fig = plt.figure(figsize=(3.5,3.5))
fig.text(0.95,0.01,'Figure 3', ha='right', fontsize='x-small')
ax = plt.axes(projection='3d')
ax.set(xlim=(-1,1), ylim=(-1,1), zlim=(-1,1))
s3d.standardAxis( ax )
ax.set_title('Planar Contour Set')
ax.add_collection3d(surface.shade(.0,illum))
ax.add_collection3d(contour.fade(ax=ax))
ax.add_collection3d(vector)
fig.tight_layout()
# =======================================================================================
plt.show()
```

### Clipping¶

The Following is the script for generating the clipping figures.

```
import numpy as np
import matplotlib.pyplot as plt
import s3dlib.surface as s3d
import s3dlib.cmap_utilities as cmu
#.. Guide Surface Clipping Figures
# # 1. Define functions to examine ....................................
color=[1,.9,.7]
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 getSurface(rez) :
surface = s3d.SphericalSurface(rez,basetype='octa',color=[1,.9,.7])
surface.map_geom_from_op(deflate)
surface.transform(s3d.eulerRot(-10,35,25,useXconv=False))
return surface
# Figure A : Planar Contours ============================================================
rez=5
surface = s3d.SphericalSurface(rez,color=color,lw=0)
surface.shade().set_surface_alpha(0.3)
line1 = surface.contourLines(0.35,direction=[1,0.5,1], color='r')
line2 = surface.contourLines(-.35,direction=[1,0.5,1], color='b')
fig = plt.figure(figsize=(3.5,3.5))
fig.text(0.98,0.01,'Figure A', ha='right',fontsize='x-small')
ax = plt.axes(projection='3d')
ax.set(xlim=(-1,1), ylim=(-1,1), zlim=(-1,1))
ax.set_title("[1, 0.5, 1]\n"+ r"$\pm$ 0.35",fontsize='medium')
ax.add_collection3d(surface)
ax.add_collection3d(line1.fade(0.2))
ax.add_collection3d(line2.fade(0.2))
fig.tight_layout()
# Figure B : Sphere Clipping ============================================================
rez, pv, nv, d = 6, [1,0.5,1] , [-1,-.5,-1] , 0.35
directions = [ pv, nv, pv, nv ]
distances = [ d, -d, -d, d ]
minmax = (-0.7,0.7)
illum = [-.5,1,1]
fig = plt.figure(figsize=(4,4))
fig.text(0.98,0.01,'Figure B', ha='right', fontsize='x-small')
for i in range(4) :
surface = s3d.SphericalSurface(rez,color=color)
direction = directions[i]
dist = distances[i]
surface.clip_plane(dist,direction=direction)
# ......................
ax = fig.add_subplot(2,2,1+i, projection='3d')
ax.set(xlim=minmax, ylim=minmax, zlim=minmax )
fColor = 'r' if i<2 else 'b'
ax.set_title( str(direction)+'\n'+str(dist), fontsize='small', color=fColor)
ax.set_axis_off()
ax.add_collection3d(surface.shade(ax=ax))
fig.tight_layout()
# Figure C : Three Types of Clipping Planes =============================================
rez = 7
direction, dirRot = [0,.5,1], -26.565
unitDir = np.divide( direction, np.linalg.norm(direction) )
negDir = [-direction[0],-direction[1],-direction[2]]
dist, cdist, sdist = 0.4, 0.6, 0.8
pvals = [
[ [direction, dist ] , [negDir, -dist] ],
[ [direction, cdist] , [direction, -cdist] ],
[ [direction, sdist] , [direction, -sdist] ]
]
titleType = [ 'Planar','Cylindrical','Spherical']
fig = plt.figure(figsize=(6,9))
fig.text(0.98,0.01,'Figure C', ha='right', fontsize='x-small')
i = 0
for sys in range(3) :
for pln in range(2) :
ax = fig.add_subplot(321+i, projection='3d')
i += 1
ax.set(xlim=(-1,1), ylim=(-1,1), zlim=(-1,1))
ax.view_init(30,30)
ax.set_axis_off()
c = pvals[sys][pln]
cc = '' if sys is 2 else c[0]
title = "{} Clipping\n{} {}".format(titleType[sys],cc,c[1])
ax.set_title(title,fontsize='small')
surface = getSurface(rez).shade(direction=illum,ax=ax)
surface.clip_plane(c[1],direction=c[0],coor=sys)
ax.add_collection3d(surface)
fig.tight_layout(pad=0)
# =======================================================================================
plt.show()
```