5

I am trying to mimic a knob on an audio mixing desk in TikZ. So far I have the following:

knob

\begin{tikzpicture}
  \draw[ultra thick] (0,0) circle [radius=0.5];
  \foreach \angle/\label in
    {
      -45+0*270*0.25 / $0$,
      -45+1*270*0.25 / $-20$,
      -45+2*270*0.25 / $-40$,
      -45+3*270*0.25 / $-60$, 
      -45+4*270*0.25 / $-\infty$
    }
  {
    \draw +(\angle:0.25) -- +(\angle:0.5);
    \draw (\angle:0.85) node {\label};
  }
  \draw[ultra thick] (0,0) -- +(45:0.5);
\end{tikzpicture}

Now I would like to support generic scales with arbitrary labels and number of ticks:

  • All that I should need to do is to pass TikZ a list of labels that I want on my knob. In the example above: {$0$, $-20$, $-40$, $-60$, $-\infty$}.

  • The code should then automatically draw the ticks and labels at equal distances between -45 and 225 degrees (i.e., the positions of $0$ and $-\infty$ in the example above). This will involve deriving the length of the passed list.

  • Ideally, I'd like to specify the knob's current position by reference to the scale on the labels (e.g., to -60), rather than by specifying the angle (45 degrees in the example above). This might be tricky:

    • On the one hand, I need the labels to be independent of the angle (e.g., $\infty$ symbol above)
    • On the other hand, I'd like to be able to set the knob to intermediate positions such as -70 (in relation to the label scale), in which case the code should automatically interpolate and derive the corresponding angle.

Any help would be most appreciated.

  • 1
    You may be interested in Generate analog clock with numbered face. BTW what is the starting and end point in terms of angles? – user11232 Oct 13 '13 at 22:04
  • One problem with your specs: how are you supposed to interpolate between -\infty and -60? – jub0bs Oct 13 '13 at 22:15
  • Thanks for the hint! As hinted above, the start and end angles can be static at -45 and 225 degrees, respectively. – Florian H. Oct 13 '13 at 22:15
  • Jubobs, good point. I guess for the sake of simplicity, -\infty can be assumed as -80 here (-80dB being very close to silence on a mixer anyway). – Florian H. Oct 13 '13 at 22:19
3

I think this is sort of what you want, though some tikz changes might improve the appearance if the labels get too wide. The syntax is

\drawdial{0, -20, -40, -60, -\infty}{-30}

where the first argument is a CSV list of the dial readings and the second argument is the reading (not the angle) of the dial's pointer. The dial's pointer location is based on extrapolating the change in value between the first two given arguments, so things could go awry if you don't specify equal increments between adjacent dial values.

\documentclass{article}
\usepackage{tikz} 
\usepackage{etoolbox}
\usepackage{fp}
\makeatletter
\newcounter{ROWcellindex@}
\newtoggle{@doneROWreads}
\newcommand\setstackEOL[1]{%
  \ifstrempty{#1}{\def\SEP@char{ }}{\def\SEP@char{#1}}%
  \expandafter\define@processROW\expandafter{\SEP@char}%
}
\newcommand\define@processROW[1]{%
    \def\@processROW##1#1##2||{%
      \def\@preSEP{##1}%
      \def\@postSEP{##2}%
    }%
}
\newcommand\getargs[1]{%
  \togglefalse{@doneROWreads}%
  \edef\@postSEP{\unexpanded{#1}\expandonce{\SEP@char}}%
  \setcounter{ROWcellindex@}{0}%
  \whileboolexpr{test {\nottoggle{@doneROWreads}}}{%
    \stepcounter{ROWcellindex@}%
    \expandafter\@processROW\@postSEP||%
    \expandafter\ifstrempty\expandafter{\@postSEP}{%
      \toggletrue{@doneROWreads}%
    }{}%
    \csedef{arg\roman{ROWcellindex@}}{\expandonce{\@preSEP}}%
  }%
% \narg GIVES HOW MANY ROWS WERE PROCESSED
  \xdef\narg{\arabic{ROWcellindex@}}%
}
\makeatother
\setstackEOL{,}

\usepackage{ifthen}

\newcounter{index}
\newcommand\makearglist[2]{%
  \def\arglist{}%
  \getargs{#1}%
  \setcounter{index}{\narg}%
  \addtocounter{index}{-1}%
  \FPdiv\dTHETA{270}{\theindex}%
  \setcounter{index}{0}%
  \whiledo{\theindex<\narg}{%
    \FPmul\THETA{\theindex}{\dTHETA}%
    \stepcounter{index}%
    \def\thislabel{$\csname arg\roman{index}\endcsname$}%
    \edef\arglist{\arglist -45+\THETA / \thislabel}%
    \ifthenelse{\equal{\theindex}{\narg}}{}{\edef\arglist{\arglist,}}%
  }%
  \FPsub\pointdif{#2}{\argi}%
  \FPsub\DELTA{\argii}{\argi}%
  \FPdiv\NUMticks{\pointdif}{\DELTA}%
  \FPmul\DEGticks{\NUMticks}{\dTHETA}%
  \FPadd\POINTangle{-45}{\DEGticks}%
}

\newcommand\drawdial[2]{%
\makearglist{#1}{#2}%
\begin{tikzpicture}
  \draw[ultra thick] (0,0) circle [radius=0.5];
  \foreach \angle/\label in \arglist
  {
    \draw +(\angle:0.25) -- +(\angle:0.5);
    \draw (\angle:0.85) node {\label};
  }
  \draw[ultra thick] (0,0) -- +(\POINTangle:0.5);
\end{tikzpicture}%
}

\begin{document} 
\drawdial{0, -20, -40, -60, -\infty}{-30}

\drawdial{0, -20, -40, -\infty}{-30}

\drawdial{0, -20, -40, -60, -80, -\infty}{-30}
\end{document}

In these three examples, I always specify the dial to be pointing at a reading of -30. What I differ is how many numbers are on the dial face.

\drawdial{0, -20, -40, -60, -\infty}{-30}

\drawdial{0, -20, -40, -\infty}{-30}

\drawdial{0, -20, -40, -60, -80, -\infty}{-30}

enter image description here

Note: my CSV list parser adapted from egreg's answer to this question (\centering breaks fragile command (is there a fix?)) by removing the \ignorespaces and \unskip.

| improve this answer | |
4

I’d add some keys so that it easier to change the diagram later. For the typesetting, I use \pgfmathprintnumber (which we can further customize using the /tikz/amd/print number style that is already applied in the third line of

\path (amd@c) edge[amd/ticks/.try] node[at end, amd/node/.try=\amdAngle]
  {\ifnum\cnt=\totCnt\def\Val{-inf}\fi
    \pgfmathprintnumber[/tikz/amd/print number/.try]{\Val}}
  ++(\amdAngle:{\pgfkeysvalueof{/tikz/amd/circle radius}});

but it does not necessarily need to be defined due to the .try handler).

Two \foreach loops are used. The first one only counts the number of ticks and saves also the latest value, the second one actually draws the ticks and places the nodes. (If you rather would provide start value, end value and a tick increment, it would probably make things easier on both sides; or do you want to draw knobs with values like 0, sqrt(2), pi and 3^2?)

Anyway, the last value in the list is assumed to be close to -inf and typeset as such. As long as the first and the last value are the borders of the range, anything can come in the list between them.

Code

\documentclass[tikz]{standalone}
\tikzset{
  amd/.code=\pgfqkeys{/tikz/amd}{#1},
  amd={
    circle radius/.initial=+1cm,
    start angle/.initial=-45,
    end angle/.initial=225,
    knob at/.initial=0,
    circle/.style={ultra thick},
    node/.style={anchor={#1+180}},
    ticks/.style={shorten <=+.5cm},
    knob/.style={ultra thick}
  },
  declare function={
    amdAngle(\i,\n)=(\pgfkeysvalueof{/tikz/amd/end\space angle}-
      (\pgfkeysvalueof{/tikz/amd/start\space angle}))/(\n)*(\i)
      +\pgfkeysvalueof{/tikz/amd/start\space angle};
  }
}
\newcommand*\amd[2][]{%
\begin{scope}[amd={#1}]
  \draw[amd/circle/.try] (0,0) coordinate (amd@c) 
                               circle [radius=\pgfkeysvalueof{/tikz/amd/circle radius}];
  \foreach \totVal[remember=\totCnt, remember=\totVal, count=\totCnt from 0] in {#2} {}
  \foreach \iVal[count=\cnt from 0,
                evaluate={\Val=\iVal},
                evaluate={\amdAngle=amdAngle(\Val,\totVal)}] in {#2} {
    \path (amd@c) edge[amd/ticks/.try] node[at end, amd/node/.try=\amdAngle]
      {\ifnum\cnt=\totCnt\def\Val{-inf}\fi
        \pgfmathprintnumber[/tikz/amd/print number/.try]{\Val}}
      ++(\amdAngle:{\pgfkeysvalueof{/tikz/amd/circle radius}});
  }
  \path (amd@c) edge[amd/knob/.try] ++({amdAngle(\pgfkeysvalueof{/tikz/amd/knob at},
                                    \totVal)}:{\pgfkeysvalueof{/tikz/amd/circle radius}});
\end{scope}}
\begin{document}
\foreach \val in {0,-2,...,-80}{% typesets 41 pages!
\begin{tikzpicture}
\useasboundingbox[reset cm] (-1.8cm, -1.8cm) -- (+1.8cm, +1.8cm);
\amd[knob at=\val]{0, -20, -40, -60, -80}
\end{tikzpicture}}
\foreach \val in {0,-2,...,-80}{% typesets 41 pages!
\begin{tikzpicture}[
  rotate={-amdAngle(\val,-80)}, % not nice:
  amd/node/.style={sloped,rotate={-amdAngle(\val,-80)},allow upside down,right}]
\useasboundingbox[reset cm] (-1.8cm, -1.8cm) -- (+1.8cm, +1.8cm);
\amd[knob at=\val]{0, -20, -40, -60, -80}
\end{tikzpicture}}
\end{document}

Output

enter image description hereenter image description here

| improve this answer | |

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.