5
$\begingroup$

Example:

pts = {{{1, 0}, {0, 1}}, {{3, 2}, {1, 0}}, {{0, 5}, {3, 2}}, {{-5, 0}, {0, 5}},
      {{3, -8}, {-5, 0}}, {{16, 5}, {3, -8}}, {{-5, 26}, {16, 5}},
      {{-39, -8}, {-5, 26}}};

Graphics[Arrow /@ pts]

Mathematica graphics

In the image above, as the arrow length gets shorter, the (fixed-size) arrowheads become increasingly dominant; the last arrow's shaft is entirely eclipsed by the arrowhead.

(It is worth nothing that this behavior is "scale-invariant": the displayed image looks exactly the same if pts is replaced by—for example—0.01 pts.)

How can I make the sizes of the arrowheads (only! 1) proportional to the arrow's length2? By this I mean that the size of the arrowheads should be a fixed fraction (e.g. 1/25) of the length of the whole arrow. This means that the solution should be scale invariant (i.e., invariant with respect to re-scaling of all the arrow lengths).

I stress the scale invariance requirement because some proposed solutions do not satisfy it. For example, if one defines

arrow[pts : {p1_, p2_}] := {Arrowheads[Norm[p2 - p1]/500], Arrow[pts]}

...then, with pts defined as above, the expression Graphics[arrow /@ pts] produces this:

Mathematica graphics

...but the expression Graphics[arrow /@ (0.01 pts)], where all the dimensions have been scaled by 0.01, produces this:

Mathematica graphics


1 The thicknesses/widths of the shafts should remain as they are now (i.e. not proportional to arrow length.)

2 It is safe to assume that I'm dealing only with arrows consisting of a single segment, like those shown above. In this case, the notion of "arrow length" is reasonably straightforward.

$\endgroup$
  • 1
    $\begingroup$ Graphics[{Arrowheads[Norm[#]/200], Arrow@#} & /@ {{{1, 0}, {0, 1}}, {{3, 2}, {1, 0}}, {{0, 5}, {3, 2}}, {{-5, 0}, {0, 5}}, {{3, -8}, {-5, 0}}, {{16, 5}, {3, -8}}, {{-5, 26}, {16, 5}}, {{-39, -8}, {-5, 26}}}]? $\endgroup$ – kglr Jun 27 '15 at 21:33
  • 1
    $\begingroup$ Similar to above. arrow[{pt1_, pt2_}] := {Arrowheads[0.002 EuclideanDistance[pt1, pt2]], Arrow[{pt1, pt2}]}; arr = arrow[#] & /@ {{{1, 0}, {0, 1}}, {{3, 2}, {1, 0}}, {{0, 5}, {3, 2}}, {{-5, 0}, {0, 5}}, {{3, -8}, {-5, 0}}, {{16, 5}, {3, -8}}, {{-5, 26}, {16, 5}}, {{-39, -8}, {-5, 26}}}; Graphics[arr] screen shot !Mathematica graphics $\endgroup$ – Nasser Jun 27 '15 at 21:38
3
$\begingroup$
pts = {{{1, 0}, {0, 1}}, {{3, 2}, {1, 0}}, {{0, 5}, {3, 2}}, {{-5, 0}, {0, 5}}, 
      {{3, -8}, {-5, 0}}, {{16, 5}, {3, -8}}, {{-5, 26}, {16, 5}}, {{-39, -8}, {-5, 26}}};

ClearAll[aF]
aF[scale_, pts_, styles___] := Graphics[{styles, 
   Arrowheads[scale Norm[Subtract @@ ##]], Arrow @ #} & /@ pts];

aF[1/300, pts]

Mathematica graphics

aF[1/300, pts, Thick, Red]

enter image description here

$\endgroup$
  • $\begingroup$ Thanks! After I tried your answer (and also Nasser's), I realized that the wording of my question was ambiguous. I have added a couple of sentences to my post to disambiguate it. Basically, the arrowheads need to be a fixed fraction of the length of the arrow. (If you multiply all the points in pts by, say, 0.01, you'll see the problem.) $\endgroup$ – kjo Jun 27 '15 at 23:08
  • $\begingroup$ @kjo, i think the update fixes the issue. $\endgroup$ – kglr Jun 28 '15 at 0:35
  • $\begingroup$ Which help tutorials should I read in order to make sense of all the @, @@, #, ##, &, /@ symbols? I consider myself fairly experienced in programming languages and this is so different to anything else I have seen. Your whole command aF[scale_, pts_, styles___] := Graphics[{styles, Arrowheads[scale Norm[Subtract @@ ##]], Arrow @ #} & /@ pts]; Seems very hard to follow for me $\endgroup$ – Francisco Rodríguez Fortuño Aug 4 '18 at 18:34
1
$\begingroup$

To achieve your updated goal I believe you need to scale the arrowheads relative to the overall graphic. If the graphic consists entirely of arrows we can use the pts themselves for this. We can also use the

draw[pts_, scale_: 1, opts : OptionsPattern[Graphics]] :=
  Module[{size},
    size = EuclideanDistance @@ CoordinateBounds[pts];
    Graphics[{
      Arrowheads[ EuclideanDistance @@@ pts * scale/size ],
      Arrow @ pts
    }, opts]
  ]

Now the appearance is invariant with absolute scale of pts:

draw[pts*100, 0.1, Frame -> True]
draw[pts*.01, 0.1, Frame -> True]

enter image description here

If the arrows are only one part of the graphic and it is important that the arrowheads scale with the overall size rather than only the spread of the arrows themselves then I think you will need a two pass approach, first finding the PlotRange of the graphic, then re-rendering it with the new arrowhead sizes. Let me know if this is necessary for your application.

$\endgroup$
1
$\begingroup$

First draw an arrow:

arrow = Graphics[{AbsoluteThickness[1], Arrowheads[0.25], Arrow[{{0, 0}, {1, 0}}]}, PlotRange -> {{0, 1}, {-0.1, 0.1}}, ImagePadding -> None];

where AbsoluteThickness keeps the thickness of the shaft fixed (per footnote 1), and Arrowheads[0.25] makes the length of the arrowhead a fixed fraction of the graphic, and hence of the arrow.

Then you can Inset arrow in another graphic, using the size and dirs parameters to place and direct it:

Graphics[{Inset[arrow, #1, {0, 0}, Norm[#2 - #1], #2 - #1] & @@@ pts}]

The trick is that the size scale used by Arrowheads is still based on the inner Graphics, and so the arrowhead is rescaled along with the rest of the arrow.

P.S. I'm sure I got the idea of using Inset from another answer on Mathematica.se, but I can't find it right now.

$\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.