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/\pgfarrowlengthcossqrt(\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); \drawred!75!black,-{Cone[pitch=0]} --++ (0:1cm); \drawred!75!black,-{Cone[pitch=36]} --++ (36:1cm); \drawred!75!black,-{Cone[pitch=72]} --++ (72:1cm); \drawred!75!black,-{Cone[pitch=108]} --++ (108:1cm) node[above left] {$\vec{H}$}; \drawred!75!black,-{Cone[pitch=144]} --++ (144:1cm); \drawred!75!black,-{Cone[pitch=180]} --++ (180:1cm); \drawred!75!black,-{Cone[pitch=216]} --++ (216:1cm); \drawred!75!black,-{Cone[pitch=252]} --++ (252:1cm); \drawred!75!black,-{Cone[pitch=288]} --++ (288:1cm); \drawred!75!black,-{Cone[pitch=324]} --++ (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}

Black Mild
  • 17,569
SolidMark
  • 1,389
  • 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 '23 at 23:56
  • If you can define a shaded arrow similar to this that would be helpful! – SolidMark Sep 25 '23 at 01: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 '23 at 09:12
  • Your point of view is welcome! – SolidMark Sep 25 '23 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 [tag:asymptote]. Why not then also add [tag:metapost], [tag:pstricks]? Why not also the linked question as well? – Qrrbrbirlbel Sep 25 '23 at 18:41
  • @SolidMark don't forget to accept an answer in case it fits what you were looking for. – Daniel N Oct 02 '23 at 03:56

2 Answers2

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 rcos(s);} real y(real s){return rsin(s);} // (x'(t),y'(t)) are tangents real xt(real s){return -rsin(s);} real yt(real s){return rcos(s);}

int n=10; // the number of tangent vectors for (int i=0; i<n; ++i){ real s=i2pi/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 rcos(s);} real y(real s){return rsin(s);} // (x'(t),y'(t)) are tangents real xt(real s){return -rsin(s);} real yt(real s){return rcos(s);}

int n=10; // the number of tangent vectors for (int i=0; i<n; ++i){ real s=i2pi/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);

Black Mild
  • 17,569
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 = \vxcos(\t) +\wxsin(\t); \uy = \vycos(\t) +\wysin(\t); \uz = \vzcos(\t) +\wzsin(\t); % modification needed when the radii are different \ang = atan2(\coneRadiusB -\coneRadiusT, \tmpAB); \ux = \uxcos(\ang) +\nxsin(\ang); \uy = \uycos(\ang) +\nysin(\ang); \uz = \uzcos(\ang) +\nzsin(\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/\coneN360; \Px{1,\j} = \Ax +\coneRadiusB\vxcos(\t) +\coneRadiusB\wxsin(\t); \Py{1,\j} = \Ay +\coneRadiusB\vycos(\t) +\coneRadiusB\wysin(\t); \Pz{1,\j} = \Az +\coneRadiusB\vzcos(\t) +\coneRadiusB\wzsin(\t); \Px{2,\j} = \Bx +\coneRadiusT\vxcos(\t) +\coneRadiusT\wxsin(\t); \Py{2,\j} = \By +\coneRadiusT\vycos(\t) +\coneRadiusT\wysin(\t); \Pz{2,\j} = \Bz +\coneRadiusT\vzcos(\t) +\coneRadiusT\wzsin(\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={({\rsin(\t)}, 0, {\rcos(\t)})-- +({\lcos(\t)}, 0, {-\lsin(\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}

Daniel N
  • 5,687
  • 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 '23 at 15:22
  • The two cones in the second figure have smoother surfaces. – SolidMark Sep 29 '23 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 '23 at 04:14