5

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}
13
  • 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. – user121799 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. – user121799 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
4

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

2
  • +1 Thanks for sharing this! – user197952 Apr 8 '20 at 9: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 6:54

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.