16

Disclaimer: I have decided to award a bounty on this question, because the solution suggested by Christian Hupfer works but is not fully satisfactory. In doing so, I rephrased this question so that potential answers might have a broader applicability. The fundamental issue remains however unchanged.


Context: I want to create hierarchically dependent PGF keys: e.g. keys sub1 and sub2 equals key main, unless they have been explicitely defined.

I'm looking for this because I want in my future package to have a key that sets characteristics (e.g. the color, or font) of each elements, but to be also able to locally define the characteristics of a single element.

Let's take an example to make things more intelligible. Say I have a macro that draws a tikzpicture constituted of three elements (A, B and C). I want to give the user full control for what regards colors.
The idea is that I have a global color, namely main color, that is applied to each element of the tikzpicture; here A, B, and C. main color has a default value (here, black) that can be overridden if the user specifies its value explicitly (e.g. main color = {150,150,150}).
However the user could also specify the color of each element x separately (e.g. color A = {255,0,0}). This local definition would then override any other set color for element x only.
The color of element x is thus determined as following:

color X = default main color / unless local main color is specified / unless local color X is specified.

I use PGF keys for that (since my project heavily rely on TikZ). This makes keys kind of "hierarchically dependent", as shown in following pattern:

main color <--- color A
             |
             |- color B
             |
             -- color C

(Note that I have several hierarchical levels in my real case.)

Question: How to manage this hierarchical keys system?


Below are information constituting the initial version of this question. I have updated answers based on Christian's comment.

Initial problem: My idea for implementing this behavior is the following pseudo-code:

main color/.default = {<value arbitraty defined by developer>}
main color/.code = {\setTheColor'main color'='#1'}
color A/.default = {\valueof{main color}}
color A/.code = {\setTheColor'color A'='#1'}
color B/.default = {\valueof{main color}}
color B/.code = {\setTheColor'color B'='#1'}
main color, color A, color B,

My issue is regarding how to define color A/.default: I've tried both color a/.default={\pgfvalueof{main color}}, and color b/.link={main color}, but it is not working as shown in the MWE below.

Updated problem: The solution suggested in comments by Christian Hupfer is to use /.forward to =. I have successfully implemented this solution in the answer I posted below. However, since I use main/.forward to=color x, this behavior override a local definition of color x that occured before defining main color. As I want to implement this hierarchical keys in a package, with several nesting levels, it would be definitively too restrictive to expect the user to give key=value pairs in the right order.


MWE: (implementing Christian's solution. Issue is on the penultimate line: B should be blue.)

enter image description here

\documentclass{article}
    \usepackage{xcolor}
    \usepackage{tikz}

    \pgfkeys{
        /example/.is family, /example,
        main color/.default={0,0,0},
        main color/.code={\definecolor{mymaincolor}{RGB}{#1}},
        main color/.forward to = /example/color a,  
        main color/.forward to = /example/color b,
        main color/.forward to = /example/color c,  
        color a/.code={\definecolor{mysubcolora}{RGB}{#1}},
        color b/.code={\definecolor{mysubcolorb}{RGB}{#1}},
        color c/.code={\definecolor{mysubcolorc}{RGB}{#1}},
    }

    \newcommand{\test}[1][]{%
        \pgfkeys{/example,
            main color,
            #1,
        }%
        \par
        \begin{tikzpicture}
            \node[fill = mysubcolora, circle, minimum height = 1cm,  text= white, font=\Large\sffamily\bfseries] at (0,0) {A};
            \node[fill = mysubcolorb, circle, minimum height = 1cm,  text= white, font=\Large\sffamily\bfseries] at (1,0) {B};
            \node[fill = mysubcolorc, circle, minimum height = 1cm,  text= white, font=\Large\sffamily\bfseries] at (2,0) {C};
        \end{tikzpicture}
        \par
    }
\begin{document}
    \begin{description}
        \item[\textbackslash test] no argument: circle A, B and C take default color = \emph{default} \texttt{main color} (black). 
            \test
        \item[\textbackslash test[color a=\{255,0,0\}{]} ] circle A: \emph{local} \texttt{color A} (red), circle B and C: no definition, so \emph{default} \texttt{main color}.
            \test[color a={255,0,0}]
        \item[\textbackslash test[main color=\{200,200,200\}{]}] circles A, B, and C's colors are overridden by \emph{local} \texttt{main color} (grey).
            \test[main color={200,200,200}]
        \item[\textbackslash test[main color=\{200,200,200\}, color b =\{0,0,255\}{]}] circles A, B, and C's colors are overridden by \emph{local} \texttt{main color} (i.e. the same than above), but circle's B color is ``over-overridden'' by \emph{local} \texttt{color B} definition (blue).
            \test[main color={200,200,200}, color b ={0,0,255}]
        \item[\textbackslash test[color b =\{0,0,255\}, main color=\{200,200,200\}{]}] Same behavior than above. However, the \emph{local} definition of \texttt{color B} is specified \emph{before} the \emph{local} definition of \texttt{main color}. In this case, suggested solution doesn't work. (circle B should be blue.)
            \test[color b ={0,0,255}, main color={200,200,200}]            
        \item[\textbackslash test]  no argument: circle A, B and C take default color = \emph{default} \texttt{main color} (black). (Testing if local color definition are really local.)
            \test
    \end{description}
\end{document}
  • Do you mean .forward to=? – user31729 May 8 '17 at 13:50
  • I think you could also use different families to initialize one from another one, but the true TikZ gurus will answer your question. – user31729 May 8 '17 at 14:01
  • As you might already know, TikZ deal with this kind of issue by every xxx. For example \zooset{every mammal/.try, every dog/.try, every golden retriever/.try}. Also, PGFplots will manually check /tikz/husky while /pgfplots/hulsky is undefined. – Symbol 1 May 10 '17 at 17:24
9
+100

You can use the handler .unknown to set up the fallback.

\documentclass[border=9,tikz]{standalone}
\usetikzlibrary{trees,shapes.geometric}
\begin{document}

\pgfkeys{
    %/hierarkey/.unknown/.code={\errmessage{Hierarkey Error: exhaustively searched, nothing found}},
    /hierarkey/.unknown/.style={/hierarkey},
    /hierarkey/A/.unknown/.style={/hierarkey/A},
    /hierarkey/A/1/.unknown/.style={/hierarkey/A/1},
    /hierarkey/A/1/i/.unknown/.style={/hierarkey/A/1/i},
    /hierarkey/A/1/ii/.unknown/.style={/hierarkey/A/1/ii},
    /hierarkey/A/1/iii/.unknown/.style={/hierarkey/A/1/iii},
    /hierarkey/A/2/.unknown/.style={/hierarkey/A/2},
    /hierarkey/A/2/i/.unknown/.style={/hierarkey/A/2/i},
    /hierarkey/A/2/ii/.unknown/.style={/hierarkey/A/2/ii},
    /hierarkey/A/2/iii/.unknown/.style={/hierarkey/A/2/iii},
    /hierarkey/A/3/.unknown/.style={/hierarkey/A/3},
    /hierarkey/A/3/i/.unknown/.style={/hierarkey/A/3/i},
    /hierarkey/A/3/ii/.unknown/.style={/hierarkey/A/3/ii},
    /hierarkey/A/3/iii/.unknown/.style={/hierarkey/A/3/iii},
    /hierarkey/B/.unknown/.style={/hierarkey/B},
    /hierarkey/B/1/.unknown/.style={/hierarkey/B/1},
    /hierarkey/B/1/i/.unknown/.style={/hierarkey/B/1/i},
    /hierarkey/B/1/ii/.unknown/.style={/hierarkey/B/1/ii},
    /hierarkey/B/1/iii/.unknown/.style={/hierarkey/B/1/iii},
    /hierarkey/B/2/.unknown/.style={/hierarkey/B/2},
    /hierarkey/B/2/i/.unknown/.style={/hierarkey/B/2/i},
    /hierarkey/B/2/ii/.unknown/.style={/hierarkey/B/2/ii},
    /hierarkey/B/2/iii/.unknown/.style={/hierarkey/B/2/iii},
    /hierarkey/B/3/.unknown/.style={/hierarkey/B/3},
    /hierarkey/B/3/i/.unknown/.style={/hierarkey/B/3/i},
    /hierarkey/B/3/ii/.unknown/.style={/hierarkey/B/3/ii},
    /hierarkey/B/3/iii/.unknown/.style={/hierarkey/B/3/iii},
    /hierarkey/C/.unknown/.style={/hierarkey/C},
    /hierarkey/C/1/.unknown/.style={/hierarkey/C/1},
    /hierarkey/C/1/i/.unknown/.style={/hierarkey/C/1/i},
    /hierarkey/C/1/ii/.unknown/.style={/hierarkey/C/1/ii},
    /hierarkey/C/1/iii/.unknown/.style={/hierarkey/C/1/iii},
    /hierarkey/C/2/.unknown/.style={/hierarkey/C/2},
    /hierarkey/C/2/i/.unknown/.style={/hierarkey/C/2/i},
    /hierarkey/C/2/ii/.unknown/.style={/hierarkey/C/2/ii},
    /hierarkey/C/2/iii/.unknown/.style={/hierarkey/C/2/iii},
    /hierarkey/C/3/.unknown/.style={/hierarkey/C/3},
    /hierarkey/C/3/i/.unknown/.style={/hierarkey/C/3/i},
    /hierarkey/C/3/ii/.unknown/.style={/hierarkey/C/3/ii},
    /hierarkey/C/3/iii/.unknown/.style={/hierarkey/C/3/iii},
}
\pgfkeys{
    /hierarkey/.style=draw,
    /hierarkey/A/.style={draw=red},
    /hierarkey/A/2/.style={fill=orange},
    /hierarkey/A/3/i/.style={line width=3,draw=cyan,fill=magenta},
    /hierarkey/B/.style={draw,circle},
    /hierarkey/B/1/iii/.style={draw,shape=diamond},
    /hierarkey/B/3/.style={draw,rounded corners},
    /hierarkey/C/.style={rotate=30},
    /hierarkey/C/2/ii/.style={font=\bfseries},
    /hierarkey/C/3/.style={font=\ttfamily},
}

\tikz[
    grow=right,
    level 1/.style={sibling distance=-180,},
    level 2/.style={sibling distance=-60},
    level 3/.style={sibling distance=-20}]{
    \node[/hierarkey]{root}
        child{node[/hierarkey/A]{A}
            child{node[/hierarkey/A/1]{A1}
                child{node[/hierarkey/A/1/i]{A1i}}
                child{node[/hierarkey/A/1/ii]{A1ii}}
                child{node[/hierarkey/A/1/iii]{A1iii}}
            }
            child{node[/hierarkey/A/2]{A2}
                child{node[/hierarkey/A/2/i]{A2i}}
                child{node[/hierarkey/A/2/ii]{A2ii}}
                child{node[/hierarkey/A/2/iii]{A2iii}}
            }
            child{node[/hierarkey/A/3]{A3}
                child{node[/hierarkey/A/3/i]{A3i}}
                child{node[/hierarkey/A/3/ii]{A3ii}}
                child{node[/hierarkey/A/3/iii]{A3iii}}
            }
        }
        child{node[/hierarkey/B]{B}
            child{node[/hierarkey/B/1]{B1}
                child{node[/hierarkey/B/1/i]{B1i}}
                child{node[/hierarkey/B/1/ii]{B1ii}}
                child{node[/hierarkey/B/1/iii]{B1iii}}
            }
            child{node[/hierarkey/B/2]{B2}
                child{node[/hierarkey/B/2/i]{B2i}}
                child{node[/hierarkey/B/2/ii]{B2ii}}
                child{node[/hierarkey/B/2/iii]{B2iii}}
            }
            child{node[/hierarkey/B/3]{B3}
                child{node[/hierarkey/B/3/i]{B3i}}
                child{node[/hierarkey/B/3/ii]{B3ii}}
                child{node[/hierarkey/B/3/iii]{B3iii}}
            }
        }
        child{node[/hierarkey/C]{C}
            child{node[/hierarkey/C/1]{C1}
                child{node[/hierarkey/C/1/i]{C1i}}
                child{node[/hierarkey/C/1/ii]{C1ii}}
                child{node[/hierarkey/C/1/iii]{C1iii}}
            }
            child{node[/hierarkey/C/2]{C2}
                child{node[/hierarkey/C/2/i]{C2i}}
                child{node[/hierarkey/C/2/ii]{C2ii}}
                child{node[/hierarkey/C/2/iii]{C2iii}}
            }
            child{node[/hierarkey/C/3]{C3}
                child{node[/hierarkey/C/3/i]{C3i}}
                child{node[/hierarkey/C/3/ii]{C3ii}}
                child{node[/hierarkey/C/3/iii]{C3iii}}
            }
        }
    ;
}
\end{document}

Roughly speaking, /foo/bar/.unknown/.style={/foo/bar} will ask pgfkeys to search the parent folder.

For example

  • /hierarkey becomes draw, as being set in the second paragraph.
  • /hierarkey/A becomes draw=red, as being set in the second paragraph.
  • /hierarkey/A/1 is unknown.
    • So pgfkeys test if /hierarkey/A is known. Yes it is; it is draw=red
  • /hierarkey/A/1/i is unknown.
    • So pgfkeys tests if /hierarkey/A/1 is known. No it is not.
      • So pgfkeys tests if /hierarkey/A is known. Yes it is; it is draw=red.
  • /hierarkey/A/1/ii is unknown.
    • So pgfkeys tests if /hierarkey/A/1 is known. No it is not.
      • So pgfkeys tests if /hierarkey/A is known. Yes it is; it is draw=red.

etc...



This is lengthy! I want shorter code

Well... it turns out that there is a fallback of fallback. That is, the default behavior when .unknown is unknown.

\documentclass[border=9,tikz]{standalone}
\usetikzlibrary{trees,shapes.geometric}
\begin{document}

\pgfkeys{/handlers/.unknown/.style={\pgfkeyscurrentpath}}

\pgfkeys{
    /hierarkey/.cd,
    .style=draw,
    A/.style={draw=red},
    A/2/.style={fill=orange},
    A/3/i/.style={line width=3,draw=cyan,fill=magenta},
    B/.style={draw,circle},
    B/1/iii/.style={draw,shape=diamond},
    B/3/.style={draw,rounded corners},
    C/.style={rotate=30},
    C/2/ii/.style={font=\bfseries},
    C/3/.style={font=\ttfamily},
}

\tikz[
    grow=right,
    level 1/.style={sibling distance=-180,},
    level 2/.style={sibling distance=-60},
    level 3/.style={sibling distance=-20}]{
    \node[/hierarkey]{root}
        child{node[/hierarkey/A]{A}
            child{node[/hierarkey/A/1]{A1}
                child{node[/hierarkey/A/1/i]{A1i}}
                child{node[/hierarkey/A/1/ii]{A1ii}}
                child{node[/hierarkey/A/1/iii]{A1iii}}
            }
            child{node[/hierarkey/A/2]{A2}
                child{node[/hierarkey/A/2/i]{A2i}}
                child{node[/hierarkey/A/2/ii]{A2ii}}
                child{node[/hierarkey/A/2/iii]{A2iii}}
            }
            child{node[/hierarkey/A/3]{A3}
                child{node[/hierarkey/A/3/i]{A3i}}
                child{node[/hierarkey/A/3/ii]{A3ii}}
                child{node[/hierarkey/A/3/iii]{A3iii}}
            }
        }
        child{node[/hierarkey/B]{B}
            child{node[/hierarkey/B/1]{B1}
                child{node[/hierarkey/B/1/i]{B1i}}
                child{node[/hierarkey/B/1/ii]{B1ii}}
                child{node[/hierarkey/B/1/iii]{B1iii}}
            }
            child{node[/hierarkey/B/2]{B2}
                child{node[/hierarkey/B/2/i]{B2i}}
                child{node[/hierarkey/B/2/ii]{B2ii}}
                child{node[/hierarkey/B/2/iii]{B2iii}}
            }
            child{node[/hierarkey/B/3]{B3}
                child{node[/hierarkey/B/3/i]{B3i}}
                child{node[/hierarkey/B/3/ii]{B3ii}}
                child{node[/hierarkey/B/3/iii]{B3iii}}
            }
        }
        child{node[/hierarkey/C]{C}
            child{node[/hierarkey/C/1]{C1}
                child{node[/hierarkey/C/1/i]{C1i}}
                child{node[/hierarkey/C/1/ii]{C1ii}}
                child{node[/hierarkey/C/1/iii]{C1iii}}
            }
            child{node[/hierarkey/C/2]{C2}
                child{node[/hierarkey/C/2/i]{C2i}}
                child{node[/hierarkey/C/2/ii]{C2ii}}
                child{node[/hierarkey/C/2/iii]{C2iii}}
            }
            child{node[/hierarkey/C/3]{C3}
                child{node[/hierarkey/C/3/i]{C3i}}
                child{node[/hierarkey/C/3/ii]{C3ii}}
                child{node[/hierarkey/C/3/iii]{C3iii}}
            }
        }
    ;
}
\end{document}

the result is the same as before,

Disclaimer: Usually the .unknown will report an error saying that I do not know this key blahblahblah... You can change this behavior by asking pgfkeys to try the parent folder. When there is no parent anymore, pgfkeys will do nothing and go on. That is, this will suppress the error report. Be sure you know what you are doing.



This is not good enough: child style will overwrite parent style

In the following code I defined .append child style such that

\pgfkeys{
    foo/bar/.append child style={red}
}

is equivalent to

\pgfkeys{
    foo/bar/.style={foo,red}
}

Therefore, if foo contains yellow then it will be overwritten by red; but if foo contains line width=10 then it is inherited by bar.


\documentclass[border=9,tikz]{standalone}
\usetikzlibrary{trees,shapes.geometric}
\begin{document}

\pgfkeys{%
    /handlers/.unknown/.style={\pgfkeyscurrentpath},%
    /handlers/.append child style/.code={%
        {%
            \edef\pgfkeyscurrentkey{\pgfkeyscurrentpath}%
            \csname pgfkeys@split@path\endcsname%
            \xdef\pgfkeysparentpath{\pgfkeyscurrentpath}%
        }%
        \edef\pgfkeysmarshal{%
            \noexpand\pgfkeys{\pgfkeyscurrentpath/.style={\pgfkeyscurrentpath long string that triggers .unknown}}%
        }%
        \pgfkeysmarshal%
        \pgfkeys{\pgfkeyscurrentpath/.append style={#1}}%
    }%
}%

\pgfkeys{
    /hierarkey/.cd,
    .append child style=draw,
    A/.append child style={draw=red},
    A/2/.append child style={fill=orange},
    A/3/i/.append child style={line width=3,draw=cyan,fill=magenta},
    B/.append child style={draw,circle},
    B/1/iii/.append child style={draw,shape=diamond},
    B/3/.append child style={draw,rounded corners},
    C/.append child style={rotate=30},
    C/2/ii/.append child style={font=\bfseries},
    C/3/.append child style={font=\ttfamily},
}

\tikz[
    grow=right,
    level 1/.style={sibling distance=-180,},
    level 2/.style={sibling distance=-60},
    level 3/.style={sibling distance=-20}]{
    \node[/hierarkey]{root}
        child{node[/hierarkey/A]{A}
            child{node[/hierarkey/A/1]{A1}
                child{node[/hierarkey/A/1/i]{A1i}}
                child{node[/hierarkey/A/1/ii]{A1ii}}
                child{node[/hierarkey/A/1/iii]{A1iii}}
            }
            child{node[/hierarkey/A/2]{A2}
                child{node[/hierarkey/A/2/i]{A2i}}
                child{node[/hierarkey/A/2/ii]{A2ii}}
                child{node[/hierarkey/A/2/iii]{A2iii}}
            }
            child{node[/hierarkey/A/3]{A3}
                child{node[/hierarkey/A/3/i]{A3i}}
                child{node[/hierarkey/A/3/ii]{A3ii}}
                child{node[/hierarkey/A/3/iii]{A3iii}}
            }
        }
        child{node[/hierarkey/B]{B}
            child{node[/hierarkey/B/1]{B1}
                child{node[/hierarkey/B/1/i]{B1i}}
                child{node[/hierarkey/B/1/ii]{B1ii}}
                child{node[/hierarkey/B/1/iii]{B1iii}}
            }
            child{node[/hierarkey/B/2]{B2}
                child{node[/hierarkey/B/2/i]{B2i}}
                child{node[/hierarkey/B/2/ii]{B2ii}}
                child{node[/hierarkey/B/2/iii]{B2iii}}
            }
            child{node[/hierarkey/B/3]{B3}
                child{node[/hierarkey/B/3/i]{B3i}}
                child{node[/hierarkey/B/3/ii]{B3ii}}
                child{node[/hierarkey/B/3/iii]{B3iii}}
            }
        }
        child{node[/hierarkey/C]{C}
            child{node[/hierarkey/C/1]{C1}
                child{node[/hierarkey/C/1/i]{C1i}}
                child{node[/hierarkey/C/1/ii]{C1ii}}
                child{node[/hierarkey/C/1/iii]{C1iii}}
            }
            child{node[/hierarkey/C/2]{C2}
                child{node[/hierarkey/C/2/i]{C2i}}
                child{node[/hierarkey/C/2/ii]{C2ii}}
                child{node[/hierarkey/C/2/iii]{C2iii}}
            }
            child{node[/hierarkey/C/3]{C3}
                child{node[/hierarkey/C/3/i]{C3i}}
                child{node[/hierarkey/C/3/ii]{C3ii}}
                child{node[/hierarkey/C/3/iii]{C3iii}}
            }
        }
    ;
}
\end{document}

Notice that in the C part all nodes are drawn, unlike the previous figure.

  • @ebo, I updated the answer and added something :) – Symbol 1 May 10 '17 at 22:52
  • Wow, nice! (And thank you!) This makes your answer more broadly applicable than my second one - it's great! – ebosi May 10 '17 at 22:57
6

>Disclaimer: This solution is not fully satisfactory. See comments.

Thanks to Christan Hupfer's comment, I implemented a solution using key/.forward to= as following.

\pgfkeys{
    /example/.is family, /example,
    main color/.default={100,100,100},
    main color/.code={\definecolor{mymaincolor}{RGB}{#1}},
    main color/.forward to = /example/color a,
    main color/.forward to = /example/color b,      
    color a/.code={\definecolor{mysubcolora}{RGB}{#1}},
    color b/.code={\definecolor{mysubcolorb}{RGB}{#1}},         
}

Explanation:

When called, the key main color forwards its value to both keys color A and color B. Note that here, the syntax is the opposite as the one of 'link' (i.e. it should not be understood as "color a is not dependent of main color" but rather "main color overrides color a").

Moreover, only main color should be called for initialization (and this must occur before considering user's input).

enter image description here


\documentclass{article}
    \usepackage{xcolor}
    \usepackage{tikz}

    \pgfkeys{
        /example/.is family, /example,
        main color/.default={100,100,100},
        main color/.code={\definecolor{mymaincolor}{RGB}{#1}},
        main color/.forward to = /example/color a,  
        main color/.forward to = /example/color b,      
        color a/.code={\definecolor{mysubcolora}{RGB}{#1}},
        color b/.code={\definecolor{mysubcolorb}{RGB}{#1}},         
    }

    \newcommand{\test}[1][]{%
        \pgfkeys{/example,
            main color,
            #1,
        }%
        \par
        \begin{tikzpicture}
            \node[fill = mysubcolora, circle, minimum height = 1cm,  text= white, font=\Large\sffamily\bfseries] at (0,0) {A};
            \node[fill = mysubcolorb, circle, minimum height = 1cm,  text= white, font=\Large\sffamily\bfseries] at (1,0) {B};
        \end{tikzpicture}
        \par
    }
\begin{document}
    \begin{description}
        \item[\textbackslash test] no argument: circle A and B both take default color = default main color. 
            \test
        \item[\textbackslash test[color a=\{255,0,0\}{]} ] circle A: local definition, circle B: default main color.
            \test[color a={255,0,0}]
        \item[\textbackslash test[main color=\{200,200,200\}{]}] circle A and B's colors are overridden by local main color
            \test[main color={200,200,200}]
        \item[\textbackslash test[main color=\{200,200,200\}, color b =\{0,0,255\}{]}] circle A and B's colors are overridden by main color, but circle's B color is over-overridden by local definition.
            \test[main color={200,200,200}, color b ={0,0,255}]
        \item[\textbackslash test]  no argument: default value (previous redefinitions of colors are local).
            \test
    \end{description}
\end{document}  
  • Note: This solution is not fully satisfactory: indeed \test[color b={0,0,255}, main color={200,200,200},] does not properly work. color b local definition is indeed overridden by the later local definition of main color. – ebosi May 8 '17 at 15:03
5

REVISED ANSWER

Well, maybe I can program in tikz, after all...

\documentclass{article}
    \usepackage{xcolor}
    \usepackage{tikz,listofitems}

\setsepchar{,/=/main color/color}
\newcommand\parseopt[1]{%
  \ifx\relax#1\relax\else%
    \greadlist*\directives{#1}%
    \foreachitem\x\in\directives[]{%
      \ifnum\listlen\directives[\xcnt,1]=2\relax%
        \edef\tmp{/example,main color=\directives[\xcnt,2]}%
        \expandafter\pgfkeys\expandafter{\tmp}%
      \fi%
    }%
    \foreachitem\x\in\directives[]{%
      \ifnum\listlen\directives[\xcnt,1,1]=2\relax%
        \edef\tmp{example,color \directives[\xcnt,1,1,2] = \directives[\xcnt,2]}%
        \expandafter\pgfkeys\expandafter{\tmp}%
      \fi%
    }%
  \fi%
}

    \pgfkeys{
        /example/.is family, /example,
        main color/.default={0,0,0},
        main color/.code={\definecolor{mymaincolor}{RGB}{#1}},
        main color/.forward to = /example/color a,  
        main color/.forward to = /example/color b,
        main color/.forward to = /example/color c,  
        color a/.code={\definecolor{mysubcolora}{RGB}{#1}},
        color b/.code={\definecolor{mysubcolorb}{RGB}{#1}},
        color c/.code={\definecolor{mysubcolorc}{RGB}{#1}},
    }

    \newcommand{\test}[1][]{%
        \pgfkeys{/example,
            main color,
            #1,
        }%
       \parseopt{#1}%
        \par
        \begin{tikzpicture}
            \node[fill = mysubcolora, circle, minimum height = 1cm,  text= white, font=\Large\sffamily\bfseries] at (0,0) {A};
            \node[fill = mysubcolorb, circle, minimum height = 1cm,  text= white, font=\Large\sffamily\bfseries] at (1,0) {B};
            \node[fill = mysubcolorc, circle, minimum height = 1cm,  text= white, font=\Large\sffamily\bfseries] at (2,0) {C};
        \end{tikzpicture}
        \par
    }
\begin{document}
    \begin{description}
        \item[\textbackslash test] no argument: circle A, B and C take default color = \emph{default} \texttt{main color} (black). 
            \test
        \item[\textbackslash test[color a=\{255,0,0\}{]} ] circle A: \emph{local} \texttt{color A} (red), circle B and C: no definition, so \emph{default} \texttt{main color}.
            \test[color a={255,0,0}]
        \item[\textbackslash test[main color=\{200,200,200\}{]}] circles A, B, and C's colors are overridden by \emph{local} \texttt{main color} (grey).
            \test[main color={200,200,200}]
        \item[\textbackslash test[main color=\{200,200,200\}, color b =\{0,0,255\}{]}] circles A, B, and C's colors are overridden by \emph{local} \texttt{main color} (i.e. the same than above), but circle's B color is ``over-overridden'' by \emph{local} \texttt{color B} definition (blue).
            \test[main color={200,200,200}, color b ={0,0,255}]
        \item[\textbackslash test[color b =\{0,0,255\}, main color=\{200,200,200\}{]}] Same behavior than above. However, the \emph{local} definition of \texttt{color B} is specified \emph{before} the \emph{local} definition of \texttt{main color}. In this case, suggested solution doesn't work. (circle B should be blue.)
            \test[color b ={0,0,255}, main color={200,200,200}]            
        \item[\textbackslash test]  no argument: circle A, B and C take default color = \emph{default} \texttt{main color} (black). (Testing if local color definition are really local.)
            \test
    \end{description}
\end{document}

enter image description here

ORIGINAL ANSWER

I can't program in tikz, but hierarchical parsing is tailor made for the listofitems package. In the MWE below, I pass various optional arguments to \parseopt and it will always arrange any main color directives before the individual color directives, regardless of their order in the optional argument.

The key to success is the line \setsepchar{,/=/main color/color} which sets up a 4-level parsing, first looking for ,. Then, within each item of that list, it looks for =. Thirdly, it looks for main color and lastly on level 4, it looks for color (but main color entries have already been parsed out and so they won't appear in level 4). Then, merely by checking how many entries are at particular parsing levels, I can deduce what whether an entry was a main color entry or merely a color entry. I make sure I process all the former ones first.

\documentclass{article}
\usepackage[margin=3cm]{geometry}
    \usepackage{xcolor}
    \usepackage{tikz,listofitems}

\newcommand\parseopt[1]{%
  \ifx\relax#1\relax  No arguments to process\par\else%
    \setsepchar{,/=/main color/color}
    \greadlist*\directives{#1}%
    \foreachitem\x\in\directives[]{%
      \ifnum\listlen\directives[\xcnt,1]=2\relax%
        override default for all to \directives[\xcnt,2]\par%
      \fi%
    }%
    \foreachitem\x\in\directives[]{%
      \ifnum\listlen\directives[\xcnt,1,1]=2\relax%
        set color ``\directives[\xcnt,1,1,2]'' to \directives[\xcnt,2]\par%
      \fi%
    }%
  \fi%
}
\begin{document}
\parseopt{}
\hrulefill\par
\parseopt{color a={255,0,0}}
\hrulefill\par
\parseopt{main color={200,200,200}}
\hrulefill\par
\parseopt{main color={200,200,200}, color b={0,0,255}}
\hrulefill\par
\parseopt{color b={0,0,255}, main color={200,200,200}}
\hrulefill\par
\parseopt{color b={0,0,255}, main color={200,200,200},color a ={255,0,0}}
\end{document}

enter image description here

Whether this parsing can be parlayed into an effective invocation of \pgfkeys, perhaps the OP would have some insights...

5

Edit

Here is another solution using some styles (inheritance between styles is defined when drawing nodes).

\documentclass{standalone}
\usepackage{xcolor}
\usepackage{tikz}
\usepackage{varwidth}

\tikzset{
  % default styles
  @main base/.style={circle,minimum height=1cm,text=white,font=\Large\sffamily\bfseries},
  @main/.style={fill=gray},
  @node a/.style={},
  @node b/.style={},
  @node c/.style={},
}

\pgfkeys{ 
  /example/.is family,
  /example,
  % keys to change styles
  main/.style={/tikz/@main/.style={#1}},
  node a/.style={/tikz/@node a/.style={#1}},
  node b/.style={/tikz/@node b/.style={#1}},
  node c/.style={/tikz/@node c/.style={#1}},
}

\newcommand{\foobar}[1][]{%
  \begin{tikzpicture}[baseline={(0,-.5ex)}]
    % choose colors by pgfkeys
    \pgfkeys{/example,#1}
    % draw
    \node[@main base,@main,@node a] at (0,0) {A};
    \node[@main base,@main,@node b] at (1,0) {B};
    \node[@main base,@main,@node c] at (2,0) {C};
  \end{tikzpicture}%
  \par%
}

\begin{document}
\begin{varwidth}{10cm}
  default \foobar
  node a \foobar[node a={fill=red}]
  main \foobar[main={fill=red}]
  main, node b \foobar[main={fill=green},node b={fill=cyan!50!blue}]
  node b, main \foobar[node b={fill=cyan!50!blue},main={fill=green}]
\end{varwidth}
\end{document}

enter image description here

First proposition

Here is a solution using \@ifundefinedcolor from xcolor package.

\documentclass{standalone}
\usepackage{xcolor}
\usepackage{tikz}
\usepackage{varwidth}

% from xcolor package
\makeatletter
\def\ifundefinedcolor#1{\@ifundefinedcolor{#1}}
\makeatother

\pgfkeys{
  /example/.is family,
  /example,
  % official keys
  main color/.code={\definecolor{main color}{RGB}{#1}},
  color a/.code={\definecolor{color a}{RGB}{#1}},
  color b/.code={\definecolor{color b}{RGB}{#1}},
  color c/.code={\definecolor{color c}{RGB}{#1}},
  % useful key
  @link color to main color/.code={%
    \ifundefinedcolor{#1}{\colorlet{#1}{main color}}{}%
  },
  % set initial main color
  main color={0,0,0},
}


\newcommand{\foobar}[1][]{%
  \begin{tikzpicture}[baseline={(0,-.5ex)}]
    % choose colors by pgfkeys
    \pgfkeys{/example,#1,@link color to main color/.list={color a,color b,color c}}
    % draw
    \node[fill=color a, circle, minimum height = 1cm,  text= white, font=\Large\sffamily\bfseries]
    at (0,0) {A};
    \node[fill=color b, circle, minimum height = 1cm,  text= white, font=\Large\sffamily\bfseries]
    at (1,0) {B};
    \node[fill=color c, circle, minimum height = 1cm,  text= white, font=\Large\sffamily\bfseries]
    at (2,0) {C};
  \end{tikzpicture}%
  \par%
}

\begin{document}
\begin{varwidth}{10cm}
  default \foobar
  color a \foobar[color a={255,0,0}]
  main color \foobar[main color={255,0,0}]
  main color, color b \foobar[main color={0,155,255}, color b ={0,155,0}]
  color b, main color \foobar[color b={0,155,0},main color={0,155,255}]
\end{varwidth}
\end{document}

enter image description here

2

Disclaimer: This new answer actually addresses my original issue (i.e. enabling users to set a local main color and/or local colors for element x ), rather than the question as it is formulated. Since it solves my problem, I consider it worth being broadcasted.


I am here here using a workaround. This is possible due to two reasons:

  • I'm only interrested in styles. (Thanks to Symbol 1 for having initiated me to these key handler via its answer.)
  • I define styles of TikZ elements only.

This workaround exploit following behavior of TikZ: If you set a certain characteristic of a TikZ element several times, only the last definition is taken into consideration. (E.g. \tikz\path[fill=blue, fill=red](0,0) rectangle (1,1); will draw a red square.)

My solution is thus to create a key for each "node" of the hierarchical tree, and then to let the managment of styles precedence being directly handled in the element definition. (E.g. "\element[main style, substyle A, subsubstyle A3, subsubsubstyle A3i]...")

\documentclass{article}
    \usepackage{xcolor}
    \usepackage{tikz}

    %colors are define before, since its more convenient.
    \definecolor{myblack}{RGB}{0,0,0}
    \definecolor{mygrey}{RGB}{200,200,200}
    \definecolor{myred}{RGB}{255,0,0}
    \definecolor{myblue}{RGB}{0,0,255}

    \newcommand{\test}[1][]{%
        %initiate values for each styles key
        \pgfkeys{
            /mystyles/.cd,
            main/.style={
                fill=myblack,
                circle,
                minimum height = 1cm,
                text= white, 
                font=\Large\sffamily\bfseries,
            },
            circle A/.style={},
            circle B/.style={},
            circle C/.style={},             
        }%
        % use commands' key=value pairs to change styles
        \pgfkeys{
            #1,
        }%
        \par
        \begin{tikzpicture}
            \node[/mystyles/main, /mystyles/circle A] at (0,0) {A};
            \node[/mystyles/main, /mystyles/circle B] at (1,0) {B};
            \node[/mystyles/main, /mystyles/circle C] at (2,0) {C};
        \end{tikzpicture}
        \par
    }
\begin{document}
    \test

    \test[/mystyles/circle A/.append style={fill=myred}]
    %indeed:
    %\test[/mystyles/circle A/.append style={myred}]% would define red for each setting, here fill color... and font color as well!

    \test[/mystyles/main/.append style={fill=mygrey}]

    \test[/mystyles/main/.append style={fill=mygrey}, /mystyles/circle B/.append style={fill=myblue}]

    \test[/mystyles/circle B/.append style={fill=myblue}, /mystyles/main/.append style={fill=mygrey}]

    \test

    %how this system could be further used
    \test[/mystyles/main/.append style={font=\small\sffamily}]
\end{document}  

enter image description here


The solution above is simple and works. It is however not that easy to manage, notably if you dont define your keys in the /tikz/ "folder", and because of the verbose /.append style={fill=.

I thus provide below a second implementation that is intended to be a bit more userfriendly: instead of directly modifying /mystyles/'s keys, I created another set of key=values: /mycommands/. This time, I use the /.code key handler. This notably enables to give users only a limited set of settings they can modify. (E.g. here I don't want them to change the circles diameters.)

\documentclass{article}
    \usepackage{xcolor}
    \usepackage{tikz}

    \definecolor{myblack}{RGB}{0,0,0}
    \definecolor{mygrey}{RGB}{200,200,200}
    \definecolor{myred}{RGB}{255,0,0}
    \definecolor{myblue}{RGB}{0,0,255}

    \pgfkeys{
        /mycommands/.cd,
        fill each/.code={\pgfkeys{/mystyles/main/.append style={fill=#1}}},
        fill A/.code={\pgfkeys{/mystyles/circle A/.append style={fill=#1}}},
        fill B/.code={\pgfkeys{/mystyles/circle B/.append style={fill=#1}}},
        fill C/.code={\pgfkeys{/mystyles/circle C/.append style={fill=#1}}},
        %next line is for showing how these commands could be further explanded.
        font each/.code={\pgfkeys{/mystyles/main/.append style={font=#1}}},
    }

    \newcommand{\test}[1][]{%
        %initiate values for each styles key
        \pgfkeys{
            /mystyles/.cd,
            main/.style={
                fill=myblack,
                circle,
                minimum height = 1cm,
                text= white, 
                font=\Large\sffamily\bfseries,
            },
            circle A/.style={},
            circle B/.style={},
            circle C/.style={},             
        }%
        % use commands' key=value pairs to change styles
        \pgfkeys{
            /mycommands/.cd,
            #1,
        }%
        \par
        \begin{tikzpicture}
            \node[/mystyles/main, /mystyles/circle A] at (0,0) {A};
            \node[/mystyles/main, /mystyles/circle B] at (1,0) {B};
            \node[/mystyles/main, /mystyles/circle C] at (2,0) {C};
        \end{tikzpicture}
        \par
    }
\begin{document}
    \test

    \test[fill A = red]

    \test[fill each=mygrey]

    \test[fill each=mygrey, fill B=myblue]

    \test[fill B=myblue, fill each=mygrey]

    \test

    \test[font each={\small\sffamily}]
\end{document}

enter image description here

  • 1
    fill A/.code={\pgfkeys{/mystyles/circle A/.append style={fill=#1}}} and fill A/.style={/mystyles/circle A/.append style={#1}} are equivalent... – Paul Gaborit May 10 '17 at 22:57
0

Imho you should perhaps consider to implement the order not through the key handling but simply when you use the colors. After all it is quite allowed to use two fill-keys in a node and the second will win:

\documentclass{article}
\usepackage{xcolor}

\usepackage{tikz}

\begin{document}

 \begin{tikzpicture}
            \def\mysubcolor{}
            \def\mymaincolor{red}
            \node[fill=\mymaincolor,fill = \mysubcolor, circle, minimum height = 1cm,  text= white, font=\Large\sffamily\bfseries] at (0,0) {A};

            \def\mysubcolor{blue}
            \def\mymaincolor{red}
            \node[fill=\mymaincolor,fill = \mysubcolor, circle, minimum height = 1cm,  text= white, font=\Large\sffamily\bfseries] at (1,0) {B};

 \end{tikzpicture}
 \end{document}

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.