7

For some figures, I need Asymptote over TikZ. However, I like the arrowheads in TikZ more than I do the arrowheads in Asymptote, particularly the stealth' arrowhead in TikZ.

Is there a way to create any (all?) of TikZ's arrowheads---at least stealth'---for Asymptote? (The arrowheads would need to be "flexible" enough to use in 2D or 3D.)

I haven't a clue how to look at TikZ's code for stealth' and mimic it for Asymptote.

MWE:

\documentclass[10pt, varwidth]{standalone}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage[inline]{asymptote}

\begin{document}
\begin{asy}

settings.outformat="pdf";
settings.prc=false;
settings.render=16;

import graph3;
import three;
unitsize(1cm);

currentprojection = obliqueX;

// AXES
limits((0,0,0), (3,2.5,2.5));
xaxis3(Label("$x$", align=NW), black + linewidth(0.6pt), arrow=Arrow3(size=4, DefaultHead2(normal=Z)));
yaxis3(Label("$y$", align=N), black + linewidth(0.6pt), arrow=Arrow3(size=4, DefaultHead2(normal=Z)));
zaxis3(Label("$z$", align=W), black + linewidth(0.6pt), arrow=Arrow3(size=4, DefaultHead2));
\end{asy}
\end{document}
Lucy
  • 71
  • 2
    Welcome to TeX.SE! You can look up the definition of stealth in pgflibraryarrows.code.tex where it is in the code of \pgfarrowsdeclare{stealth'}{stealth'}. Whether or not it is straightforward to convert this to an asymptote code is a different question. You may attract more attention to your nice question if you provide us with the asymptote code in which you want to use these arrows. –  Sep 16 '18 at 18:01
  • OK, I added a MWE. – Lucy Sep 16 '18 at 18:33
  • So each of the three arrowheads should be any arrowhead from TIkz (particularly stealth'). – Lucy Sep 16 '18 at 18:37
  • These are 3D arrow. To be honest, I am not convinced if the code of the 2D TikZ arrows can be converted to 3D arrows in a very simple way. Here you can find how to customize 2D arrows. Nevertheless, in section 3.13 of this great tutorial you can find some fancy predefined 3D arrowheads. To me Arrow3(HookHead3) looks a bit like a 3D generalization of stealth' but of course that's not well-defined. –  Sep 16 '18 at 18:38
  • I did see those 3D arrowheads in the tutorial. (I'm trying to avoid those 3D arrowheads.) I was just wondering if it's possible to instead replace the arrowheads I'm using in my MWE with arrowheads that look much like TikZ's arrowheads. – Lucy Sep 16 '18 at 18:41
  • I see. I am afraid that I don't know a full answer. So you are saying that HookHead2 is not close enough, do you? –  Sep 16 '18 at 19:28
  • I was hoping to get arrowheads more closely to those in TikZ. HookHead2 looks quite good, and it's probably the closest one to stealth' in TikZ. – Lucy Sep 16 '18 at 19:30
  • Well, I guess you could look up both definitions and then copy the HookHead2 to some new custom style which you adjust. The problem is that the syntax is quite different so it might not be straightforward. –  Sep 16 '18 at 19:32
  • That's my main problem: I don't know enough about the definitions to create the arrows myself. I'm hoping someone much smarter than me can do this in a jiffy. – Lucy Sep 16 '18 at 19:41
  • Many asymptote users may not know what a TikZ "stealth" arrowhead looks like. Can you add an image to your question showing what you hope to achieve? – James Sep 17 '18 at 11:27
  • Sure, I'll try to add an image later today (on the run now). – Lucy Sep 17 '18 at 18:34
  • Your best bet is likely to be changing some of the lengths defined in plain_arrows.asy to see if you can make HookHead2 look more like what you want. This is easier than it sounds: add lines like plain_arrows.arrowbarb=1; to your own asy file (after the imports but before you draw anything) and see what happens. This approach will affect all the arrows you draw in the picture, so use it with caution. – Charles Staats Sep 19 '18 at 17:33
  • @CharlesStaats Much thanks! I will give it a shot and check back on here. – Lucy Sep 19 '18 at 21:55

1 Answers1

5

I'm updating my answer since there was a bug in the code when combining dashed lines with MidArrow. This new module has corrected this. I've yet to find any bugs. I more or less adopted verbatim the way asymptote does arrows, but changed a few lines to incorporate sharper arrows and stealth styled arrow heads. Here's the module (named _custom_arrows.asy).

// This module contains functions for mimicing the tikz style of arrow heads.
// In particular, the sharpness of detail (arrows come to a point), and provides
// code for the stealth arrowhead which basic asymptote lacks.

arrowhead StealthHead(real dir=arrowdir, real barb=arrowbarb)
{
    arrowhead a;
    a.head=new path(path g, position position=EndPoint, pen p=currentpen,
                    real size=0, real angle=arrowangle)
    {
        if(size == 0) size=a.size(p);
        angle=min(angle*arrowhookfactor, 45);
        bool relative=position.relative;
        real position=position.position.x;
        if(relative) position=reltime(g, position);
        path r=subpath(g, position, 0);
        pair x=point(r, 0);
        real t=arctime(r, size);
        pair y=point(r,t);
        path base=arrowbase(r,y,t,size);
        path left=rotate(-angle,x)*r;
        path right=rotate(angle,x)*r;
        real[] T=arrowbasepoints(base,left,right,1);
        pair denom=point(right,T[1])-y;
        real factor=denom != 0 ? length((point(left,T[0])-y)/denom) : 1;
        path left=rotate(-angle*factor,x)*r;
        path right=rotate(angle*factor,x)*r;
        real[] T=arrowbasepoints(base,left,right,1);
        left=subpath(left,0,T[0]);
        right=subpath(right,T[1],0);
        pair pl0=point(left,0), pl1=relpoint(left,1);
        pair pr0=relpoint(right,0), pr1=relpoint(right,1);
        pair M=(pl1+pr0)/2;
        pair v=barb*unit(M-pl0);
        pl1=pl1+v; pr0=pr0+v;
        left=pl0{dir(-dir+degrees(M-pl0, false))}--pl1--M;
        right=M--pr0--pr1{dir(dir+degrees(pr1-M, false))};
        return left--right&cycle;
    };
    return a;
}
arrowhead StealthHead=StealthHead();

private real position(position currentpos, real size, path g, bool center)
{
    // Set pos to the real value equivalent of position.
    real pos = currentpos.position.x;
    if(currentpos.relative) {
        pos *= arclength(g);
        if(center) pos += 0.5*size;
        pos=arctime(g, pos);
    }
    else if (center)
        pos=arctime(g, arclength(subpath(g, 0, pos))+0.5*size);
    return pos;
}

private void drawsharparrow(frame f, arrowhead arhead=DefaultHead,
                            path g, pen p=currentpen, real size=0,
                            real angle=arrowangle, filltype fill=null,
                            position currentpos=EndPoint, bool forwards=true,
                            margin the_margin=NoMargin, bool center=false)
{
    // Paths for a portion of the path g and the arrow head.
    path head, r;

    // Boolean used when drawing the path.
    bool endpoint;

    // Integer used for the length of the path.
    int L;

    // Used for the real value equivalent of the input currentpos.
    real pos;

    // If size was not set, return size of the current pen.
    if(size == 0) size = arhead.size(p);

    // If fill was not set, use the fill type of the current pen.
    if(fill == null) fill = arhead.defaultfilltype(p);

    // Make sure the size is a legal value.
    size = min(arrowsizelimit*arclength(g), size);

    // Convert the current position into a real number.
    pos = position(currentpos, size, g, center);

    // Adjust the path by the selected margin.
    g = the_margin(g, p).g;

    // Store the length of the new path as a variable.
    L = length(g);

    // If the path should be going backwards, adjust g and pos.
    if(!forwards) {
        g   = reverse(g);
        pos = L-pos;
    }

    // Get the subpath of g with respect to the position pos.
    r = subpath(g, pos, 0);

    // Again, make sure size is a legal value.
    size = min(arrowsizelimit*arclength(r), size);

    // Set information about the arrow head.
    head = arhead.head(g, pos, p, size, angle);

    endpoint = pos > L-sqrtEpsilon;
    if(cyclic(head) && (fill == NoFill || endpoint)) {
        if(pos > 0)   draw(f, subpath(r, arctime(r, size), length(r)), p);
        if(!endpoint) draw(f, subpath(g, pos, L), p);
    }
    else draw(f, g, p);

    // Fill the arrow head, setting line width to 0.0 to make it "sharp".
    fill.fill(f, head, p+linewidth(0.0)+solid);
}

private void drawsharparrow2(frame f, arrowhead arhead=DefaultHead, path g,
                             pen p=currentpen, real size=0,
                             real angle=arrowangle, filltype fill=null,
                             margin the_margin=NoMargin)
{
    // Paths for a portion of the path g, the arrow head, and arrow tail.
    path head, tail, r;

    // Integer used for the length of the path.
    int L;

    // If size was not set, return size of the current pen.
    if(size == 0) size = arhead.size(p);

    // If fill was not set, use the fill type of the current pen.
    if(fill == null) fill = arhead.defaultfilltype(p);

    // Make sure the size is a legal value.
    size = min(arrowsizelimit*arclength(g), size);

    // Adjust the path by the selected margin.
    g = the_margin(g, p).g;

    // Store the length of the new path as a variable.
    L = length(g);

    // Set r to the reverse path of g.
    r = reverse(g);

    // Set information about the arrow heads.
    head = arhead.head(g, L, p, size, angle);
    tail = arhead.head(r, L, p, size, angle);

    if(cyclic(head))
        draw(f, subpath(r, arctime(r, size), L-arctime(g, size)), p);
    else draw(f,g,p);

    // Fill in the head and tail ends of the path with arrows.
    fill.fill(f,head,p+linewidth(0.0)+solid);
    fill.fill(f,tail,p+linewidth(0.0)+solid);
}

private picture sharparrow(arrowhead arhead=DefaultHead,
                           path g, pen p=currentpen, real size=0,
                           real angle=arrowangle, filltype fill=null,
                           position currentpos=EndPoint, bool forwards=true,
                           margin the_margin=NoMargin, bool center=false)
{
    // Picture we're adding an arrow to.
    picture pic;

    // Real equivalent of currentpos.
    real pos;

    // Path used for reversing arrow if forwards=false is set.
    path G;

    // If size was not set, return size of the current pen.
    if(size == 0) size = arhead.size(p);

    // Add the arrow to pic.
    pic.add(
        new void(frame f, transform t) {
            drawsharparrow(f, arhead, t*g, p, size, angle, fill, currentpos,
                           forwards, the_margin, center);
        }
    );

    // Add the path to the picture with the selected pen.
    pic.addPath(g, p);

    // Get the real value equivalent of currentpos.
    pos = position(currentpos, size, g, center);

    // If the path should be backwards, reverse it.
    if(!forwards) {
        G   = reverse(g);
        pos = length(g)-pos;
    }
    else G = g;

    // Draw the arrow on the picture.
    addArrow(pic, arhead, G, p, size, angle, fill, pos);

    return pic;
}

picture sharparrow2(arrowhead arhead=DefaultHead, path g, pen p=currentpen,
                    real size=0, real angle=arrowangle, filltype fill=null,
                    margin the_margin=NoMargin)
{
    // Picture we're adding an arrow to.
    picture pic;

    // Integer representing the length of the path.
    int L;

    // If size was not set, return size of the current pen.
    if(size == 0) size = arhead.size(p);

    pic.add(
        new void(frame f, transform t) {
            drawsharparrow2(f, arhead, t*g, p, size, angle, fill, the_margin);
        }
    );

    // Add the path with the selected pen to the picture.
    pic.addPath(g, p);

    // Set L to the length of g.
    L = length(g);

    // Add an arrow to the head and tail of the path.
    addArrow(pic, arhead, g, p, size, angle, fill, L);
    addArrow(pic, arhead, reverse(g), p, size, angle, fill, L);

    return pic;
}

arrowbar BeginSharpArrow(arrowhead arhead=DefaultHead, real size=0,
                         real angle=arrowangle, filltype fill=null,
                         position currentpos=BeginPoint)
{
    return new bool(picture pic, path g, pen p, margin the_margin) {
        add(pic, sharparrow(arhead, g, p, size, angle, fill, currentpos,
                            forwards=false, the_margin));
        return false;
    };
}

arrowbar SharpArrow(arrowhead arhead=DefaultHead, real size=0,
                    real angle=arrowangle, filltype fill=null,
                    position currentpos=EndPoint)
{
    return new bool(picture pic, path g, pen p, margin the_margin) {
        add(pic, sharparrow(arhead, g, p, size, angle, fill,
                            currentpos, the_margin));
        return false;
    };
}

arrowbar EndSharpArrow(arrowhead arhead=DefaultHead, real size=0,
                       real angle=arrowangle, filltype fill=null,
                       position currentpos=EndPoint)=SharpArrow;

arrowbar MidSharpArrow(arrowhead arhead=DefaultHead, real size=0,
                       real angle=arrowangle, filltype fill=null)
{
    return new bool(picture pic, path g, pen p, margin the_margin) {
        add(pic, sharparrow(arhead, g, p, size, angle, fill, MidPoint,
                            the_margin, center=true));
        return false;
    };
}

arrowbar SharpArrows(arrowhead arhead=DefaultHead, real size=0,
                     real angle=arrowangle, filltype fill=null)
{
    return new bool(picture pic, path g, pen p, margin the_margin) {
        add(pic, sharparrow2(arhead, g, p, size, angle, fill, the_margin));
        return false;
    };
}

And here's a test:

// Seting output format to "pdf".
import settings;
import _custom_arrows;
settings.outformat="pdf";
settings.render=4;

// Size of output.
size(300);

// Pairs of points to draw arrows between.
pair O = (0, 0);
pair X = (1, 0);

// Size of arrowhead.
real arsize = 5bp;

path g = O--X;

real dy = -0.5;
real dx =  1.5;

draw(shift(0*dx, 0*dy)*g, black, SharpArrow(arsize));
draw(shift(0*dx, 1*dy)*g, black, EndSharpArrow(arsize));
draw(shift(0*dx, 2*dy)*g, black, MidSharpArrow(arsize));
draw(shift(0*dx, 3*dy)*g, black, SharpArrows(arsize));
draw(shift(0*dx, 4*dy)*g, black, BeginSharpArrow(arsize));

draw(shift(1*dx, 0*dy)*g, black+dashed, SharpArrow(arsize));
draw(shift(1*dx, 1*dy)*g, black+dashed, EndSharpArrow(arsize));
draw(shift(1*dx, 2*dy)*g, black+dashed, MidSharpArrow(arsize));
draw(shift(1*dx, 3*dy)*g, black+dashed, SharpArrows(arsize));
draw(shift(1*dx, 4*dy)*g, black+dashed, BeginSharpArrow(arsize));

draw(shift(2*dx, 0*dy)*g, black, SharpArrow(StealthHead, arsize));
draw(shift(2*dx, 1*dy)*g, black, EndSharpArrow(StealthHead, arsize));
draw(shift(2*dx, 2*dy)*g, black, MidSharpArrow(StealthHead, arsize));
draw(shift(2*dx, 3*dy)*g, black, SharpArrows(StealthHead, arsize));
draw(shift(2*dx, 4*dy)*g, black, BeginSharpArrow(StealthHead, arsize));

draw(shift(3*dx, 0*dy)*g, black+dashed, SharpArrow(StealthHead, arsize));
draw(shift(3*dx, 1*dy)*g, black+dashed, EndSharpArrow(StealthHead, arsize));
draw(shift(3*dx, 2*dy)*g, black+dashed, MidSharpArrow(StealthHead, arsize));
draw(shift(3*dx, 3*dy)*g, black+dashed, SharpArrows(StealthHead, arsize));
draw(shift(3*dx, 4*dy)*g, black+dashed, BeginSharpArrow(StealthHead, arsize));

The output: enter image description here I acknowledge the request was for 3D arrows, but I've yet to find a satisfactory way of doing this other than by mimicry. Such as:

// Seting output format to "pdf".
import settings;
import _custom_arrows;
import graph;
settings.outformat="pdf";
settings.render=4;

// Size of output.
size(150);

// Various pens used throughout (axes, curves, perpendiculars).
pen apen = black+linewidth(0.8pt);
pen cpen = black+linewidth(0.4pt);
pen ppen = black+linewidth(0.2pt)+linetype("8 4");

// Paths for drawing.
path g;

// Mimic 3D drawing with these.
pair O = (0.0, 0.0);
pair X = scale(1/sqrt(2))*(-1.0, -1.0);
pair Y = (1.0, 0.0);
pair Z = (0.0, 1.0);

// Label for the axes.
Label L;

// Variable for indexing and angles.
int i;
real phi;

// Number of perpendiculars to drop.
int n = 8;

// Size of the arrow head.
real arsize = 5bp;

// Used for mimicing 3D drawing.
pair xyzpoint(real a, real b, real c){
    return scale(a)*X+scale(b)*Y+scale(c)*Z;
}

// 3D curve.
pair f0(real t){
    real xt = 0.4*cos(t);
    real yt = 0.4*sin(t);
    real zt = 0.4*cos(4.0*t);
    return xyzpoint(xt, yt, zt);
}

// Projection of 3D curve.
pair f1(real t){
    real xt = 0.4*cos(t);
    real yt = 0.4*sin(t);
    return xyzpoint(xt, yt, 0.0);
}

g = O--X;
L = Label("$x$", position=1.0, SW);
draw(L, g, apen, SharpArrow(StealthHead, arsize));

g = O--Y;
L = Label("$y$", position=1.0, E);
draw(L, g, apen, SharpArrow(StealthHead, arsize));

g = O--Z;
L = Label("$z$", position=1.0, N);
draw(L, g, apen, SharpArrow(StealthHead, arsize));

g = graph(f0, 0, 2pi, 400, operator ..);
draw(g, cpen);

g = graph(f1, 0, 2pi, 100, operator ..);
draw(g, cpen+dashed);

for (i=0; i<n; ++i){
    phi = 2*pi*i/n;
    g = f0(phi)--f1(phi);
    draw(g, ppen);
}

Output: enter image description here

A plethora of examples using this module can be found on my GitHub (GPL3 license): https://github.com/ryanmaguire/Mathematics-and-Physics/tree/master/asymptote

  • +1 Thanks for sharing this! –  Apr 08 '20 at 09:34
  • +1. Btw, you don't have to re-implement projection routines: "A triple or path3 can be projected to a pair or path, with project(triple, projection P=currentprojection) or project(path3, projection P=currentprojection)." – g.kov Aug 24 '20 at 06:54