3

Before I go into too much detail on this question, the end result I want is something like this picture:wobbly load I can draw all of it except the distributed force arrows running from the wobbly line w. Ideally, I'd be able to create a macro that will draw a series of force lines between two points, topped with a wobbly or predetermined line.


I'm in the process of writing a package to draw structural analysis diagrams similarly to stanli—I didn't really like various aspects of it, and wanted more flexibility in my drawings. I've reimplemented large parts of it to do additional things, and one of the commands (\distload) will draw a series of arrows to represent a distributed load between two points. I'm now looking at creating a \pathload command, which will do the same thing, except the force lines will start (or end) at some arbitrary predefined path.

Here's a MWE illustrating the progress I've made so far. I've split it into separate code blocks for clarity, so you'll need to concatenate them for it to compile. First up is the common preamble:

% arara: lualatex: { shell: true }
\documentclass{article}
\usepackage{expl3}
\usepackage{xparse}
\usepackage{etoolbox}
\usepackage{tikz}
% Lua math library
\usepgflibrary{luamath}
\pgfkeys{pgf/luamath=parser}
\usepgflibrary{fpu}

\usetikzlibrary{calc}
\usepgflibrary{arrows.meta}
\usetikzlibrary{bending}
\usetikzlibrary{decorations.markings}
\usetikzlibrary{decorations.pathmorphing}
\usetikzlibrary{decorations.pathreplacing}
\usetikzlibrary{intersections}
\usetikzlibrary{positioning}

% TikZ Styles
\tikzset{
  force/.style={draw, very thick, arrows = {-Stealth[bend]}},  % Force with arrow tip at end
  force'/.style={force, arrows = {Stealth[bend]-}},   % Force with arrow tip at start 
  force label/.style={inner sep=1pt},
}

% Distributed load parameters
\newlength{\distloadDistance}
\newlength{\distloadLength}
\setlength{\distloadDistance}{0pt}
\setlength{\distloadLength}{5mm}
\newcommand{\distloadSegments}{5}

This next macro draws the distributed load between two points, optionally specifying a label, the length of the force lines at each end, the number of segments, and additional text properties for that label. It works quite well, though I may need to optimise some of it.

% \distload[*away]{start}{end}[label][magnitude start][magnitude end][segments][text properties]
\NewDocumentCommand{\distload}{s m m o O{\distloadLength} O{\distloadLength} O{\distloadSegments} o}{
  \coordinate (distloadA1) at ($ (#2)!\distloadDistance!90:(#3) $);
  \coordinate (distloadB1) at ($ (#3)!\distloadDistance!-90:(#2) $);
  \coordinate (distloadA2) at ($ (#2)!{\distloadDistance+#5}!90:(#3) $);
  \coordinate (distloadB2) at ($ (#3)!{\distloadDistance+#6}!-90:(#2) $);
  \pgfmathsetmacro{\distloadInterval}{1/#7}
  \pgfmathsetmacro{\distloadIntervalBegin}{\distloadInterval}
  \pgfmathsetmacro{\distloadIntervalStep}{\distloadInterval*2}
  \pgfmathsetmacro{\distloadIntervalEnd}{1-\distloadInterval}

  \draw [thin] (distloadA2) -- (distloadB2);
  \IfValueT{#4}{
    \IfNoValueTF{#8}{
      \path (distloadA2) -- (distloadB2) node[force label, sloped, above=2pt, midway]{#4};
    } {
      \path (distloadA2) -- (distloadB2) node[force label, sloped, above=2pt, midway, #8]{#4};
    }
  }
  \IfBooleanTF{#1}{
    \ifdimequal{#5}{0pt}{}{
      \draw [force', semithick] (distloadA2) -- (distloadA1);
    }
    \ifdimequal{#6}{0pt}{}{
      \draw [force', semithick] (distloadB2) -- (distloadB1);
    }
    \begin{scope}
      \clip (distloadA1) -- (distloadB1) -- (distloadB2) -- (distloadA2) -- cycle;
      \foreach \i in {\distloadIntervalBegin,\distloadIntervalStep,...,\distloadIntervalEnd}
        \draw [force', semithick] ($(distloadA2)!\i!(distloadB2)$) -- ($(distloadA1)!\i!(distloadB1)$);
    \end{scope}
  }
  {
    \ifdimequal{#5}{0pt}{}{
      \draw [force, semithick] (distloadA2) -- (distloadA1);
    }
    \ifdimequal{#6}{0pt}{}{
      \draw [force, semithick] (distloadB2) -- (distloadB1);
    }
    \begin{scope}
      \clip (distloadA1) -- (distloadB1) -- (distloadB2) -- (distloadA2) -- cycle;
      \foreach \i in {\distloadIntervalBegin,\distloadIntervalStep,...,\distloadIntervalEnd}
        \draw [force, semithick] ($(distloadA2)!\i!(distloadB2)$) -- ($(distloadA1)!\i!(distloadB1)$);
    \end{scope}
  }
}

The next code block is the analogous macro for generating a series of force lines between two points and some predefined line that I'm trying to figure out. The easiest way to solve this would be to create some kind of shape out of the "top line" and the imaginary line between the two points, and then clip the arrows, but this would still require determining the start and end points of the "top line".

% \pathload[*away]{start}{end}{saved path}[label][segments][text properties]
\NewDocumentCommand{\pathload}{s m m m o O{\distloadSegments} o}{
  \pgfmathsetmacro{\distloadInterval}{1/#6}
  \pgfmathsetmacro{\distloadIntervalBegin}{\distloadInterval}
  \pgfmathsetmacro{\distloadIntervalStep}{\distloadInterval*2}
  \pgfmathsetmacro{\distloadIntervalEnd}{1-\distloadInterval}

  \path[name path=pathload][use path=#4]; % coordinate at (start of path) (pathloadA)  coordinate at (end of path) (pathloadB);
  % It works if I manually input the path, but that kind of defeats the purpose of this macro.
  % \path[save path=\pathLoad, name path=pathload] (0, 1) coordinate (pathloadA) .. controls (1, 4) .. (4, 2) .. controls (6, 3) .. (10, 2) coordinate (pathloadB);

  \coordinate (distloadA1) at ($ (#2)!\distloadDistance!90:(#3) $);
  \coordinate (distloadB1) at ($ (#3)!\distloadDistance!-90:(#2) $);

  % \draw[red] (pathloadA) -- ($(distloadA1)!(pathloadA)!(distloadB1)$);
  % \draw[red] (pathloadB) -- ($(distloadA1)!(pathloadB)!(distloadB1)$);

  % Two passes are probably needed. The first collects the intersections, the second draws the "force lines"?
  % \foreach \i in {\distloadIntervalBegin,\distloadIntervalStep,...,\distloadIntervalEnd}
  %   \draw [green] ($(pathloadA)!\i!(pathloadB)$) -- ($(distloadA1)!\i!(distloadB1)$);

  \draw[blue, ultra thick][use path=#4];

  % Once the force lines are drawn, I can add nodes to label it using code from \distload and other macros I've written
}

Finally, the actual document demonstrating the above macros:

\begin{document}
\section{Distributed loads}
\begin{tikzpicture}
  % Uniformly distributed load
  \coordinate (node-A) at (0, 0);
  \coordinate (node-B) at (5, 0);
  \draw[very thick, gray] (node-A) -- (node-B);
  \distload{node-A}{node-B}[Force $A-B$]

  % Linearly increasing load
  \coordinate (node-C) at (0, -2);
  \coordinate (node-D) at (7, -2);
  \draw[very thick, gray] (node-C) -- (node-D);
  \distload{node-C}{node-D}[Force $C-D$][0mm][15mm][4]

  % Demonstrating all the options
  \coordinate (node-E) at (0, -5);
  \coordinate (node-F) at (8, -5);
  \draw[very thick, gray] (node-E) -- (node-F);
  \distload*{node-E}{node-F}[BIG FORCE][20mm][4mm][10][node font=\bfseries\huge]

  % Demonstrating ALL THE POSSIBILITIES
  \coordinate (node-G) at (2, -12);
  \coordinate (node-H) at (6, -6);
  \draw[very thick, gray] (node-G) -- (node-H);
  \distload*{node-G}{node-H}[why you do this thing?][20mm][-10mm][15][red, node font=\itshape\large]
\end{tikzpicture}

\section{Loads along predefined path}
\begin{tikzpicture}
  \coordinate (node-I) at (0, 0);
  \coordinate (node-J) at (10, 0);

  \path[save path=\pathLoad] (0, 1) .. controls (1, 4) .. (4, 2) .. controls (6, 3) .. (10, 0);

  \draw[very thick, gray] (node-I) -- (node-J);
  \pathload{node-I}{node-J}{\pathLoad}[Wiggle]

  % \draw[blue, ultra thick][use path=\loadEdge];
  % \draw[white, dashed][use path=\pathLoad];
\end{tikzpicture}
\end{document}

The above code produces the following result:distributed loads

Assuming I'm not barking up the X-Y Problem Tree, I've basically got three main problems to solve:

  1. Find the maximum perpendicular distance between the two paths, then create invisible force lines (i.e. \paths) that can be used to find the intersections
  2. Draw the force lines between the "beam" and the wobbly line
  3. Clip some of those force lines if the wobbly line is too close to the beam for the full arrow to be drawn

TikZ/PGF doesn't appear to have a means to extract the information I want, namely the endpoints and local maxima of a predefined path. At this stage, I'm not going to worry about any sanity checks to ensure that the wobbly path is actually "above" the line from A to B.

9
  • Take a look at this question – Nico Feb 5 '20 at 14:32
  • 1
    your code seems a bit complicated! may this help? \documentclass{article} \usepackage{tikz} \begin{document} \begin{tikzpicture}[>=stealth] \def\xa{0} \def\xb{10} \def\n{20} % number of arrows \def\pathLoad{(\xa, 1) .. controls (1, 4) .. (4, 2) .. controls (6, 3) .. (\xb, 0)} \pgfmathsetmacro{\d}{(\xb-\xa)/\n} \begin{scope} \clip \pathLoad-|(\xa,1); \foreach \i in {0,...,\n} \draw[<-,shorten <=.5pt] (\d*\i+.2,0)--+(90:4); \end{scope} \draw[blue,thick] \pathLoad; \draw[thick,top color=cyan,bottom color=gray] (\xa,0) rectangle (\xb,-.5); \end{tikzpicture} \end{document} – Black Mild Feb 5 '20 at 14:35
  • @BlackMild The reason for the code complexity is that I want to come up with a general solution for this problem that can be made into a macro (or perhaps a TikZ library) that can be used over and over again. Hence the variety of examples with the \distload macro derived from stanli. – Robbie Feb 5 '20 at 14:46
  • 1
    @Robbie So just use \clip to solve your problem – Black Mild Feb 5 '20 at 14:52
  • 2
    The problem can be very easily solved with intersections and tips=proper or tips=on proper draw. Your code seems exceedingly complicated to me. Anyone trying to answer this has to first look up all the arguments to understand what all the \IfBooleanTF and so on really do. – user194703 Feb 5 '20 at 16:50
3

Maybe this goes in the right direction.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{arrows.meta}
\usetikzlibrary{bending}
\usetikzlibrary{calc}
\usetikzlibrary{intersections}

% TikZ Styles
\tikzset{
  force/.style={draw, very thick, arrows = {-Stealth[bend]}},  % Force with arrow tip at end
  force'/.style={force, arrows = {Stealth[bend]-}},   % Force with arrow tip at start 
  force label/.style={inner sep=1pt},
}

\tikzset{
   pics/path load/.style={code={
  \tikzset{path load/.cd,#1}
  \def\pv##1{\pgfkeysvalueof{/tikz/path load/##1}} 
  \pgfmathsetmacro{\distloadInterval}{1/\pv{segments}}
  \pgfmathsetmacro{\distloadIntervalBegin}{\distloadInterval}
  \pgfmathsetmacro{\distloadIntervalStep}{\distloadInterval*2}
  \pgfmathsetmacro{\distloadIntervalEnd}{1-\distloadInterval}
  \foreach \X in {\distloadIntervalBegin,\distloadIntervalStep,...,\distloadIntervalEnd}
   { 
    \edef\temp{\noexpand\path[name path=vert]
     ($\pv{start}!\X!\pv{end}$) coordinate (vertaux)    -- 
     (vertaux|-current bounding box.north);} 
    \temp
    \path[name intersections={of=vert and \pv{path}},draw,force]
     (intersection-1) -- (vertaux);}
    }},path load/.cd,path/.initial=,start/.initial=,end/.initial=,label/.initial=,
    segments/.initial=5}

\begin{document}

\section{Loads along predefined path}
\begin{tikzpicture}[tips=on proper draw]
  \coordinate (node-I) at (0, 0);
  \coordinate (node-J) at (10, 0);

  \path[name path=pathLoad,save path=\pathLoad,draw,very thick,blue] (0, 1) .. controls (1, 4) .. (4, 2) .. controls (6, 3) .. (10, 0);

  \draw[very thick, gray] (node-I) -- (node-J);

  \pic{path load={path=pathLoad,start={(node-I)},end={(node-J)}}};
  \draw[white, dashed][use path=\pathLoad];
\end{tikzpicture}
\end{document}

enter image description here

ADDENDUM: Here is a complicated way of extracting the first and last coordinate from a saved path. It is not used above but may eventually be useful for something.

\documentclass[tikz,border=3mm]{standalone}
\makeatletter 
\tikzset{
  first and last coordinates of/.code={
  \begingroup
   \def\pgfsyssoftpath@movetotoken##1##2{%
    \pgf@xa=##1%
    \pgf@ya=##2%
   }%
   \def\pgfsyssoftpath@curvetotoken##1##2{%
    \pgf@xb=##1%
    \pgf@yb=##2%
   }%
   \def\pgfsyssoftpath@curvetosupportatoken##1##2{%
    \pgf@xb=##1%
    \pgf@yb=##2%
   }%
   \def\pgfsyssoftpath@curvetosupportbtoken##1##2{%
    \pgf@xb=##1%
    \pgf@yb=##2%
   }%
   \def\pgfsyssoftpath@linetotoken##1##2{%
    \pgf@xb=##1%
    \pgf@yb=##2%
   }%
   #1
   \tikzset{insert path={(\the\pgf@xa,\the\pgf@ya) coordinate (first)
   (\the\pgf@xb,\the\pgf@yb) coordinate (last)}}
  \endgroup}
}
\makeatother
\begin{document}
\begin{tikzpicture}
 \path[save path=\pathA]
  (0, 1) .. controls (1, 4) .. (4, 2) .. controls (6, 3) .. (10, 0);
 \draw[first and last coordinates of=\pathA,blue][use path=\pathA]
  (first) node[circle,fill,red]{} (last) node[circle,fill,red]{};
\end{tikzpicture}
\end{document}
6
  • This looks promising. Is there any advantage to using a pic instead of a macro? All of the rest of my structural analysis drawings use macros to draw the various things; I've got commands for point loads, various types of support, beams, dimensioning, etc. I am planning on replacing all the settings macros with pgfkeys in the next version. – Robbie Feb 6 '20 at 6:40
  • 1
    @Robbie I like pics and pgf keys much better. When one says \pic{path load={path=pathLoad,start={(node-I)},end={(node-J)}}}; then the order does not matter, for me it is way easier to fit the thing with parameters than in commands where I need to recall the order/usage. Also you can add all the usual parameters very easily, try e.g. \pic[dashed,red]{path load={path=pathLoad,start={(node-I)},end={(node-J)}}}; – user194703 Feb 6 '20 at 6:49
  • It did feel like a bit of a hack to get additional parameters into the macros that I wrote. Currently the only customisation permitted are node properties, primarily for tweaking the text. Using macros along the lines of \command[*]{start}{end}[options=pgfkeys] would be much simpler. I haven't looked into it, but there must be a simple way to turn an o-type argument into pgfkeys and their values. – Robbie Feb 6 '20 at 7:02
  • 1
    @Robbie BTW, I had found a method of extracting the first and last coordinate of a saved path and just added it. There may well be a simpler way. It does not work for arbitrary paths either, rather, it will fail if the path is interrupted. – user194703 Feb 6 '20 at 7:14
  • That's amazing! That's exactly what I was trying to figure out how to do. Thank you! It might not be absolutely necessary for the solution you provided earlier, but I'll be able to find a use for it. – Robbie Feb 6 '20 at 9:42

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.