3

I have the following figure that represents some electromagnetic field. An electromagnetic field

The figure was somewhat replicated from this one: Source of replication

You can see that the original one has some shadings to the arrows while mine doesn't. Is there an effective way to achieve it? I tried to make the conical tips based on this solution here. The tips are perfect but without their shadings it's hard to see where the arrows are pointing.

Here's my code to reproduce my figure:

\documentclass[tikz,border=3mm]{standalone}

\usetikzlibrary{3d,arrows.meta}

\makeatletter

\pgfkeys{
  /pgf/arrow keys/.cd,
  pitch/.code={%
    \pgfmathsetmacro\pgfarrowpitch{#1}
    \pgfmathsetmacro\pgfarrowsinpitch{abs(sin(\pgfarrowpitch))}
    \pgfmathsetmacro\pgfarrowcospitch{abs(cos(\pgfarrowpitch))}
  },
}

\pgfdeclarearrow{
  name = Cone,
  defaults = {       % inherit from Kite
    length     = +3pt +2,
    width'     = +0pt +0.5,
    line width = +0pt 1 1,
    pitch      = +0, % lie on screen
  },
  cache = false,     % no need cache
  setup code = {},   % so no need setup
  drawing code = {   % but still need math
    % draw the base
    \pgfmathsetmacro\pgfarrowhalfwidth{.5\pgfarrowwidth}
    \pgfmathsetmacro\pgfarrowhalfwidthsin{\pgfarrowhalfwidth*\pgfarrowsinpitch}
    \pgfpathellipse{\pgfpointorigin}{\pgfqpoint{\pgfarrowhalfwidthsin pt}{0pt}}{\pgfqpoint{0pt}{\pgfarrowhalfwidth pt}}
    \pgfusepath{fill}
    % test if the cone part visible
    \pgfmathsetmacro\pgfarrowlengthcos{\pgfarrowlength*\pgfarrowcospitch}
    \pgfmathparse{\pgfarrowlengthcos>\pgfarrowhalfwidthsin}
    \ifnum\pgfmathresult=1
      % it is visible, so draw
      \pgfmathsetmacro\pgfarrowlengthtemp{\pgfarrowhalfwidthsin*\pgfarrowhalfwidthsin/\pgfarrowlengthcos}
      \pgfmathsetmacro\pgfarrowwidthtemp{\pgfarrowhalfwidth/\pgfarrowlengthcos*sqrt(\pgfarrowlengthcos*\pgfarrowlengthcos-\pgfarrowhalfwidthsin*\pgfarrowhalfwidthsin)}
      \pgfpathmoveto{\pgfqpoint{\pgfarrowlengthcos pt}{0pt}}
      \pgfpathlineto{\pgfqpoint{\pgfarrowlengthtemp pt}{ \pgfarrowwidthtemp pt}}
      \pgfpathlineto{\pgfqpoint{\pgfarrowlengthtemp pt}{-\pgfarrowwidthtemp pt}}
      \pgfpathclose
      \pgfusepath{fill}
    \fi
    \pgfpathmoveto{\pgfpointorigin}
  }
}

\begin{document}

\begin{tikzpicture}[z={(0,-0.5)}]
\begin{scope}[canvas is xz plane at y=0,line width=1.25pt,line cap=round]
%\draw (0,0) circle (2cm);
\draw[red!75!black,-{Cone[pitch=0]}](90:2) --++ (0:1cm);
\draw[red!75!black,-{Cone[pitch=36]}](126:2) --++ (36:1cm);
\draw[red!75!black,-{Cone[pitch=72]}](162:2) --++ (72:1cm);
\draw[red!75!black,-{Cone[pitch=108]}](198:2) --++ (108:1cm) node[above left] {$\vec{H}$};
\draw[red!75!black,-{Cone[pitch=144]}](234:2) --++ (144:1cm);
\draw[red!75!black,-{Cone[pitch=180]}](270:2) --++ (180:1cm);
\draw[red!75!black,-{Cone[pitch=216]}](306:2) --++ (216:1cm);
\draw[red!75!black,-{Cone[pitch=252]}](342:2) --++ (252:1cm);
\draw[red!75!black,-{Cone[pitch=288]}](378:2) --++ (288:1cm);
\draw[red!75!black,-{Cone[pitch=324]}](414:2) --++ (324:1cm);
\end{scope}
\draw[-{Cone},line width=1.5pt,line cap=round,blue!75!black] (0,0,0) -- (0,2,0) node[above]{$\vec{J}+\frac{\partial \vec{D}}{\partial t}$};
\end{tikzpicture}

\end{document}
6
  • HI! I cannot follow the definition of Cone you introduced, but I understand that there are two behaviors depending on whether the vertex is visible. A possibility to suggest the position in space would be to add a white line along the arrow's body (suggesting a highlight) and then to add a similar line on the cone only when visible. For consistency, all those lines must appear near the top part of each arrow... This is the point where the definition of the body of the arrow is important.
    – Daniel N
    Sep 24, 2023 at 23:56
  • If you can define a shaded arrow similar to this that would be helpful!
    – SolidMark
    Sep 25, 2023 at 1:36
  • I might try, but then I'll change your point of view. I'm not at easy with the arrow definition you use; I would define a 3d pic element instead to be a solid arrow (depending on the initial and end points of the desired arrow). And it will take some time... a week say. Anyway, in my opinion, to have coherent highlights in a 3d image the observer's point of view and the light source's position must be given (defined).
    – Daniel N
    Sep 25, 2023 at 9:12
  • Your point of view is welcome!
    – SolidMark
    Sep 25, 2023 at 14:26
  • @BlackMild Tags are about questions not answers. Unless OP “opens” their question to outside the TikZ verse I don't see why this should be tagged asymptote. Why not then also add metapost, pstricks? Why not also the linked question as well? Sep 25, 2023 at 18:41

2 Answers 2

3

enter image description here

The code is long since I defined a general cone in the light as a pic object. It depends on eight arguments: the centers of the bottom and top faces (6 arguments) and the two corresponding radii.

Based on this element, a solid arrow is constructed also as a pipc object. This time there are only six arguments (#4 is always "empty'). But there are two keys that control the arrow's aspect and that can be modified: arrowhead and arrow radius. In the example they are both modified for the blue arrow.

Remark I insert some cones that can be drawn with the cone object. Maybe somebody finds them useful.

enter image description here

The code

\documentclass[11pt, margin=15pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{math}
\begin{document}

\tikzset{%
  view/.style 2 args={%  observer longitude and latitude (y upwards)
                      %  Remark. lomg=0 means x=0
    z={({-sin(#1)}, {-cos(#1)*sin(#2)})},
    x={({cos(#1)}, {-sin(#1)*sin(#2)})},
    y={(0, {cos(#2)})},
    evaluate={%
      \tox={sin(#1)*cos(#2)};
      \toy={sin(#2)};
      \toz={cos(#1)*cos(#2)};
    },
    longitude = #1,
    latitude = #2
  },
  sun/.style n args={3}{% longitude, latitude, light contrast in [0, 1]
    sun longitude = #1,
    sun latitude = #2,
    contrast = #3,
    evaluate={%
      real \sunx, \suny, \sunz, \lightC;
      \sunx = sin(\sLongit)*cos(\sLatit);
      \suny = sin(\sLatit);
      \sunz = cos(\sLongit)*cos(\sLatit);
    }
  }
}

\pgfkeys{/tikz/.cd,
  latitude/.store in=\aLatit,  % observer's latitude
  latitude=0
}
\pgfkeys{/tikz/.cd,
  longitude/.store in=\aLongit,  % observer's longitude
  longitude=0  % corresponds to x=0
}
\pgfkeys{/tikz/.cd,
  sun latitude/.store in=\sLatit,
  sun latitude = 80
}
\pgfkeys{/tikz/.cd,
  sun longitude/.store in=\sLongit, 
  sun longitude=90
}
\pgfkeys{/tikz/.cd,
  contrast/.store in=\lightC,  % light contrast \in [0, 1]
  contrast=.5
}
\pgfkeys{/tikz/.cd,
  arrowhead/.store in=\arrowLConstant,  % proportion of the head \in [0, 1]
  arrowhead=.3
}
\pgfkeys{/tikz/.cd,
  arrow radius/.store in=\arrowRadius,  % body radius in cm
  arrow radius=.08
}

\tikzmath{%
  function coneBaseColor(\k) {% \k = 1 or 2 for the two disks: bottom top
    \res = \nx*\tox + \ny*\toy + \nz*\toz;
    if \k==1 then {% bottom; the normal vector is -n
      \res = -\res;
    };
    if \res>0 then {% if seen
      \tmp = int(100*\lightC*(\nx*\sunx + \ny*\suny + \nz*\sunz));
      if \k==1 then {return -\tmp;} else {return \tmp;};
    } else {return -1000;};
  };
  function coneFaceColor(\j, \M) {% verifies if seen
    real \ang;
    \t = 360*((\j-.5)/\M);
    \ux = \vx*cos(\t) +\wx*sin(\t);
    \uy = \vy*cos(\t) +\wy*sin(\t);
    \uz = \vz*cos(\t) +\wz*sin(\t);
    % modification needed when the radii are different
    \ang = atan2(\coneRadiusB -\coneRadiusT, \tmpAB);
    \ux = \ux*cos(\ang) +\nx*sin(\ang);
    \uy = \uy*cos(\ang) +\ny*sin(\ang);
    \uz = \uz*cos(\ang) +\nz*sin(\ang);    
    \res = \ux*\tox + \uy*\toy + \uz*\toz;
    if \res>0 then {% if seen
      \tmp = int(100*\lightC*(\ux*\sunx + \uy*\suny + \uz*\sunz));
      return \tmp;
    } else {return -1000;};
  };  
}

\tikzset{%
  pics/cone/.style args={(#1,#2,#3), (#4,#5,#6), rB=#7, rT=#8}{%
    % x, y, z of the bottom and top centers, bottom and top radius
    code={%  
      \colorlet{mainColor}{.}
      \colorlet{leftRGB{1}}{white}
      \colorlet{leftRGB{0}}{mainColor!50!black}
      \colorlet{mainDark}{mainColor!50!black}
      \tikzmath{%  A, B, rB, rT, and construction of the orthonormal basis
        real \Ax, \Ay, \Az, \Bx, \By, \Bz, \coneRadiusB, \coneRadiusT;
        \Ax = #1;
        \Ay = #2;
        \Az = #3;
        \Bx = #4;
        \By = #5;
        \Bz = #6;
        \coneRadiusB = #7;
        \coneRadiusT = #8;
        integer \coneN, \k, \j, \prevj, \i;
        \coneN = 13 +int(max(40*#7, 40*#8));
        real \tmpAB, \tmpx, \tmpy, \tmpz, \tmpcst, \tmpForColor;
        real \nx, \ny, \nz, \vx, \vy, \vz, \wx, \wy, \wz;
        \tmpx = \Bx -\Ax;
        \tmpy = \By -\Ay;
        \tmpz = \Bz -\Az;
        \tmpAB = sqrt(\tmpx*\tmpx +\tmpy*\tmpy +\tmpz*\tmpz);
        \nx = \tmpx/\tmpAB;
        \ny = \tmpy/\tmpAB;
        \nz = \tmpz/\tmpAB;
        if abs(\ny)>=abs(\nz) then {%
          \tmpcst = sqrt(\nx*\nx +\ny*\ny);
          \vx = -\ny/\tmpcst;
          \vy = \nx/\tmpcst;
          \vz = 0;
          \wx = -\vy*\nz;
          \wy = \vx*\nz;
          \wz = (1 -\nz*\nz)/\tmpcst;
        } else {%
          \tmpcst = sqrt(\nx*\nx +\nz*\nz);
          \vx = -\nz/\tmpcst;
          \vy = 0;
          \vz = \nx/\tmpcst;
          \wx = \vz*\ny;
          \wy = (\ny*\ny -1)/\tmpcst;
          \wz = -\vx*\ny;
        };
        %% points \P {1,\j} for bottom, \P {2,\j} for top
        for \j in {0, ..., \coneN}{%
          \t = \j/\coneN*360;
          \Px{1,\j} = \Ax +\coneRadiusB*\vx*cos(\t) +\coneRadiusB*\wx*sin(\t);
          \Py{1,\j} = \Ay +\coneRadiusB*\vy*cos(\t) +\coneRadiusB*\wy*sin(\t);
          \Pz{1,\j} = \Az +\coneRadiusB*\vz*cos(\t) +\coneRadiusB*\wz*sin(\t);
          \Px{2,\j} = \Bx +\coneRadiusT*\vx*cos(\t) +\coneRadiusT*\wx*sin(\t);
          \Py{2,\j} = \By +\coneRadiusT*\vy*cos(\t) +\coneRadiusT*\wy*sin(\t);
          \Pz{2,\j} = \Bz +\coneRadiusT*\vz*cos(\t) +\coneRadiusT*\wz*sin(\t);
        };
        %% lateral faces
        for \j in {1, ..., \coneN}{%
          \prevj = \j -1;
          \tmpForColor = coneFaceColor(\j, \coneN);
          if \tmpForColor>-999 then {%
            if \tmpForColor>=0. then { \i = 1; } else {%
              \i = 0;  % in the shade for leftRGB{}
              \tmpForColor = int(abs(\tmpForColor));
            };
            {%
              \filldraw[leftRGB{\i}!\tmpForColor!mainColor, line width=.001pt]
              (\Px{1,\prevj}, \Py{1,\prevj}, \Pz{1,\prevj})
              -- (\Px{1,\j}, \Py{1,\j}, \Pz{1,\j})
              -- (\Px{2,\j}, \Py{2,\j}, \Pz{2,\j})
              -- (\Px{2,\prevj}, \Py{2,\prevj}, \Pz{2,\prevj})
              -- cycle;
            };            
          };
        };
        %% bottom or top face
        for \k in {1, 2}{
          \tmpForColor = coneBaseColor(\k);
          if \tmpForColor>-999. then {%
            if \tmpForColor>=0. then { \i = 1; } else {%
              \i = 0;  % in the shade for leftRGB{}
              \tmpForColor = int(abs(\tmpForColor));
            };
            {
              \fill[leftRGB{\i}!\tmpForColor!mainColor]
              (\Px{\k,0}, \Py{\k,0}, \Pz{\k,0}) 
              \foreach \j in {1, ..., \coneN}{%
                -- (\Px{\k,\j}, \Py{\k,\j}, \Pz{\k,\j}) 
              } -- cycle;
           };
          };
        };
      }  % end tikzmath
    }
  },
  pics/solid arrow/.style args={(#1,#2,#3)--#4+(#5,#6,#7)}{%
    code={%
      \colorlet{arrowColor}{.}
      \tikzmath{%
        real \aVx, \aVy, \aVz;
        \aVx = #5;
        \aVy = #6;
        \aVz = #7;
        real \CyBx, \CyBy, \CyBz, \Mx, \My, \Mz, \CoTx, \CoTy, \CoTz;
        \CyBx = #1;
        \CyBy = #2;
        \CyBz = #3;
        \CoTx = \CyBx +\aVx;
        \CoTy = \CyBy +\aVy;
        \CoTz = \CyBz +\aVz;
        \Mx = \CyBx +(1 -\arrowLConstant)*\aVx;
        \My = \CyBy +(1 -\arrowLConstant)*\aVy;
        \Mz = \CyBz +(1 -\arrowLConstant)*\aVz;
        real \cylinderRadius, \coneRadius, \arrowL;
        \arrowL = sqrt(\aVx*\aVx +\aVy*\aVy +\aVz*\aVz);
        \cylinderRadius = \arrowRadius;
        \coneRadius = 2*\cylinderRadius;
        \testTopSeen = \aVx*\tox + \aVy*\toy + \aVz*\toz;
        if \testTopSeen>0 then {%
          {
            \draw pic[arrowColor]
            {cone={(\CyBx, \CyBy, \CyBz), (\Mx, \My, \Mz),
                rB=\cylinderRadius, rT=\cylinderRadius}};
            \draw pic[arrowColor]
            {cone={(\Mx, \My, \Mz), (\CoTx, \CoTy, \CoTz),
                rB=\coneRadius, rT=0}};
          };
        } else {%
          {
            \draw pic[arrowColor]
            {cone={(\Mx, \My, \Mz), (\CoTx, \CoTy, \CoTz),
                rB=\coneRadius, rT=0}};
            \draw pic[arrowColor]
            {cone={(\CyBx, \CyBy, \CyBz), (\Mx, \My, \Mz),
                rB=\cylinderRadius, rT=\cylinderRadius}};
          };
        };
      }  % end tikzmath
    }
  }
}


\begin{tikzpicture}[view={20}{30}, sun={-110}{30}{.5},
  arrow radius=.07,
  evaluate={%
    real \r, \l;
    \r = 3; \l = 1.6;
    integer \N;
    \N = 11;
  }]
  \foreach \k [evaluate=\k as \t using {90 +(\k/\N)*360}]
  in {1, ..., \N}{%
    \path pic[red]
    {solid arrow={({\r*sin(\t)}, 0, {\r*cos(\t)})--
        +({\l*cos(\t)}, 0, {-\l*sin(\t)})}};
  }
  \path pic[blue, arrow radius=.075, arrowhead=.17]
  {solid arrow={(0, 0, 0)-- +(0, 3*\l, 0)}};
  
  \path (0, 0, 0) +(0, 3*\l, 0)
  node[right=1ex] {$\vec{J}+\frac{\partial\vec{D}}{\partial t}$};;
\end{tikzpicture}
\end{document}
3
  • This one looks neat and clean! Do you think its possible to make the solids with true curved surfaces instead of polygon sides? But from zooming out it looks smooth nevertheless.
    – SolidMark
    Sep 29, 2023 at 15:22
  • The two cones in the second figure have smoother surfaces.
    – SolidMark
    Sep 29, 2023 at 15:23
  • The lateral surfaces are constructed from plane "rectangles". But their number can be increased to enhance the smooth impression. There is a formula for the number of rectangles, \coneN, that depends on the radii of the two bases. You can increase that number; the execution time will increase though.
    – Daniel N
    Sep 30, 2023 at 4:14
3

For real 3D arrows in Asymptote.

enter image description here

// Run on http://asymptote.ualberta.ca/
import three;
usepackage("amsmath");
size(5cm);
currentprojection=orthographic(2,3,1,zoom=.9);
string s="$\vec{J}+\dfrac{\partial\vec{D}}{\partial t}$";
draw(Label(s,align=N,EndPoint,blue),O--3Z,blue+3pt,Arrow3(size=15pt));

// (x(t),y(t)) is the circle of radius r
real r=2;
real x(real s){return r*cos(s);}
real y(real s){return r*sin(s);}
// (x'(t),y'(t)) are tangents
real xt(real s){return -r*sin(s);}
real yt(real s){return r*cos(s);}

int n=10; // the number of tangent vectors
for (int i=0; i<n; ++i){
real s=i*2pi/n;
path3 vec=O--(xt(s),yt(s),0);
transform3 T=shift(x(s),y(s),0)*scale3(.5);  
draw(T*vec,magenta+2pt,Arrow3(size=10pt));
}

label("$\vec{H}$",(0,-r-1,0),magenta);

Update: To get better look, each arrow body is considering as a cylinder (the module solids is needed).

enter image description here

// Run on http://asymptote.ualberta.ca/
import three;
import solids;
usepackage("amsmath");
size(5cm);
currentprojection=orthographic(2,3,1,zoom=.8);
string s="$\vec{J}+\dfrac{\partial\vec{D}}{\partial t}$";
draw(Label(s,align=N,EndPoint,blue),O--3Z,blue,Arrow3(size=15pt));
draw(scale(.05,.05,2.8)*unitcylinder,blue);

// (x(t),y(t)) is the circle of radius r
real r=2;
real x(real s){return r*cos(s);}
real y(real s){return r*sin(s);}
// (x'(t),y'(t)) are tangents
real xt(real s){return -r*sin(s);}
real yt(real s){return r*cos(s);}

int n=10; // the number of tangent vectors
for (int i=0; i<n; ++i){
real s=i*2pi/n;
triple B=(xt(s),yt(s),0),A=(x(s),y(s),0);  
path3 vec=A--A+.6B;
draw(vec,magenta,Arrow3(size=10pt));
draw(shift(A)*surface(cylinder(O,.05,1,B)),magenta);
}

label("$\vec{H}$",(0,-r-1,0),magenta);
3
  • 1
    What about the body of the arrow? Could it be shaded as well?
    – SolidMark
    Sep 27, 2023 at 5:43
  • @SolidMark That is interesting. The best I have known so far is considering the arrow body as a closed hollow cylinder tex.stackexchange.com/a/620534/140722
    – Black Mild
    Sep 27, 2023 at 11:29
  • @SolidMark see my update for a temporary solution
    – Black Mild
    Sep 27, 2023 at 13:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .