4

I've been writing a package for LaTeX lately called TikZ-Feynman. It is an extension to TikZ and just like PGFplots, it defines a new environment within {tikzpicture} called {feynman} and makes available new commands and styles within.

I have defined all my keys and styles related to TikZ-Feynman within a branch called /tikzfeynman in PGFkeys and ideally I would like to have it such that within the {feynman} environment, the /tikzfeynman path is always searched first before moving on.

The way I have been achieving this so far is basically to have:

\pgfkeys{
  /tikzfeynman/.cd,
  /tikzfeynman/.search also={/tikz},
  ...
}

But looking at it now, it seems like a suboptimal solution. Firstly, it requires the whole .cd and .search also to be prefixed everywhere, and it seems to be incompatible with new .style that make use of keys within /tikzfeynman (see this bug).

The issue is further complicated by the fact that TikZ-Feynman uses the graph drawing library and thus in some occasions, it will have to search the /tikz/graphs and /graph drawing paths too (but not always).

I have been trying to look at how PGFplots handles this with its {axis} environments, but so far I haven't been able to identify what I need (if PGFplots even does this).

So is there a way to make a PGFkeys path (or family) take precedence within a \begingroup ... \endgroup (or \scope ... \endscope)? And (optionally), can this also apply inside a graph?


Here is a minimal (non-)working example that illustrates the issue I would like to fix.

\documentclass[tikz]{standalone}

%% Hidden to the user
\pgfkeys{
  /foo/.is family,
  /foo/.search also={/tikz},
}
\def\fooset{\pgfqkeys{/foo}}
\fooset{
  key a/.style={
    key b,
    key c,
  },
  key b/.style={
    /tikz/red,
  },
  key c/.style={
    /tikz/thick,
  },
}

\makeatletter
\def\fooenv{
  \begingroup
  % Neither of the following seem to do anything as the /.cd only applies to
  % keys within the same command call.
  \pgfkeys{/foo/.cd,/foo/.search also={/tikz}}
  \scope[/foo/.cd,/foo/.search also={/tikz}]
}
\def\endfooenv{
  \endscope
  \endgroup
}
\makeatother

%% What a user might do
\fooset{
  my key/.style={
    key c,
    blue,
  },
}

\begin{document}
\begin{tikzpicture}
  % The following should fail because we are not in fooenv.
  \draw [key a] (0, 0) -- (1, 0);
  \begin{fooenv}
    % The following should work since we are now in fooenv, but don't.
    \draw [key a] (0, 0) -- (1, 0);
    \draw [my key] (0, 0) -- (0, 1);
    % In this case, it finds key a fine, but fails to find key c.
    \draw [/foo/my key] (0, 0) -- (1, 1);
  \end{fooenv}
\end{tikzpicture}
\end{document}
  • You can first /.try and then pass it to \tikzset – percusse Feb 1 '16 at 7:53
  • Unless I'm mistaken, what you're suggesting is equivalent to what I'm current doing. By setting /.search also and /.cding into the right path, PGFkeys first attempts to find the key in /tikzfeynman and if that fails, tries in the paths given in the /.search also. Also, this doesn't really set /tikzfeynman as the first search path within the whole scope which is the main issue I'm facing. Please correct me if you meant something else. – JP-Ellis Feb 1 '16 at 9:37
  • If you /.try it only tries in the current context. Then if fails you can then push it to \pgfkeysalso and other places. /.search also basically expands the current key family to include others. – percusse Feb 1 '16 at 9:39
  • Are you saying that I should get users of TikZ-Feynman to write /.try? I'm not quite sure how that would be implemented. Also, what would happen if I wanted to override a /tikz key so that my key takes precedence? I have updated the initial post with a minimal (non-)working example that illustrates the issue. – JP-Ellis Feb 1 '16 at 10:08
  • There is a fatal deficiency in your code: \draw[key a] passes key a to \tikzset, so /tikz is always the a choice, and in fact the first choice. – Symbol 1 Feb 2 '16 at 17:01
3

An non-pgfkeys approach

Let \tikzset be \foobar inside your environment. This might be the easiest way. (In the manner that I added just one line.)

\documentclass[border=9,tikz]{standalone}

%% Hidden to the user
\pgfkeys{
  /foo/.is family,
  /foo/.search also={/tikz},
}
\def\fooset{\pgfqkeys{/foo}}
\fooset{
  key a/.style={
    key b,
    key c,
  },
  key b/.style={
    red,
  },
  key c/.style={
    thick,
  },
}
\def\fooenv{
    \begingroup
    \let\tikzset\fooset
}
\def\endfooenv{
    \endgroup
}

%% What a user might do
\fooset{
  my key/.style={
    key c,
    blue,
  },
}

\begin{document}

\begin{tikzpicture}
  \begin{fooenv}
    \draw [key a] (0, 0) -- (1, 0);
    \draw [my key] (0, 0) -- (0, 1);
    \draw [/foo/my key] (0, 0) -- (1, 1);
  \end{fooenv}
\end{tikzpicture}

\end{document}

A pgfkeys handler approach

In TeX's language, what you try to do is much like

\let\oldtikzpath=\tikzpath
\def\tikzpath={\foopath \oldtikzpath}

This function is provide by TeX at the lowest level. Pgfkeys does not have this function yet. But we can create it by something like

\pgfkeys{
    /handlers/.cd redirect/.code={
        % store the redirection target somewhere
    },
    /handlers/.new cd/.code={
        % check if user set new target manually
         % cd properly
    }
}

A small example goes

\pgfkeys{
    /handlers/.cd redirect/.code={
        \pgfkeyssetvalue{\pgfkeyscurrentpath/cd target}{#1}
    },
    /handlers/.new cd/.code={
        \pgfkeysgetvalue{\pgfkeyscurrentpath/cd target}\pgfkeysredirectpath
        \ifx\pgfkeysredirectpath\relax
            \edef\pgfkeysdefaultpath{\pgfkeyscurrentpath/}
        \else
            \edef\pgfkeysdefaultpath{\pgfkeysredirectpath/}
        \fi
    }
}

\pgfkeys{
    /bar/.cd redirect={/foo},
}

\pgfkeys{
    /foo/foo/.code={foo-foo},
    /bar/bar/.code={bar-bar}
}

\pgfkeys{
    /bar/.cd,
    bar
}

\pgfkeys{
    /bar/.new cd,
    foo
}

We can even redefine .cd by how we define .new cd.


Just one step away: \xxxxset-family are define by \pgfqkeys. The manual say that it is equivalent to \pgfkeys{#1/.cd,#2}. In reality, \pgfqkeys defines \pgfkeysdefaultpath without calling handler .cd. So we have to redefine \pgfqkeys, either by calling .new cd or copy-and-pasting the if-statement again.


A sidenote: the manual claims

The setting of a key is always local to the current TEX group.

So it is OK to say /tikz/.cd redirect=to somewhere or something similar in your own environment. The effect "expires" once you leave the environment.

Old Answer

The design of pgfkeys makes no easy way to "cut the line". By "line" I mean that if you say

\pgfkeys{
    /a/.search also={
        /b,
        /c
    }
}

then pgfkeys will try to search /a/x, /b/x, /c/x in this order if one issues /a/.cd,x=something. That is, /a, /b, /c are "lined up" and you cannot reorder it.

In your example, \draw[key a] is ultimately passed to \tikzset{key a}. Therefore

  • pgfkeys will search under /tikz;
  • pgfkeys will NOT search /foo unless you says /tikz/.search also={/foo}.
  • Even if you said so, /tikz is still the first candidate.
  • And the worst thing is: it destroys colors specification and arrows specification.

To come over this, you have to add alias under /tikz manually. There are already plenty of aliases: every picture, every axis, every graph, etc.

Otherwise you have to ask package users to put options at proper place. Just like

\tikz\path(0,0)[rotate=30]node{rotated?};

and

\tikz\path(0,0)node[rotate=30]{rotated?};

Nonetheless, here there is still good news: For options passed to \fooset, pgfkeys will try /foo/x and /tikz/x in this order provided that you said \fooset{.search also={/tikz}} somewhere before.

Hence you might want to create special commands such as \foodraw to replace \draw. Then you are certain that \foodraw[key a] does pass key a to \fooset.

  • Thanks for the answer. What you suggest is mostly what I have been doing already. Trying to define alternatives to the standard TikZ commands and also make use of the every <element>/.style keys; however, this only gets me so far. I was hoping there be a better way. – JP-Ellis Feb 2 '16 at 23:25
  • I can't find time to write an example but \tikzset is nothing but a pgfkeys shortcut. You can hack it at the start of the environment and pass everything to a /foo/key/.try first and, depending on the success or not, pass to TikZ. That allows an unknown key to avoid an error but instead set an if clause. – percusse Feb 3 '16 at 1:25
  • @percusse at the beginning OP is not sure if all options are passed through \tikzset. For me the point is different: I recognize pgfkeys as an alternative to primitive TeX commands. So in any case I prefer pgfkeys (handler) approach. – Symbol 1 Feb 3 '16 at 1:45
  • Yes but It is a pgfkeys approach. /.try /.retry etc. are all valid key handlers. If you have time, you can install a filter as pgfplots does. – percusse Feb 3 '16 at 1:54
  • @percusse at the beginning OP is not sure if all options are passed through \tikzset. For me the point is different: I recognize pgfkeys as an alternative to TeX primitives. So in any case I prefer pgfkeys (handler) approach. In this particular case, we should not develop decorative commands unless we have appropriate handlers. And even the handler (.new cd) should be defined by handlers, such as .if defined then else. – Symbol 1 Feb 3 '16 at 1:56
2

I think something like this should work though I didn't test properly. What I did is fiddle with the foo/.unknown part and then copy the rest of the /tikz/.unknown part.

What happens is that once /foo/<key>=<value> fails we give up on the family of foo and change to TikZ. The detail needs to be handled (and that's the part I copied) is that colors, arrows and shapes are not styles! So stealth-latex actually goes and looks for a style for that named key and then falls back to the handler I've copied.

So they need to be taken care of properly (which is amazing to start with that this exists for my taste of user friendliness).

\documentclass[tikz]{standalone}

%% Hidden to the user
\pgfkeys{/foo/.is family}
\def\fooset{\pgfqkeys{/foo}}
\makeatletter
\def\installfooset{
  \def\tikzset{\pgfqkeys{/foo}}%
    \pgfkeys{/foo/.unknown/.code={%
      \let\foo@key\pgfkeyscurrentname%
      \pgfkeys{/tikz/.cd,/tikz/\foo@key/.try={##1}}%
        \ifpgfkeyssuccess\typeout{I've passed "\foo@key" to TikZ}\else%
        \let\tikz@key\foo@key%
        % Start copying and print on the terminal
        \typeout{TikZ also didn't like "\foo@key"! Maybe arrow,color, or shape?}
    \expandafter\pgfutil@in@\expandafter!\expandafter{\tikz@key}%
    \ifpgfutil@in@%
      % this is a color!
      \expandafter\tikz@addoption\expandafter{\expandafter\tikz@compat@color@set\expandafter{\tikz@key}}%
      \edef\tikz@textcolor{\tikz@key}%
    \else%
      \pgfutil@doifcolorelse{\tikz@key}
      {%     
        \expandafter\tikz@addoption\expandafter{\expandafter\tikz@compat@color@set\expandafter{\tikz@key}}%
        \edef\tikz@textcolor{\tikz@key}%
      }%
      {%
        % Ok, second chance: This might be an arrow specification:
        \expandafter\pgfutil@in@\expandafter-\expandafter{\tikz@key}%
        \ifpgfutil@in@%
          % Ah, an arrow spec!
          \expandafter\tikz@processarrows\expandafter{\tikz@key}%
        \else%
          % Ok, third chance: A shape!
          \expandafter\ifx\csname pgf@sh@s@\tikz@key\endcsname\relax%
            \pgfkeys{/errors/unknown key/.expand
            once=\expandafter{\expandafter/\expandafter t\expandafter
            i\expandafter k\expandafter z\expandafter/\tikz@key}{##1}}%
          \else%
            \edef\tikz@shape{\tikz@key}%
          \fi%
        \fi%
      }%
    \fi%
  \fi%
        }
    }
}
\def\fooenv{\begingroup\installfooset\scope[]}
\def\endfooenv{\endscope\endgroup}
\makeatother


\fooset{
  key a/.style={key b,key c,},
  key b/.style={/tikz/red!40},
  key c/.style={/tikz/thick},
}
%% What a user might do
\fooset{my key/.style={key c,blue}}

\begin{document}
\begin{tikzpicture}
  % Fails with unknown [key a] ... Done!
  %\draw [key a] (0, 0) -- (1, 0);
    % Works... Done!
  \begin{fooenv}
    \draw [key a] (0, 0) -- (1, 0);
    \draw [my key] (0, 0) -- (0, 1);
        % Does it overwrite? Done!
    \draw [/foo/my key,dashed,key b,blue] (0, 0) -- (1, 1);
  \end{fooenv}
  % Uninstalled? ... Done!
  %\draw [key a] (0, 0) -- (1, 0);
\end{tikzpicture}
\end{document}

It will hopefully print out the following to the terminal

I've passed "draw" to TikZ
I've passed "line width" to TikZ
I've passed "draw" to TikZ
I've passed "line width" to TikZ
TikZ also didn't like "blue"! Maybe arrow,color, or shape?
I've passed "draw" to TikZ
I've passed "line width" to TikZ
TikZ also didn't like "blue"! Maybe arrow,color, or shape?
I've passed "dashed" to TikZ
TikZ also didn't like "blue"! Maybe arrow,color, or shape?

Notice how red!40 doesn't appear because it travels the legitimate /.unknown code.

Having said that, what you are trying to do is against the TikZ design essentials. The idea is to get out of the way of the user and leave entrance points to modify every detail as much as possible. Hence overwriting existing keys are pretty against this mantra. That's why you have lots and lots of every .../.style=... keys in TikZ.

If you overwrite a TikZ key you are shaving off some of the functionality that might be needed for a power user.

  • Thanks a lot for this! I'll have a look at whether I can get it to fix this issue. Also I realize that overriding keys is against the general way TikZ works and I definitely do not intend to do it much at all; however, I don't think it is unreasonable to slightly modify certain keys only within the {feynman} environment. – JP-Ellis Feb 3 '16 at 11:07
  • Curiously, is there a reason (other than to integrate the typeout commands) for including the whole TikZ .unknown/.code instead of using \pgfkeysgetvalue{/tikz/.unknown/.@cmd}{\orig@tikz@search} like I have in my answer? – JP-Ellis Feb 3 '16 at 11:35
  • @JP-Ellis Not really it's a habit since I usually modify stuff. Here you can also push your other package dependent options etc. following the TikZ mantra with a custom key execute at unknown key for example. So that you can execute other non-TikZ but pgfkeyed stuff and so on. Also notice that this is now foo/.unknown you have tikz/.unknown as a difference – percusse Feb 3 '16 at 12:22
1

One of the ideas of toying with is trying to override certain TikZ function within the scope of the environment. In particular, trying to override /tikz/.unknown/.@cmd such that it first search /foo before doing what it would usually do.

This is not the best solution for me because this does not allow me to override keys in /tikz with keys in /foo, though admittedly this isn't a deal breaker since I wouldn't want to do that too much as it could lead to some confusion to people using the package.

This works for keys which are explicitly specified (such as key a and my key) but unfortunately it does not seem to work for keys referred to within the style. The error message is I do not know '/key b', so for some reason keys within styles are handled differently... but how?

Another peculiarity is the case of /foo/my key. Since this is a full key, it does not need to be searched for, and instead key c passes through /tikz/.unknown and is successfully found. What is then strange is that it fails to find blue and actually produces some errors (extra \else, and many incomplete \if and \ifx).

Here is a modified version of the MWE above which implements the modified /tikz/.unknown:

\documentclass[tikz]{standalone}

%% Hidden to the user
\makeatletter
\pgfkeys{
  /foo/.is family,
  /foo/.search also={/tikz},
}
\def\fooset{\pgfqkeys{/foo}}
\fooset{
  key a/.style={key b, key c},
  key b/.style={red},
  key c/.style={thick},
}

\def\fooenv{
  \scope
  \ammend@search@path
}
\def\endfooenv{
  \endscope
}

\def\ammend@search@path{%
  \pgfkeysgetvalue{/tikz/.unknown/.@cmd}{\orig@tikz@search}
  \pgfkeys{/tikz/.unknown/.code=%
    \let\unknown@key\pgfkeyscurrentname
    \pgfkeys{/foo/\unknown@key/.try={##1}}
    \ifpgfkeyssuccess\else
    \let\pgfkeyscurrentname\unknown@key
    \orig@tikz@search##1\pgfeov
  }
}
\makeatother

%% What a user might do
\fooset{
  my key/.style={key c, blue}
}

\begin{document}
\begin{tikzpicture}
  % The following should fail because we are not in fooenv.
  \draw [key a] (0, 0) -- (1, 0);
  \begin{fooenv}
    % The following should work since we are now in fooenv, but don't.
    \draw [key a] (0, 0) -- (1, 0);
    \draw [my key] (0, 0) -- (0, 1);
    % In this case, it finds key a fine, but fails to find key c.
    \draw [/foo/my key] (0, 0) -- (1, 1);
  \end{fooenv}
\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.