5
$\begingroup$

This question surfaced when I was working on this question

Given

zVals = {{1, 1, 1, 1}, {1, 2, 1, 2}, {1, 1, 3, 1}, {1, 2, 1, 4}};

and

ListPlot3D[zVals, Mesh -> All

gives the plot

plot1

I expected

pts = Catenate @ MapIndexed[{Sequence @@ #2, #1} &, zVals, {2}];
ListPlot3D[pts, Mesh -> All]

which generates explicit xyz triples to give the same plot, but it doesn't. The vertices are the same, but the triangulation is different. it looks like

plot2

Can somebody come up with a function or a method that will reproduce the triangulation used by ListPlot3D, but which accepts a list of xyz-triples?

$\endgroup$
  • $\begingroup$ It seems that ListPlot3D is generating the first edge of the triangle from the first two sequential points. Permuting the 2nd and 6th point of the catenated list results in precisely 1 re-triangulation. zc = Catenate@MapIndexed[{Sequence @@ #2, #1} &, zVals, {2}]; ex = zc[[{1, 6, 3, 4, 5, 2, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}]]; ListPlot3D[ex,Mesh->All] No clue yet what order ListPlot3D is generating its points from zVals though. $\endgroup$ – eyorble Jan 3 '18 at 2:27
6
$\begingroup$

The documentation says

ListPlot3D[array]

requires array to be a rectangular shape and that by default it assumes that the x and y coordinates for each point data point to be successive integers starting at 1.

What it doesn't state is which coordinate, x or y, changes fastest?

Looking at the examples it is clear that the x coordinate is the fastest changing.

So for example:

zVals = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}};

means that for the first sub-list we would write {x, y, z} by holding the y value at a constant value of 1 and allowing x to increment from 1 to 4

{1, 2, 3, 4} -> {1,1,1}, {2,1,2}, {3,1,3}, {4,1,4}

Similarly for the second sublist we hold the y value at a constant of 2 and allow x to once again increment from 1 to 4

{5, 6, 7, 8} -> {1,2,5}, {2,2,6}, {3,2,7}, {4,2,8}

and so on.

To convert the zVals to pts with {x,y,z} values use:

pts = Flatten[ Table[{j, i, zVals[[i, j]]}, {i, 4}, {j, 4}], 1]

(* {{1, 1, 1},  {2, 1, 2},  {3, 1, 3},  {4, 1, 4},
    {1, 2, 5},  {2, 2, 6},  {3, 2, 7},  {4, 2, 8},
    {1, 3, 9},  {2, 3, 10}, {3, 3, 11}, {4, 3, 12},
    {1, 4, 13}, {2, 4, 14}, {3, 4, 15}, {4, 4, 16}} *)

and then

ListPlot3D[zVals, Mesh -> All, AxesLabel -> {x, y, z}]

Mathematica graphics

ListPlot3D[pts, Mesh -> All, AxesLabel -> {x, y, z}]

Mathematica graphics

If instead you use

pts = Catenate@MapIndexed[{Sequence @@ #2, #1} &, zVals, {2}]
(* {{1, 1, 1}, {1, 2, 2}, {1, 3, 3}, {1, 4, 4}, {2, 1, 5}, {2, 
  2, 6}, {2, 3, 7}, {2, 4, 8}, {3, 1, 9}, {3, 2, 10}, {3, 3, 11}, {3, 
  4, 12}, {4, 1, 13}, {4, 2, 14}, {4, 3, 15}, {4, 4, 16}} *)

which is equivalent to

pts = Flatten[Table[{i, j, zVals[[i, j]]}, {i, 4}, {j, 4}], 1]
(* {{1, 1, 1}, {1, 2, 2}, {1, 3, 3}, {1, 4, 4}, {2, 1, 5}, {2, 
  2, 6}, {2, 3, 7}, {2, 4, 8}, {3, 1, 9}, {3, 2, 10}, {3, 3, 11}, {3, 
  4, 12}, {4, 1, 13}, {4, 2, 14}, {4, 3, 15}, {4, 4, 16}} *)

the resulting figure has the wrong relationship between x, y and z.

ListPlot3D[pts, Mesh -> All, AxesLabel -> {x, y, z}]

Mathematica graphics

Update - Triangulation

As pointed out by m_goldberg the proposed solution has a different triangulation compared to the figure using zVals.

That had escaped my notice.

I dissected the figure and discovered that it used GraphicsComplex with a list of identical points between the two figures but a different set of polygons (same points, different connection resulting in different polygons).

I have not been able to figure out the rules that Mathematica uses for connecting the points with Mesh -> All but I was able to discover that one would get different connections if one input a different order for the points.

By trial and error I stumbled upon that if I took the original set of {x, y, z} points (i.e, pts with x changing most rapidly) and ordered them by what I will call the decrement x and increment y order, I was able to achieve the same triangulation.

pts = Flatten[Table[{j, i, zVals[[i, j]]}, {i, 4}, {j, 4}], 1]
(* {{1, 1, 1}, {2, 1, 2}, {3, 1, 3}, {4, 1, 4}, {1, 2, 5}, {2, 
  2, 6}, {3, 2, 7}, {4, 2, 8}, {1, 3, 9}, {2, 3, 10}, {3, 3, 11}, {4, 
  3, 12}, {1, 4, 13}, {2, 4, 14}, {3, 4, 15}, {4, 4, 16}} *)

ptsDecXandIncY = 
 pts[[{4, 3, 2, 1, 8, 7, 6, 5, 12, 11, 10, 9, 16, 15, 14, 13}]]
(* {{4, 1, 4}, {3, 1, 3}, {2, 1, 2}, {1, 1, 1}, {4, 2, 8}, {3, 
  2, 7}, {2, 2, 6}, {1, 2, 5}, {4, 3, 12}, {3, 3, 11}, {2, 3, 10}, {1,
   3, 9}, {4, 4, 16}, {3, 4, 15}, {2, 4, 14}, {1, 4, 13}} *)

ListPlot3D[ptsDecXandIncY, Mesh -> All, AxesLabel -> {x, y, z}]

Mathematica graphics

Unfortunately I am unable to determine what the general rule is for connecting the points.

Hope this helps.

$\endgroup$
  • $\begingroup$ This doesn't seem to bee quit right because the two triangulations are not the same, but are mirror images of each other $\endgroup$ – m_goldberg Jan 3 '18 at 22:30
  • $\begingroup$ @m_goldberg I agree. I have updated with a section on triangulation. I now get identical results (by trial and error) but am unable to infer he rules Mathematica uses in connecting the points. $\endgroup$ – Jack LaVigne Jan 5 '18 at 20:56
4
$\begingroup$

ListPlot3D maps the values to points with the reverse {x,y} order as your construction.

Let's consider the original case first:

zVals = {{1, 1, 1, 1}, {1, 2, 1, 2}, {1, 1, 3, 1}, {1, 2, 1, 4}};

Note that this case is symmetric about the plane $x=y$. Let zc be your catenation of them to form a list of {x,y,z} points.

zc = Catenate@MapIndexed[{Sequence @@ #2, #1} &, zVals, {2}];

We can plot these both with:

ListPlot3D[zVals, Mesh->All, AxesLabel -> {x, y, z}]
ListPlot3D[zc, Mesh->All, AxesLabel -> {x, y, z}]

And, as shown in the question, they will appear essentially identical, with inverted triangulation. This inverted triangulation arises from the transposition of the x and y components of the mesh used to display them, which can be found by using InputForm on the plots.

zPoints = InputForm[ListPlot3D[zVals, Mesh -> All, AxesLabel -> {x, y, z}]][[1, 1, 1]]
zcPoints = InputForm[ListPlot3D[zc, Mesh -> All, AxesLabel -> {x, y, z}]][[1, 1, 1]]
(zcPoints /. {x_,y_,z_}->{y,x,z}) == zPoints

A more obvious visual example can be had with this set of z values, which are not symmetric about $x=y$:

zVals = {{1, 2, 3, 4}, {-1, -2, -3, -4}, {4, 3, 2, 1}, {-4, -3, -2, -1}};
zc = Catenate@MapIndexed[{Sequence @@ #2, #1} &, zVals, {2}];

In this case, the two plots are clearly inverted:

ListPlot3D[zVals, Mesh->All, AxesLabel -> {x, y, z}]
ListPlot3D[zc, Mesh->All, AxesLabel -> {x, y, z}]

As for why ListPlot3D behaves this way, I do not know.

A graph equivalent to the output of ListPlot3D[zVals] can be generated by taking the list of z points with x and y swapped, e.g.:

z2 = Flatten[Table[{j, i, zVals[[i, j]]},
          {i, 1, Length[zVals]}, {j, 1, Length[zVals[[1]]]}], 1];
ListPlot3D[z2, Mesh -> All, AxesLabel -> {x, y, z}]
$\endgroup$
2
$\begingroup$

A function that will generate the same surface as ListPlot3D can be written as follows:

listPlot3DFromZ[zVals : {__}, opts : OptionsPattern[]] := 
  Module[{pts}, 
    pts = Catenate[Reverse /@ MapIndexed[{#2[[2]], #2[[1]], #1} &, zVals, {2}]];
    ListPlot3D[pts, opts]]

listPlot3DFromZ is a generalization of the solution that Jack LaVigne presented in the update to his answer.

I don't believe even for a moment that ListPlot3D converts an array argument like this function does. I believe it generates the GraphicsComplex it outputs by more direct methods.

Karacoreable's example

Here is the plot form the original question, reproduced correctly.

With[{zvals = {{1, 1, 1, 1}, {1, 2, 1, 2}, {1, 1, 3, 1}, {1, 2, 1, 4}}}, 
  GraphicsRow[
    {listPlot3DFromZ[zvals,
       Mesh -> All,
       AxesLabel -> {x, y, z},
       BoxRatios -> {1, 1, 1}],
     ListPlot3D[zvals,
       Mesh -> All,
       AxesLabel -> {x, y, z},
       BoxRatios -> {1, 1, 1}]}]]

plots1

Jack LaVigne's and eyorble's examples

Here are the plots from Jack LaVigne's and eyorble's answers, both also reproduced correctly.

With[{zvals = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}}, 
  GraphicsRow[
    {listPlot3DFromZ[zvals,
       Mesh -> All,
       AxesLabel -> {x, y, z},
       BoxRatios -> {1, 1, 1}],
     ListPlot3D[zvals,
       Mesh -> All,
       AxesLabel -> {x, y, z},
       BoxRatios -> {1, 1, 1}]}]]

plots2

With[{zvals = {{1, 2, 3, 4}, {-1, -2, -3, -4}, {4, 3, 2, 1}, {-4, -3, -2, -1}}, 
  GraphicsRow[
    {listPlot3DFromZ[zvals,
       Mesh -> All,
       AxesLabel -> {x, y, z},
       BoxRatios -> {1, 1, 1}],
     ListPlot3D[zvals,
       Mesh -> All,
       AxesLabel -> {x, y, z},
       BoxRatios -> {1, 1, 1}]}]]

plot3

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.