Function to create a path made of horizontal and vertical lines between a number of points

Function:

path[points_List, directions_List] := 
 Block[{disksPurple, pseudoCross, v, h, ints, dirs},
  If[Length@directions != Length@points - 1, Print["incorrect input"]; Abort[]];
  disksPurple = {Purple, Disk[#, 2] & /@ points};
  pseudoCross[p1_, p2_] := {{p1[[1]], p2[[2]]}, {p2[[1]], p1[[2]]}};
  dirs = directions /. {v -> {1, 0}, h -> {0, 1}};
  ints = Flatten[#, 1] & @ Pick[pseudoCross @@@ Partition[points, 2, 1], dirs, 1];
  Graphics[{disksPurple, {Red, Dashed, Line @ Riffle[points, ints]}}]
  ]

Usage: list of points is the first argument; the second is a list of directions (only first directions) between the consecutive points. E.g., to go from p1 to p2 first vertically, and then horizontally, type v; to go first horizontally, then vertically - type h. The reason is that if you go vertically from p1, you have to go horizontally next to reach p2, and vice versa.

Examples:

Clear[p1, p2, p3, p4, pts]
p1 = {40, 48}; p2 = {50, 116}; p3 = {63, 160}; p4 = {80, 100};
pts = {p1, p2, p3, p4};

path[pts, {v, v, v}]

enter image description here

path[pts, {h, h, h}]

enter image description here

path[pts, {v, h, h}]

enter image description here

path[pts, {v, h, v}] (* this is correct, just the choice of direction is poor *)

enter image description here


n = 10;
pts = RandomInteger[{0, 100}, {n, 2}];
dir = RandomChoice[{v, h}, Length@pts - 1];

path[pts, dir]

enter image description here

Works also when three consecutive points have their $x$ or $y$ coordinates the same:

path[{{100, 0}, {200, 0}, {300, 0}}, {h, h}]

enter image description here

The same image is obtained with {v, v}, {v, h} and {h, v}; similarly for the vertical alignment.


Incorrect input:

path[{{100, 0}, {200, 0}, {300, 0}}, {h, h, v}]

incorrect input

$Aborted


And finally, there's this funny behaviour when you make a typo in the directions:

Clear[p1, p2, p3, p4, pts, path]
p1 = {40, 48}; p2 = {50, 116}; p3 = {63, 160}; p4 = {80, 100};
pts = {p1, p2, p3, p4};
path[pts, {v, v, hv}]

enter image description here


Here is an attempt at automatically finding the best paths to use to join the points. This avoids having to explicitly give the horizontal and vertical specification for every point.

ClearAll[generateDirections, hvPath]

generateDirections[pts_, initialDirection_] :=
 Module[{d = initialDirection /. None :> RandomChoice[{1, 2}]},
  Join[{d},
   (Ordering[#[[All, d = 3 - d]]][[2]] == 2 || (d = 3 - d); d) & /@ 
    Partition[pts, 3, 1]]
 ]

hvPath[{pt1 : {a_, b_}, pt2 : {i_, j_}}, dir_] :=
  {pt1, Switch[dir, 1, {i, b}, 2, {a, j}], pt2}

hvPath[pts : {{_, _} ..}, initialDirection_ : None] :=
 Join @@ MapThread[
   hvPath, {Partition[pts, 2, 1], generateDirections[pts, initialDirection]}]

The idea is to use generateDirections to generate the best horizontal-vertical pathways, using the rule of thumb of initiating every new path using alternativily horizontal and vertical lines, except when this results in going over the same line used to arrive to the given point. Once the optimal directions are found the rest is easily handled by hvPath which generates the list with all the middle points.

Here is a couple of usage example:

With[{pts = {{0, 0}, {1, 1}, {2, 2}, {3, 3}}},
 Graphics[{
   [email protected], Point@pts,
   Dashed, Line@hvPath@pts
   }]
 ]

enter image description here

and a more complex one in which we join 20 random points:

enter image description here


You can simply provide two definitions for f

f[p1_, p2_, lVertical] := {p1, {p1[[1]], p2[[2]]}}
f[p1_, p2_, lHorizontal] := {p1, {p2[[1]], p1[[2]]}}