12

I have a 5-segment tikz picture that I want to include in a \newcommand that takes an argument that determines how many segments are filled in.

Here is my MWE:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{positioning,shapes}

\begin{document}
\begin{tikzpicture}[baseline=-0.5ex]
    \tikzstyle{every node}=[signal, signal to=east, draw=gray, minimum height=.9em, minimum width=2mm];
    \node (1) {};
    \node[signal from=west, right=0pt of 1] (2) {};
    \node[signal from=west, right=0pt of 2] (3)  {};
    \node[signal from=west, right=0pt of 3] (4) {};
    \node[signal from=west, signal to=0, right=0pt of 4] (5) {};
\end{tikzpicture}
\end{document}

It looks like this when compiled:

enter image description here

enter image description here

I have tried adding \def\myNumber{#1} to the \newcommand\myCommand[1] and added \ifnum\myNumber>0 fill=black \fi to the node argument, but I keep getting errors.

3
  • I was going to try this out and decided to call my test file tikzconditionalstyle.tex, only to discover that I already have a test file called tikzconditionalstyle.tex and it seems fairly relevant! That file was for the answer to tex.stackexchange.com/q/29048/86. Is that of any help to you? Jan 20, 2018 at 21:14
  • @LoopSpace I'm not seeing anything in that linked question that can help me. Thanks, though. Jan 20, 2018 at 23:00
  • 1
    I've put an example as an answer. I'm not particularly recommending it over the answers you already have, but more to show an alternative and to expand on my comment. Jan 21, 2018 at 0:08

5 Answers 5

12

enter image description here

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{positioning,shapes}

\begin{document}
\newcommand\zz[1]{%
\begin{tikzpicture}[baseline=-0.5ex]
    \tikzstyle{every node}=[signal, signal to=east, draw=gray, minimum height=.9em, minimum width=2mm];
    \node[fill=\ifnum#1>0 black\else white\fi] (1) {};
    \node[fill=\ifnum#1>1 black\else white\fi,signal from=west, right=0pt of 1] (2) {};
    \node[fill=\ifnum#1>2 black\else white\fi,signal from=west, right=0pt of 2] (3)  {};
    \node[fill=\ifnum#1>3 black\else white\fi,signal from=west, right=0pt of 3] (4) {};
    \node[fill=\ifnum#1>4 black\else white\fi,signal from=west, signal to=0, right=0pt of 4] (5) {};
\end{tikzpicture}}

\zz{0}

\zz{1}

\zz{2}

\zz{3}

\zz{4}

\zz{5}

\end{document}
3
  • 1
    May be you can shorten your macro to \tikz[baseline=-0.5ex]\path foreach \i in {0,...,4}{++(.5,0) node[signal, signal from={\ifnum\i>0 west\fi}, signal to={\ifnum\i<4 east\fi}, fill=\ifnum#1>\i black\else white\fi, draw=gray, minimum height=.9em, minimum width=4mm]{}}; ;)
    – Kpym
    Jan 21, 2018 at 22:28
  • @Kpym I could but that moves it further from the OPs original, and probably in the real document does not need all the versions together but rather one per page as a sort of progress bar Jan 21, 2018 at 23:51
  • 1
    I guess I should have said as much, but this is for an RPG character sheet to denote skill ranks. I'm developing a LaTeX package for homebrew material. I do like your one-liner, @Kpym and it appeals to my wanting to keep it DRY. With your change, I can much easier modify things when needed. But David Carlisle's answer was simple for me to understand and hit all the important notes. Jan 31, 2018 at 15:25
11

A simple approach using the key/val interface:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{positioning,shapes}

\newcommand{\mycommand}[2][]{
    \begin{tikzpicture}[
        draw=gray,
        baseline=-0.5ex,
        every node/.style={
            draw,
            signal, signal to=east,
            minimum height=.9em, minimum width=2mm
          },
        progress/.style={segment ##1/.style={fill}},
        progress/.list={#2},
        #1,
    ]

    \node[segment 1/.try] (1) {};
    \node[signal from=west, right=0pt of 1,segment 2/.try] (2) {};
    \node[signal from=west, right=0pt of 2,segment 3/.try] (3)  {};
    \node[signal from=west, right=0pt of 3,segment 4/.try] (4) {};
    \node[signal from=west, signal to=0, right=0pt of 4,segment 5/.try] (5) {};
    \end{tikzpicture}
}

\newcommand{\myprogress}[2][]{\mycommand[#1]{0,...,#2}}

\begin{document}
    \mycommand{}\par
    \mycommand{1,...,5}\par
    \mycommand[fill=red]{1,3,5}\par
    \mycommand[draw=blue]{2,...,5}\par
    \mycommand{4}\par
    \myprogress{4}\par
\end{document}

The \mycommand macro allows you to provide a list of segments to fill, the \myprogress one to just fill them up to the number provided. The optional argument is a regular tikz key/val list, so you can customize the colors very naturally by setting fill=color or draw=color.

preview

Explanation

The idea is to have a key segment N for each of the segments 1 to 5. Then the key is empty if there is nothing to do for that segment, or set to fill if the segment needs to be filled. So the nodes style now contain segment N but I added /.try so that TikZ simply ignores the key if it is not defined. This way we do not have to give trivial definitions for these keys when they should have no effect.

Now we want to set the segment N keys in a simple way, i.e. with a list syntax. First we provide a helper key progress=N that has the effect of setting segment N to fill. Then we use the utility /.list to execute the progress=K key for each item K of the list provided as argument (#2). Finally, we execute #1 as a key/val list so that keys in the optional argument can overwrite the current scope.

One final touch: we set the draw color outside of the every node style so that it is set for the whole pic as a default, the every node style now just requires the draw action without specifying a color, so that the actual color can be overwritten by the #1 argument if needed.

Generalisation

If you want to make the definition of the macro a little bit less ad-hoc, you can generalise it to N segments (here N is the first mandatory argument), and use chains to position the segments.

Note: I set node distance to -\pgflinewidth so that the borders are not doubled in width at the segment boundaries.

\usetikzlibrary{chains}

\newcommand{\mycommand}[3][]{
    \begin{tikzpicture}[
        draw=gray,
        baseline=-0.5ex,
        every node/.style={
            draw,
            signal, signal to=east,
            minimum height=.9em, minimum width=2mm
          },
        progress/.style={segment ##1/.style={fill}},
        progress/.list={#3},
        start chain,
        node distance = -\pgflinewidth,
        #1,
    ]

    \node[on chain,segment 1/.try] {};
    \ifnum#2>2
    \pgfmathsetmacro{\N}{#2-1}
    \foreach \K in {2,...,\N}{
        \node[on chain,signal from=west, segment \K/.try] {};
    }
    \fi
    \node[on chain,signal from=west, signal to=0,segment #2/.try] {};
    \end{tikzpicture}
}

preview2

4

And yet another proposal....

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{positioning,shapes,calc}
\newcounter{instances} % introduced to avoid giving two different nodes the same name
\newcommand{\mycommand}[1]{\stepcounter{instances}
    \ifnum#1=0
    \node (\theinstances-1) {};
    \else
    \node[fill=black] (\theinstances-1) {};
    \fi
    \foreach \i in {1,...,4}
    {
    \pgfmathtruncatemacro{\j}{\i+1}
    \pgfmathtruncatemacro{\k}{min(-\i+#1,1)*100}
    \node[signal from=west, right=0pt of \theinstances-\i,fill=black!\k!white]
    (\theinstances-\j) {};
    }
}   

\begin{document}
\begin{tikzpicture}[baseline=-0.5ex]
    \tikzstyle{every node}=[signal, signal to=east, draw=gray, minimum height=.9em, minimum width=2mm];
    \foreach \f in {0,...,5}
    {\begin{scope}[yshift=-\f cm]
    \mycommand{\f}
    \end{scope}}
\end{tikzpicture}
\end{document}

enter image description here

4

To expand a bit on my comment on the question, and to link it to Macro inside TikZ node properties, here's a solution using the ifthen package for the conditional. I also use a pic for the drawing and bury the conditional in a style.

\documentclass{article}
%\url{https://tex.stackexchange.com/q/411319/86}
\usepackage{ifthen}
\usepackage{tikz}
\usetikzlibrary{positioning,shapes}

\tikzset{
  filled signal style/.style 2 args={
    signal,
    signal to=east,
    draw=gray,
    minimum height=.9em,
    minimum width=2mm,
    fill if={#1>#2}{black}
  },
  fill if/.code 2 args={
    \ifthenelse{#1}{%
      \pgfkeysalso{fill=#2}
    }{
      \pgfkeysalso{fill=none}
    }
  },
  filled signal/.pic={
    \node[filled signal style={#1}{0}] (1) {};
    \node[filled signal style={#1}{1},signal from=west, right=0pt of 1] (2) {};
    \node[filled signal style={#1}{2},signal from=west, right=0pt of 2] (3)  {};
    \node[filled signal style={#1}{3},signal from=west, right=0pt of 3] (4) {};
    \node[filled signal style={#1}{4},signal from=west, signal to=0, right=0pt of 4] (5) {};
  }
}


\begin{document}
\begin{tikzpicture}[baseline=-0.5ex]
\pic (0,0) {filled signal=0};
\pic at (0,-1) {filled signal=1};
\pic at (0,-2) {filled signal=2};
\pic at (0,-3) {filled signal=3};
\pic at (0,-4) {filled signal=4};
\end{tikzpicture}
\end{document}

The ifthen package is not strictly required for this situation as we're just doing a numeric test, so this could instead be:

\documentclass{article}
%\url{https://tex.stackexchange.com/q/411319/86}
\usepackage{tikz}
\usetikzlibrary{positioning,shapes}

\tikzset{
  filled signal style/.style 2 args={
    signal,
    signal to=east,
    draw=gray,
    minimum height=.9em,
    minimum width=2mm,
    fill if={#1>#2}{black}
  },
  fill if/.code 2 args={
    \ifnum#1\relax
      \pgfkeysalso{fill=#2}
    \else
      \pgfkeysalso{fill=none}
    \fi
  },
  filled signal/.pic={
    \node[filled signal style={#1}{0}] (1) {};
    \node[filled signal style={#1}{1},signal from=west, right=0pt of 1] (2) {};
    \node[filled signal style={#1}{2},signal from=west, right=0pt of 2] (3)  {};
    \node[filled signal style={#1}{3},signal from=west, right=0pt of 3] (4) {};
    \node[filled signal style={#1}{4},signal from=west, signal to=0, right=0pt of 4] (5) {};
  }
}


\begin{document}
\begin{tikzpicture}[baseline=-0.5ex]
\pic (0,0) {filled signal=0};
\pic at (0,-1) {filled signal=1};
\pic at (0,-2) {filled signal=2};
\pic at (0,-3) {filled signal=3};
\pic at (0,-4) {filled signal=4};
\end{tikzpicture}
\end{document}

Both produce:

progress bar with signals

3

The answer below uses the wheelchart package, which I wrote.

The gap between the slices is obtained with the key gap polar.

Slice 1 has an arrow at the end but not at the start. Therefore the key slices end arrow{1}={1}{0} is used.

Slices 2, 3 and 4 have an arrow at the start and end. Therefore the key slices arrow{2,3,4}={1}{0} is used.

Slice 5 has an arrow at the start but not at the end. Therefore the key slices start arrow{5}={-1}{0} is used.

The style of the slices is determined by the key slices style. For the slices 0 till the argument of \myCommand, the style is specified with the key slices style{0,...,#1}. Here \WClistcolors refers to items in the list given to the key WClistcolors.

The number of slices is determined by the key total count.

The bar shape is obtained with the key xbar.

enter image description here

\documentclass[border=6pt,varwidth]{standalone}
\usepackage{wheelchart}
\newcommand{\myCommand}[1]{\begin{tikzpicture}
\wheelchart[
  gap polar=0.1,
  slices end arrow{1}={1}{0},
  slices arrow{2,3,4}={1}{0},
  slices start arrow{5}={-1}{0},
  slices style={fill=none,draw=gray,ultra thick},
  slices style{0,...,#1}={\WClistcolors,draw=gray,ultra thick},
  total count=5,
  xbar={7}{1},
  WClistcolors={green!50!black,green,yellow,orange,red}
]{}
\end{tikzpicture}}
\begin{document}
\myCommand{0}\\
\myCommand{1}\\
\myCommand{2}\\
\myCommand{3}\\
\myCommand{4}\\
\myCommand{5}
\end{document}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .