Before I go into too much detail on this question, the end result I want is something like this picture:
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:
Assuming I'm not barking up the X-Y Problem Tree, I've basically got three main problems to solve:
- Find the maximum perpendicular distance between the two paths, then create invisible force lines (i.e.
\path
s) that can be used to find the intersections - Draw the force lines between the "beam" and the wobbly line
- 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.
\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}
\distload
macro derived from stanli.\clip
to solve your problemtips=proper
ortips=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.