2

I'd like to generate triangles and other shapes by length or angle with according labels to display exam-like questions.

My issue is not the math itself but the selection of logic packages and the evaluation of equations before the tikz picture. \ifthenelse seems useful and readable. I still need to calculate the coordinates/length as variables though. Maybe some other 'calculating environment'?

The goal would be something like:

\newkeycommand\triangleSAS[a,b,c,alpha,beta,gamma][1]{

\def \a {\commandkey{a}} \def \b {\commandkey{b}} \def \c {\commandkey{c}} \def \aalpha {\commandkey{alpha}} \def \abeta {\commandkey{beta}} \def \agamma {\commandkey{gamma}}

\ifthenelse{\not\equal{\b}{} \AND \not\equal{\c}{} \AND \not\equal{\aalpha}{}} % b,alpha,c {

\def \Cx {\bcos(\aalpha)} %error \def \Cy {\bsin(\aalpha)} % like this? \tikzmath{\Cx={\bcos(\aalpha)}; \Cy={\bsin(\aalpha)}}

} { not }

\begin{tikzpicture}[scale=1.0]

\tikzmath{\a=2; \b=4; \c=5; \aalpha=5;}
\def \labela {3 cm}
\def \labelb {2,7 cm}
\def \labelc {d}


% \coordinate [label=left:$A$] (A) at (0,0); % \coordinate [label=right:$B$] (B) at (\c,0); % \coordinate [label=above:$C$] (C) at (\Cx,\Cy); % error % \draw (A) -- node[sloped,below] {#6} (B) -- node[sloped,above] {a} (C) -- node[sloped, above] {#4} (A); %sloped \end{tikzpicture}

RootRaven
  • 249
  • 1
    Could you tell us what the use case is? It might be easier to come up with a helpful solution. Why do you add the [tikz-pgf] tag if there is no TikZ in your question? Is metapost a specific requirement here or would it also be okay to use TikZ? Neither TikZ nor metapost require LuaLaTeX per se. – Jasper Habicht Sep 12 '23 at 12:10
  • I'd prefer pure tikz if possible but i assumed these calculations require Lua. I am looking for a way to generate random shapes with labels. – RootRaven Sep 12 '23 at 12:19
  • I think you can do the calculations with TikZ as well. The question is rather, how to logically approach this? A triangle is defined by either the lengths of all three sides, or with the length of two sides and an angle, or with the length of only one side and two angles, if I am not mistaken. But you would need to somehow check this beforehand ... So, while this is quite straight-forward for a rectangle, it is a bit tricky for a triangle, I'd say ... – Jasper Habicht Sep 12 '23 at 13:06
  • That is no problem. The function can either check for the given information with some ifelse package or I just create individual functions for each variation of given information. Tikz evaluates everything within {} brackets (e.g. {3*sin(30)}), that should make the drawing easy (I did not know that). The tricky part will be the adjusted labeling. – RootRaven Sep 12 '23 at 13:33
  • While @JasperHabicht's definition of triangles isn't wrong, they only define the triangle without location or rotation. Assuming only one shape per diagram we can forget the location aspect but it still leaves us the rotation. The same applies to the rectangle. Will we assume one side to be horizontal? Do you have the logic/the algorithm already written out? – Qrrbrbirlbel Sep 12 '23 at 14:41
  • 2
    I mean there are 64 possibilites whether six values (three sides and three angles) are given or not, including one where more is given than needed or where not enough is given. It would be easier, if you limit the input or specify which of the many cases it is you input. The math of it all is secondary. That's the least of your problems. – Qrrbrbirlbel Sep 12 '23 at 15:09
  • The math is not the issue, the tools are. I don't know which packages and commands to use for the variable and logical branches. After tinkering with \NewDocumentCommand, I settled with \newkeycommand. It can check for arguments via \ifthenelse{\not\equal{\b}{} \AND \not\equal{\c}{} \AND \not\equal{\aalpha}{}}. I don't know how to calculate the coordinates before the tikz picture though. – RootRaven Sep 13 '23 at 13:19
  • 1
    I'd strongly advice against the usage of keycommand. If you want to use a key=value implementation either use ltkeys (the key=values built into LaTeX, also known as l3keys if you're using the L3 programming language), expkv (I'm the author, there is expkv-cs which is reasonably similar to keycommand, but more stable), or (since you're using TikZ) pgfkeys. – Skillmon Sep 21 '23 at 15:02
  • 1
    Also, you're destroying LaTeX's handling of UTF8 in non-Unicode engines with your \defs, at least put them in a group, better yet avoid one-letter variable names, those are most likely taken already, if you're unsure use \newcommand before the first \def to "initialise" them as your names. – Skillmon Sep 21 '23 at 18:05
  • @Skillmon Doesn't that block myself from using the command multiple times too? "\a already defined" No matter what name used? What is the disadvantage of keycommand? I find the syntax quiet beginner friendly. – RootRaven Sep 21 '23 at 19:46
  • 2
    @RootRaven you use \newcommand\foo{} once in the preamble to make sure that your name doesn't conflict, and then use \def\foo{} inside your macro. Or you scope your definitions to make sure that they don't break anything else. The first seems easier, imho. – Skillmon Sep 21 '23 at 19:49

2 Answers2

4

64 possiblities

As I said in my comment, there are 64 possibilities between six values being set or not set. Of those

  • 22 have less than three parameters given (not solveable for triangles),
  • 22 have more than three parameters given (“overdefined”) and
  • 20 have exactly three parameters given (where one of those is the case of only three angles given which is also not solvable).

PGFKeys instead of a big If-Then-Else tree

In the code below, a .list powered loop is going through the value-keys a, b, c, alpha, beta and gamma building another value that contains either a 0 or 1 for each of those parameters whether they are given (1) or not (0).

However, it will also count down from 3 and setting a parameter to 0 even if it is given when already three parameters were found to be given.

Afterwards, we have a resulting value of six digits of which up to three digits are a 1. If it contains less than three, no solution can be calculated (and we will recognize this and issue an error Missing input. – via a node, though).

Not defined = not solvable

This sequence of digits is then tried as a key name (/tikz/my shapes/triangle/?????? to be precise). If this is successfull, meaning that key was defined it is assumed that that key successfully defined coordinates A, B and C and then will draw the triangle via /tikz/my shapes/triangle/path={<a>}{<b>}{<c>}{<alpha>}{<beta>}{<gamma>} where only those parameters are setup that were used in the calculation.

This means if you ask for

\tikzTriangle{a=3, b=4, c=5, alpha=30, beta=60, gamma=210}

it will find a, b and c to be set, will ignore and unset alpha, beta and gamma and then will ask triangle/111000 = {3}{4}{5}{}{}{} to define the coordinates. Since this is defined, it will then draw the path via my shapes/triangle/path = {3}{4}{5}{}{}{}, meaning the triangle/path key can find out that its three last parameters are empty and does not draw any angles, not even with the wrong values 30, 60 or the nonsensical 210:
enter image description here

Math

All you have to do (and what I did already) is provide the calculations for the nineteen valid input combinations with three given parameters.

For this, use the key

/tikz/my shapes/utils/new triangle={??????}{<path operations>}

where the <path operations> use the values #1 to #6 (depending on the 1s that are set in ?????? to define the coordinates A, B (preset to be at the origin) and C
or

/tikz/my shapes/utils/new triangle'={??????}{XXXXXX}{{<a>}{<b>}{<c>}{<alpha>}{<beta>}{<gamma>}}

where XXXXXX is an existing triangle definition you transform ?????? into with the given values.

Assumptions

In the code below, I've defined all of them with the assumptions

  • coordinate B is at (0, 0);
  • the side a (BC) is horizontal
  • and the bottom side.

As much as possible I've tried to use TikZ's built-in functions to find other coordinates (relative coordinates, angle sums and the intersection of syntax). If those wouldn't work I have used the law of cosines or the law of sines

Examples

Under the given assumptions, the easiest case is 101010 (a/#1, c/ #3 and β/#5 given) where we can use the given parameters without any transformation or calculation:

  utils/new triangle= {101010}{coordinate (C) at (right:{#1})
                               coordinate (A) at ({#5}:{#3})},

The case 111000 uses this case but first evaluates β via the law of cosines:

  utils/new triangle'={111000}{101010}{
    {#1}{#2}{#3}
    {#4}{acos(((#1)^2+(#3)^2-(#2)^2)/(2*(#1)*(#3)))}{#6}%
  },

We can see that β is acos[(a² + c² − b²)/(2ac)].

Even though, #4 and #6 is empty and #2 will be ignored by 101010 I still forward it, maybe it would be wise to not do this so that mistakes raise an error.

Notes

I don't like the angle pic very much since it doesn't take the size of the text in consideration but since this is not the main point of this question I'm using it to just have those values in the output.

Triangles are a middleground between too many cases to manually provide calculations and too trivial. For example, a rectangle has just one drawable case, the one where bot sides are given. Or are we counting diagonals? Or the angle of that diagonal? But then a rectangle is just two right triangles and we're back to the problem at hand.

Yes, this only uses PGFMath which doesn't provide precision to the 15th digit after the decimal separator … but who's going to notice? We never output the result of these calculations, we only use them to draw the triangle and for that it's enough.

Code

\documentclass[tikz]{standalone}
\usepackage{tikz}
\usetikzlibrary{angles, ext.misc}
\newcommand*\mSvo[1]{\pgfkeysvalueof{/tikz/my shapes/#1}}
\tikzset{my shapes/.cd,
  rotate/.style={/tikz/rotate={#1}}, % forward rotation
  a/.initial=, b/.initial=, c/.initial=,
  alpha/.initial=, beta/.initial=, gamma/.initial=,
  every unknown  side/.style={shape=coordinate}, % hide me
  every   known  side/.style={
    shape=rectangle, midway, node contents={$#1 = \mSvo{#1}$}},
  every unknown angle/.style={shape=coordinate}, % hide me
  every   known angle/.style={
    draw, pic text={$\csname #1\endcsname = \mSvo{#1}^\circ$},
    pic text options={fill=white, inner sep=+.1em, fill opacity=.5,
      text opacity=1, node font=\footnotesize}},
  utils/do angle alpha/.style={angle=B--A--C},
  utils/do angle  beta/.style={angle=C--B--A},
  utils/do angle gamma/.style={angle=A--C--B},
  every diagram/.style={angle radius=5mm, angle eccentricity=1},
  print/side/.style={insert path={
    node[node contents=, /utils/TeX/ifxempty={\mSvo{#1}}
      {my shapes/every unknown side={#1}}{my shapes/every known side={#1}}]}},
  print/angle/.style={insert path={
    pic[/utils/TeX/ifxempty={\mSvo{#1}}{my shapes/every unknown angle={#1}}
      {my shapes/every known angle={#1}}]{/tikz/my shapes/utils/do angle #1}}},
  print/corner/.style args={#1--#2--#3}{
    insert path={pic[angle radius=.7em, pic text={$#2$}]{angle=#1--#2--#3}}},
  every path/.style={draw, auto=right},
  triangle/path/.style n args={6}{% we've got everything setup, draw triangle
    insert path={(A) -- (B)   [my shapes/print/side=c]
                     -- (C)   [my shapes/print/side=a]
                     -- cycle [my shapes/print/side=b,
       my shapes/print/angle/.list={alpha, beta, gamma},
       my shapes/print/corner/.list={C--A--B, A--B--C, B--C--A}
     ]}},
  triangle/.style={%
    utils/test/.initial=, utils/counter/.initial=3,
    utils/test for/.list={a, b, c, alpha, beta, gamma},
    triangle/\mSvo{utils/test}/.try/.expanded=
      {\mSvo{a}}{\mSvo{b}}{\mSvo{c}}{\mSvo{alpha}}{\mSvo{beta}}{\mSvo{gamma}},
    /utils/exec={% if the previous .try was successful, draw triangle
      \ifpgfkeyssuccess
        \path[my shapes/every path,
          my shapes/triangle/path={\mSvo{a}}    {\mSvo{b}}   {\mSvo{c}}
                                  {\mSvo{alpha}}{\mSvo{beta}}{\mSvo{gamma}}];
      \else % otherwise issue error/warning
        \node[anchor=base west, /utils/TeX/ifnum={\mSvo{utils/counter}>0}
          {red, node contents=Missing input.}
          {green, node contents=Not implemented.}]; % or 000111
      \fi}},
  utils/test for/.style={
    /utils/TeX/ifnum={\pgfkeysvalueof{/tikz/my shapes/utils/counter}>0}
      {/utils/TeX/ifxempty=% if input #1 is empty, mark it as 0 (“false”)
        {\mSvo{#1}}{utils/test/.append=0}
                           % if input #1 contains something, mark it as 1 (“true”)
                           % and decrease counter
                   {utils/test/.append=1, utils/counter/.--, }}
      {utils/test/.append=0, #1=}},% until counter reaches 0
  utils/new triangle/.style 2 args={% new triangle setup
    triangle/#1/.code n args={6}{\coordinate (B) at (0,0) #2;}},
  utils/new triangle'/.style n args={3}{% triangle setup forwarding to other
    triangle/#1/.style n args={6}{triangle/#2=#3}},
%  utils/new triangle={000111}{}, % never works
  % c and two angles
  utils/new triangle ={001011}{coordinate (A) at ({#5}:{#3}) coordinate (C)
             at (intersection of B--right:1 and A--{[shift=({-(#6)}:1)]A})},
  utils/new triangle'={001101}{001011}{{#1}{#2}{#3}{#4}{180-(#4)-(#6)}{#6}},
  utils/new triangle'={001110}{001011}{{#1}{#2}{#3}{#4}{#5}{180-(#4)-(#5)}},
  % b and two angle
  utils/new triangle'={010011}{001011}
    {{#1}{#2}{(#2)*sin(#6)/sin(#5)}{#4}{#5}{#6}},
  utils/new triangle'={010101}{010011}{{#1}{#2}{#3}{#4}{180-(#4)-(#6)}{#6}},
  utils/new triangle'={010110}{010011}{{#1}{#2}{#3}{#4}{#5}{180-(#4)-(#5)}},
  % b, c and one angle
  utils/new triangle'={011001}{001011}
    {{#1}{#2}{#3}{#4}{asin((#2)/(#3)*sin(#6))}{#6}},
  utils/new triangle'={011010}{001011}
    {{#1}{#2}{#3}{#4}{#5}{asin((#3)/(#2)*sin(#5))}},
  utils/new triangle'={011100}{111000}
    {{sqrt((#2)^2+(#3)^2-2*(#2)*(#3)*cos(#4))}{#2}{#3}{#4}{#5}{#6}},
  % a and two angles
  utils/new triangle ={100011}{coordinate (C) at (right:{#1})
    coordinate(A)at(intersection of B--{#5}:1 and C--{[shift=({180-(#6)}:1)]C})},
  utils/new triangle'={100101}{100011}{{#1}{#2}{#3}{#4}{180-(#4)-(#6)}{#6}},
  utils/new triangle'={100110}{100011}{{#1}{#2}{#3}{#4}{#5}{180-(#4)-(#5)}},
  % a, c and one angle
  utils/new triangle'={101001}{100101}
    {{#1}{#2}{#3}{asin((#1)*sin(#6)/(#3))}{#5}{#6}},
  utils/new triangle ={101010}{coordinate (C) at (right:{#1})
                               coordinate (A) at ({#5}:{#3})},
  utils/new triangle'={101100}{100101}
    {{#1}{#2}{#3}{#4}{#5}{asin((#3)*sin(#4)/(#1))}},
  % a, b and one angle
  utils/new triangle={110001}{
    coordinate(C)at(right:{#1}) coordinate(A)at([shift=(C)]{{180-(#6)}:{#2}})},
  utils/new triangle'={110010}{100110}
    {{#1}{#2}{#3}{asin((#1)*sin(#5)/(#2))}{#5}{#6}},
  utils/new triangle'={110100}{100110}
    {{#1}{#2}{#3}{#4}{asin((#2)*sin(#4)/(#1))}{#6}},
  % a, b, c
  utils/new triangle'={111000}{101010}{{#1}{#2}{#3}
    {#4}{acos(((#1)^2+(#3)^2-(#2)^2)/(2*(#1)*(#3)))}{#6}},
}
\newcommand*\myTriangle[1]{%
  \tikzset{my shapes/every diagram,my shapes/.cd,#1,triangle}}
\newcommand*\tikzTriangle[1]{%
  \tikz[my shapes/every diagram]{\pgfqkeys{/tikz/my shapes}{#1,triangle}}}
\begin{document}
\tikz[
  column sep=5mm, row sep=5mm,
  rows/.style 2 args={
    @/.style={row    ##1/.append code=\pgfqkeys{/tikz/my shapes}{#2}},
    @/.list={#1}},
  cols/.style 2 args={
    @/.style={column ##1/.append code=\pgfqkeys{/tikz/my shapes}{#2}},
    @/.list={#1}},
  row 1/.style={anchor=base west}, column 1/.style={anchor=base east},
  cols={3, 5, 7, 9}{gamma=30}, rows={3, 5, 7, 9}{c=3},
  cols={4, 5, 8, 9}{beta =40}, rows={4, 5, 8, 9}{b=4},
  cols={6, 7, 8, 9}{alpha=50}, rows={6, 7, 8, 9}{a=5},
]\matrix{
            &[2em] \node{000}; & \node{001}; & \node{010}; & \node{011};
            &[2em] \node{100}; & \node{101}; & \node{110}; & \node{111};   \\[2em]
\node{000}; & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}
            & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}\\
\node{001}; & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}
            & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}\\
\node{010}; & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}
            & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}\\
\node{011}; & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}
            & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}\\[2em]
\node{100}; & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}
            & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}\\
\node{101}; & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}
            & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}\\
\node{110}; & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}
            & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}\\
\node{111}; & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}
            & \myTriangle{} & \myTriangle{} & \myTriangle{} & \myTriangle{}\\
};

\tikzTriangle{a=3, b=4, c=5} \tikzTriangle{rotate=30, a=3, beta=40, gamma=50} \end{document}

Output

enter image description here

Qrrbrbirlbel
  • 119,821
  • Thank you! The code fails on my end though. A simple triangle (side,angle,side) or a rectangle (a,b) would suffice. This pgf package looks really cryptic to a beginner. I was hoping to accomplish this with a simple adjustable python-like script. – RootRaven Sep 25 '23 at 08:34
0

Here is a working starting example. The calculator package and the xfp package allow trig evaluations. The \fpeval command seemed better to use in this case.

\triangleSAS[b=10,c=12.5,alpha=44, rotate=-10, la=a]

There is probably a more elegant way to accomplish this. The command seems to work fine but fails using random values like so:

\triangleSAS[b=10,c=12.5,alpha=\fpeval{randint(10,90)}, rotate=-10, la=a] "Use of \??? doesn't match its definition" I'll open another thread for this.

\NewExpandableDocumentCommand{\bettersquareroot}{O{2}m}{%
    \fpeval{round(sqrt(#2),#1)}%
}

\newkeycommand\triangleSAS[a,b,c,alpha,beta,gamma,la,lb,lc,lalpha,lbeta,lgamma,rotate=0,scale=1][1]{ \ifcommandkey{a}{a=\commandkey{a},}{} \ifcommandkey{b}{b=\commandkey{b},}{} \ifcommandkey{c}{c=\commandkey{c},}{} \ifcommandkey{alpha}{alpha=\commandkey{alpha},}{} \ifcommandkey{beta}{beta=\commandkey{beta},}{} \ifcommandkey{gamma}{gamma=\commandkey{gamma},}{}

\def \a {\commandkey{a}}
\def \b {\commandkey{b}}
\def \c {\commandkey{c}}
\def \aalpha {\commandkey{alpha}}
\def \abeta {\commandkey{beta}}
\def \agamma {\commandkey{gamma}}


\ifthenelse{\not\equal{\b}{} \AND \not\equal{\c}{} \AND \not\equal{\aalpha}{}} % b,alpha,c
{
    got b,alpha,c
    aalpha: \aalpha
    \def \Cx {\fpeval{\b*cosd(\aalpha)}}
    \def \Cy {\fpeval{\b*sind(\aalpha)}}
    \def \a {\fpeval{\bettersquareroot{\b^2+\c^2-2*\b*\c*cosd(\aalpha)}}}
    \def \abeta {\fpeval{round(asind(\fpeval{round(sind(\aalpha) * \b / \a,3)}),1)}}
    \def \agamma {\fpeval{round(asind(\fpeval{round(sind(\aalpha) * \c / \a,3)}),1)}} % 3 is max precision for asind (?)
}{}


\rotatebox{\commandkey{rotate}}{% \begin{tikzpicture}[scale=\commandkey{scale},x=1cm,y=1.0cm]%,cap=round,>=latex] % label provided? \def \labela {\ifcommandkey{la}{\commandkey{la}}{\a}} \def \labelb {\ifcommandkey{lb}{\commandkey{lb}}{\b}} \def \labelc {\ifcommandkey{lc}{\commandkey{lc}}{\c}}

    % mark right angle
    \def \labelalpha {\ifthenelse{\equal{\aalpha}{90}}{\cdot}{\aalpha\degree}}
    \def \labelbeta  {\ifthenelse{\equal{\abeta}{90}}{\cdot}{\abeta\degree}}
    \def \labelgamma {\ifthenelse{\equal{\agamma}{90}}{\cdot}{\agamma\degree}}

    % label provided? replace label
    \ifcommandkey{lalpha}{\def \labelalpha {\commandkey{lalpha}}}{}
    \ifcommandkey{lbeta}{\def \labelbeta {\commandkey{lbeta}}}{}
    \ifcommandkey{lgamma}{\def \labelgamma {\commandkey{lgamma}}}{}

    \coordinate [label=left:$A$] (A) at (0,0);
    \coordinate [label=right:$B$] (B) at (\c,0);
    \coordinate [label=above:$C$] (C) at (\Cx ,\Cy ); 
    \draw (A) -- node[sloped,below] {\labelc} (B) -- node[sloped,above] {\labela} (C) -- node[sloped, above] {\labelb} (A); 

    % angles
    \tkzMarkAngle(B,A,C)
    \tkzLabelAngle[pos=0.6](B,A,C){$\labelalpha$}
    \tkzMarkAngle(C,B,A)
    \tkzLabelAngle[pos=0.6](C,B,A){$\labelbeta$}
    \tkzMarkAngle(A,C,B) 
    \tkzLabelAngle[pos=0.6](A,C,B){$\labelgamma$}
  \end{tikzpicture}
 }

}

RootRaven
  • 249