# What are ways to better show depth along a 3D curve?

What are effective techniques for making the depth of a 3D curve clear when it twists and turns, making its depth ambiguous from a static perspective?

points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];
ParametricPlot3D[
spline[t],
{t, 0, 1},
PlotRange -> {{0, 10}, {0, 10}, {0, 10}},
Boxed -> False,
AxesLabel -> {"u", "t", "s"},
AxesOrigin -> {0, 0, 0},
FaceGrids -> {{{0,
0, -1}, {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10}}}},
PlotStyle -> Directive[Black, Thin],
ViewPoint -> {5/6, -1.5, 0},
ImageSize -> Large,
Ticks -> False,
AxesStyle -> {Arrowheads[{{1/100, 1, arrow[]}}], Automatic}
] /. Line -> Arrow


Preferably keeping the presentation static, e.g. not animating it to spin, because then it would make the curve hard to trace and study by eye. Although, I remember seeing a technique where an image is animated to rotate back and forth just a small number of degrees—enough parallax to give the sense of depth—that might be fine, or at least interesting to see. But I'm more interested in general techniques—employing colors, lighting or shadows, opacity, and other properties I'm not imagining—that have been used in publications to address this exact issue.

• The first thing I thought was to use a Tube[]. This creates a surface for the curve that reacts with the lighting to better, umm, "reflect" its 3D nature. Odd that nobody mentions this. Especially since so many answer uses, while emphasizing second-order effects. Apr 1 at 13:59
• You might also want to try a 3D stereogram: plot two 3D graphs side by side, but with slightly different points of view. Then stare at the space between the graphs until your brain fuses the two images to create a 3D object. Apr 1 at 19:33
• draw a shadow on the u-t plane Apr 1 at 23:08

Try this for improved lighting and shading:

points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];
ParametricPlot3D[spline[t], {t, 0, 1},
PlotRange -> {{0, 10}, {0, 10}, {0, 10}},
Boxed -> False,
AxesLabel -> {"u", "t", "s"}, AxesOrigin -> {0, 0, 0},
FaceGrids -> {{{0,
0, -1}, {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10}}}},
PlotStyle -> Directive[Orange, Specularity[White, 30]],
ViewPoint -> {5/6, -1.5, 0}, ImageSize -> Large, Ticks -> False,
Lighting -> "Neutral"
] /. Line -> (Tube[#, 0.1] &)


• Note you can use PlotStyle -> Directive[Orange, Specularity[White, 30], Tube[0.1]] instead of the line replacement. Apr 1 at 13:59
• Wow, thank you. I always wondered what the supposed way to do it would be! ^^ Apr 1 at 14:39
• Or colorize along the curve.
ColorFunction -> Function[{x, y, z, t}, Hue@t], PlotStyle ->
Directive[Black, Thin, Tube[.1]], ColorFunctionScaling -> True

points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];
ParametricPlot3D[spline[t], {t, 0, 1},
PlotRange -> {{0, 10}, {0, 10}, {0, 10}}, Boxed -> False,
AxesLabel -> {"u", "t", "s"}, AxesOrigin -> {0, 0, 0},
FaceGrids -> {{{0,
0, -1}, {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10}}}}, ViewPoint -> {5/6, -1.5, 0},
ImageSize -> Large, Ticks -> False,
ColorFunction -> Function[{x, y, z, t}, Hue@t],
PlotStyle -> Directive[Black, Thin, Tube[.1]],
ColorFunctionScaling -> True]


• inspire by @MelaGo, set Method -> {"OneLayer" -> {"Depth", 1}} as in the document of Graphics3D.
SeedRandom[4];
points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];
g = ParametricPlot3D[spline[t], {t, 0, 1},
PlotRange -> {{0, 10}, {0, 10}, {0, 10}}, Boxed -> False,
AxesLabel -> {"u", "t", "s"}, AxesOrigin -> {0, 0, 0},
PlotStyle -> Black,
FaceGrids -> {{{0, 0, -1}, {Range[0, 10], Range[0, 10]}}},
Ticks -> False];
Show[g, Method -> {"OneLayer" -> {"Depth", 1}}, Boxed -> False,
Axes -> True]


• Using the ClipPlanes to clip some part of the curve along one direction in order to show the depth.
• Due to the bug of 14.0, the ClipPlanesStyle does not work(only 13,12,11 version work)
points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];
Manipulate[
ParametricPlot3D[spline[t], {t, 0, 1},
PlotRange -> {{0, 10}, {0, 10}, {0, 10}}, Boxed -> False,
AxesLabel -> {"u", "t", "s"}, AxesOrigin -> {0, 0, 0},
FaceGrids -> {{{0,
0, -1}, {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10}}}}, ViewPoint -> {5/6, -1.5, 0},
Ticks -> False, AxesStyle -> {Arrowheads[{{1/100, 1}}], Automatic},
ColorFunction -> Function[{x, y, z, t}, Hue@t],
PlotStyle -> Directive[Black, Thin, Tube[.1]],
ColorFunctionScaling -> True, ClipPlanes -> {0, -1, 0, d},
ClipPlanesStyle ->{Directive[Opacity[.5], Gray]}, PerformanceGoal -> "Quality"], {d,
0, 10}]


• Nice, that's what I was looking for. Good to learn about that Method option. Apr 1 at 15:20
• Whoa. The ClipPlanes idea solves my need better than I dreamed of—this is why I love SE and this community. I was trying to draw the path of a particle that travels in a curve in time, i.e. may move forward in time as well as curve and move backwards in time. This would have the effect of the particle appearing to exist at multiple locations at one time. A ClipPlanes animation like you showed illustrates that remarkably well; there would appear to be 2 disjoint curves at first, but the animation would reveal that they meet at the time the particle turns around. Thanks! Apr 2 at 12:15
• This gives me the side-idea to try ClipPlanesing in both directions, i.e. showing the points at the cross-section only, although that doesn't exact match as an answer to this question. Anyway, if I get to it first, I'll edit it into my answer. Apr 2 at 12:19

Borrowing a page from visualization of protein structures, which often use "fog" as a depth cue (see example at the bottom):

With an x axis depth cue:

SeedRandom[4];
points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];
ParametricPlot3D[spline[t], {t, 0, 1},
PlotRange -> {{0, 10}, {0, 10}, {0, 10}}, Boxed -> False,
AxesLabel -> {"u", "t", "s"}, AxesOrigin -> {0, 0, 0},
PlotStyle -> Black,
FaceGrids -> {{{0, 0, -1}, {Range[0, 10], Range[0, 10]}}},
Ticks -> False, ColorFunction -> Function[{x, y, z, t}, Opacity[x]],
ColorFunctionScaling -> True]


Or alternatively a t dimension depth cue, with

ColorFunction -> Function[{x, y, z, t}, Opacity[t]]


Just for fun, here is a ribbon model of the glutamate receptor mGluR4 (PDB #7E9H). The fog depth cue is evident in the top view.

• Protein visualization is a great example to draw from! Thank you for demonstrating what a fog depth cue would look like—I was curious myself. Unfortunately, I have to conclude for myself that a fog cue is not quite as strong as the material-light-and-shadow cue—for instance, looking at the left edge of your first example, it took me a few seconds to "bend" the shape in my mind to perceive that the faintest part was in the _back_—I guess the curves themselves can give illusions that oppose reality. Apr 1 at 2:28
• The second example, as I'm sure you already know, doesn't actually capture depth since it leaves depth as undecidable as before, though it does show something else—the parametrization direction. (Please do keep it as an educational example though—it was my own instinct to try that as well.) Apr 1 at 2:30

You can explore the various MaterialShading and Lighting options.

SeedRandom[1];
points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];

ParametricPlot3D[spline[t], {t, 0, 1},
AxesLabel -> {"u", "t", "s"},
AxesOrigin -> {0, 0, 0},
Boxed -> False,
FaceGrids -> {{{0, 0, -1}, {Range[0, 10], Range[0, 10]}}},
Lighting -> "ThreePoint",
PlotRange -> {0, 10},
Ticks -> False,
ViewPoint -> {5/6, -1.5, 0}]


• Huh, thank you for introducing me to Lighting -> "ThreePoint". That looks really awesome! Mar 31 at 23:35
SeedRandom[1];
points = RandomReal[{0, 10}, {25, 3}];
spline = BSplineFunction[points];

ptxy = points /. {x_, y_, z_} :> {x, y, 0};
splinexy = BSplineCurve[ptxy];
ptyz = points /. {x_, y_, z_} :> {0, y, z};
splineyz = BSplineCurve[ptyz];
ptxz = points /. {x_, y_, z_} :> {x, 0, z};
splinexz = BSplineCurve[ptxz];

p1 = ParametricPlot3D[{spline[t]}, {t, 0, 1}
, PlotRange -> {{0, 10}, {0, 10}, {0, 10}}
, Boxed -> False, AxesLabel -> {"u", "t", "s"}
, AxesOrigin -> {0, 0, 0}
, FaceGrids -> {
{{0, 0, -1}, {Range[1, 10, 2], Range[1, 10, 2]}}
, {{0, -1, 0}, {Range[1, 10, 2], Range[1, 10, 2]}}
, {{-1, 0, 0}, {Range[1, 10, 2], Range[1, 10, 2]}}
}
, PlotStyle -> Directive[Dashed, Orange, Specularity[White, 30]]
, ImageSize -> Large, Ticks -> False, Lighting -> "Neutral"];

g1 = Graphics3D[{
AbsoluteThickness[4], BSplineCurve[points]
, AbsoluteThickness[1]
, Red, Point@ptxy, BSplineCurve[ptxy]
, Blue, Point@ptxz, BSplineCurve[ptxz]
, Green, Point@ptyz, BSplineCurve[ptyz]
}
];

Show[p1, g1]


Using the option MeshFunction is another possibility :

points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];

splineDerivative = D[spline[t], t];

arcLength =
NDSolveValue[{f'[t] == Norm[splineDerivative], f[0] == 0},
f, {t, 0, 1}];

ParametricPlot3D[ spline[t], {t, 0, 1},
PlotRange -> {{0, 10}, {0, 10}, {0, 10}}, Boxed -> False,
AxesLabel -> {"u", "t", "s"}, AxesOrigin -> {0, 0, 0},
FaceGrids -> {{{0,
0, -1}, {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10}}}}, ViewPoint -> {5/6, -1.5, 0},
ImageSize -> Large, Ticks -> False,
, MeshFunctions -> {arcLength[#4] &}
, Mesh -> 300
, MeshShading -> {None, Red}] /. Line[data__] :> {Tube[data, .2]}


It is necessary to create the function arcLength because the spline is not arc-length parametrised.

With MeshShading -> {None, Red, None, Blue, None, Yellow} it gives :

• A nice sausage :-) Apr 1 at 10:42
points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];
Manipulate[
ParametricPlot3D[spline[t], {t, 0, 1},
PlotRange -> {{0, 10}, {0, 10}, {-2, 10}}, Boxed -> False,
FaceGrids -> {{{0, 0, -1}, {Range[0, 10], Range[0, 10]}}},
PlotStyle -> Directive[Black, Thin], Axes -> False,
PlotPoints -> 200, SphericalRegion -> True,
RotationAction -> "Clip",
ViewPoint ->
RotationMatrix[fi,
RotationMatrix[0.1, {1, 0, 0}] . {1.3, -2.4, 2.}] . {1.3, -2.4,
2.}], {fi, 0, 2 \[Pi]}]


Or if you prefer static image but still perceive 3D you can try stereograms. Click on the image to open it in full screen and then look like if through the screen until you see three copies of the curve and the one in the middle with 3D effect. Head must be kept straight vertical to have eyes in the same height.

I appreciate the answers so far; I hope to see more. I've been playing around since I posted my question though—I'll post my findings in this answer.

## Dashes

I noticed by accident that dashes happen to give a little bit of depth information. You can see dashes "bunch up" where they travel in exactly the depth direction—in and out of the screen. I know it's not a remarkable difference but, it might be good solution for someone looking for a light touch, without going into tubes and lighting.

More to come.