3

I would like to create a multinode shape doublecirc with two node parts, say text and lower, consisting of two circles with text in each.

(There are obviously ways to do this more simply -- but this is a MWE, my intended shape is more complex.)

I would like the color of the part lower circle to be inherited from text, but with an optional pgfkey allowing it to be changed:

\node[name=a,shape=doublecirc,style={draw},red] % circles+text red
\node[name=b,shape=doublecirc,style={draw},draw=red]% circles red
\node[name=c,shape=doublecirc,style={draw},draw=red, /tikz/lower color = blue] % lower circle blue

Three multi-part shapes

I found out a way to do this based on extending Loop Space's solution to another question. However, it turned out to be much more complex than I expected, essentially working around pgfkeys. (Specifically, I used the etoolbox package command \ifdefstring, and a 'dummy' initial color assignment (empty) to my key.)

\documentclass{article}
\usepackage{etoolbox}
\usepackage{pgf,tikz}
\usetikzlibrary{arrows,shapes.arrows,shapes.geometric,
shapes.multipart,backgrounds,decorations.pathmorphing,positioning,fit,
shapes.callouts}

\makeatletter

\newbox\pgfnodepartlowerbox 
\newbox\pgfnodeparttextbox  
\pgfkeys{/tikz/lower color/.initial = empty}% Default value indicates undefined

\pgfdeclareshape{doublecirc}{%
\nodeparts{text,lower}%
\savedmacro\lowercolor{%
    \edef\tst{\pgfkeysvalueof{/tikz/lower color}}%
    \ifdefstring{\tst}{empty}% comparison command from etoolbox
        {\ifx\tikz@strokecolor\pgfutil@empty%if not defined
                      \def\lowercolor{.}%
                   \else%
                       \def\lowercolor{\tikz@strokecolor}%
                   \fi}%
            {\def\lowercolor{\expandafter\pgfkeysvalueof{/tikz/lower color}}}%if defined
            }
 \savedanchor\centerpoint{%
\pgf@x=.5\wd\pgfnodeparttextbox% 
\pgf@y=.5\ht\pgfnodeparttextbox% 
  }
 \savedanchor\lower{%
\pgf@x=.5\wd\pgfnodepartlowerbox% 
\pgf@y=.5\ht\pgfnodepartlowerbox% 
\advance\pgf@y by -2.0\ht\pgfnodeparttextbox%
\advance\pgf@x by 2.0\wd\pgfnodeparttextbox%
 }
  \anchor{center}{\centerpoint}%
 \anchor{lower}{\lower}
 \backgroundpath{
  \pgfpathcircle{\centerpoint}{\wd\pgfnodeparttextbox}%
   \pgfusepath{stroke}%
  \pgfsetstrokecolor{\lowercolor}%
  \pgfpathcircle{\lower}{1.5\wd\pgfnodepartlowerbox}%
  \pgfusepath{stroke}%
}
  \anchorborder{\centerpoint}
  }
\makeatother

\begin{document}
\begin{tikzpicture}
\node[name=a,shape=doublecirc,style={draw},red] at (0,0) {A\nodepart{lower}{B}};  % Inherit color from .
\node[name=b,shape=doublecirc,style={draw},draw=red] at (2,0) {A\nodepart{lower}{B}};  % Inherit color from draw
\node[name=c,shape=doublecirc,style={draw},draw=red, /tikz/lower color = blue] at (4,0) {A\nodepart{lower}{B}}; % Specify color with parameter
\end{tikzpicture}

\end{document}

Is there a simpler way to do this with pgfkeys ?

A particular technical problem was that I did not find an elegant way to initialize my pgfkey (lower color), while leaving the key undefined. At least not in a way that I could then detect with some form of \if (but I found the TeX conditionals perplexing: I don't fully understand why I need to use \edef in the code above)

In answering another question @percusse introduces an
.initial without value handler which looks promising. However, this requires redefining the \pgfkeys@notset and \pgfkeysvalueof which I worry would make it harder to create a self-contained package with the shape declaration (?)

I also explored trying to use macros or other handlers associated with my pgfkey, but found this confusing. (See the discussion of storing values vs. macros here and here)

I'm guessing that being able to handle colors in this way might be useful for other multi-part node shapes.

(Author's note: this is my first question posting, so apologize if I unwittingly violate protocol or if this has been answered previously.)

  • Thanks @marmot for spotting this - I have removed multipartb. (That is my local experimental copy - which provided the motivation for the question.) – democritus Sep 5 '18 at 5:41
3

First of all, congratulations to that great question. Let me start with a list of minor comments.

  1. You do not need etoolbox for the comparison.
  2. Nor do you need any of the libraries you load.
  3. And then I am not sure you need an \expandafter in \def\lowercolor{\expandafter\pgfkeysvalueof{/tikz/lower color}}.

Here comes a stripped down version. I actually was not aware that one can access colors like this, so I learned something.

\documentclass{article}
\usepackage{tikz}

\makeatletter
\newbox\pgfnodepartlowerbox 
\newbox\pgfnodeparttextbox  
\pgfkeys{/tikz/lower color/.initial=empty}% Default value indicates undefined

\pgfdeclareshape{doublecirc}{%
\nodeparts{text,lower}%
\savedmacro\lowercolor{%
    \edef\tst{\pgfkeysvalueof{/tikz/lower color}}%
    \def\tempty{empty}
    \ifx\tst\tempty\relax
        \ifx\tikz@strokecolor\pgfutil@empty%if not defined
                      \def\lowercolor{.}%
                   \else%
                       \def\lowercolor{\tikz@strokecolor}%
                   \fi%
    \else              
        \def\lowercolor{\pgfkeysvalueof{/tikz/lower color}}%if defined
    \fi}
 \savedanchor\centerpoint{%
\pgf@x=.5\wd\pgfnodeparttextbox% 
\pgf@y=.5\ht\pgfnodeparttextbox% 
  }
 \savedanchor\lower{%
\pgf@x=.5\wd\pgfnodepartlowerbox% 
\pgf@y=.5\ht\pgfnodepartlowerbox% 
\advance\pgf@y by -2.0\ht\pgfnodeparttextbox%
\advance\pgf@x by 2.0\wd\pgfnodeparttextbox%
 }
  \anchor{center}{\centerpoint}%
 \anchor{lower}{\lower}
 \backgroundpath{
  \pgfpathcircle{\centerpoint}{\wd\pgfnodeparttextbox}%
   \pgfusepath{stroke}%
  \pgfsetstrokecolor{\lowercolor}%
  \pgfpathcircle{\lower}{1.5\wd\pgfnodepartlowerbox}%
  \pgfusepath{stroke}%
}
  \anchorborder{\centerpoint}
  }
\makeatother

\begin{document}
\begin{tikzpicture}
\node[name=a,shape=doublecirc,style={draw},red] at (0,0) {A\nodepart{lower}{B}};  % Inherit color from .
\node[name=b,shape=doublecirc,style={draw},draw=red] at (2,0) {A\nodepart{lower}{B}};  % Inherit color from draw
\node[name=c,shape=doublecirc,style={draw},draw=red, /tikz/lower color = blue] at (4,0) {A\nodepart{lower}{B}}; % Specify color with parameter
\end{tikzpicture}
\end{document}

TOWARDS AN ANSWER(?): If I am not mistaken, you may simplify it further. You only want to change the color if the key is set. This suggests

\documentclass{article}
\usepackage{tikz}

\makeatletter
\newbox\pgfnodepartlowerbox 
\newbox\pgfnodeparttextbox  
\pgfkeys{/tikz/lower color/.initial=\pgfutil@empty}% Default value indicates undefined

\pgfdeclareshape{doublecirc}{%
\nodeparts{text,lower}%
 \savedanchor\centerpoint{%
\pgf@x=.5\wd\pgfnodeparttextbox% 
\pgf@y=.5\ht\pgfnodeparttextbox% 
  }
 \savedanchor\lower{%
\pgf@x=.5\wd\pgfnodepartlowerbox% 
\pgf@y=.5\ht\pgfnodepartlowerbox% 
\advance\pgf@y by -2.0\ht\pgfnodeparttextbox%
\advance\pgf@x by 2.0\wd\pgfnodeparttextbox%
 }
  \anchor{center}{\centerpoint}%
 \anchor{lower}{\lower}
 \backgroundpath{
  \pgfpathcircle{\centerpoint}{\wd\pgfnodeparttextbox}%
  \pgfusepath{stroke}%
  \edef\tikz@temp{\pgfkeysvalueof{/tikz/lower color}}%
  \ifx\tikz@temp\pgfutil@empty\relax
  \else
  \pgfsetstrokecolor{\tikz@temp}%
  \fi
  \pgfpathcircle{\lower}{1.5\wd\pgfnodepartlowerbox}%
  \pgfusepath{stroke}%
}
  \anchorborder{\centerpoint}
  }
\makeatother

\begin{document}
\begin{tikzpicture}
\node[name=a,shape=doublecirc,style={draw},red] at (0,0) {A\nodepart{lower}{B}};  % Inherit color from .
\node[name=b,shape=doublecirc,style={draw},draw=red] at (2,0) {A\nodepart{lower}{B}};  % Inherit color from draw
\node[name=c,shape=doublecirc,style={draw},draw=red,lower color = blue] at (4,0) {A\nodepart{lower}{B}}; % Specify color with parameter
\node[name=d,shape=doublecirc,style={draw},red] at (6,0) {A\nodepart[blue]{lower}{B}}; % Specify color with parameter
\end{tikzpicture}
\end{document}

enter image description here

EDITS: Further simplification by using \pgfutil@empty and \tikz@temp.

EDIT: As for your additional request: it can be done, however, I could make it only work by making the key global. I put this in this separate code since I am not sure I would recommend doing this. As you see, now the last part also has a color green. (Of course this can be rectified by resetting the color after the background path has been drawn. My point is that this seemingly simple additional feature appears to require hacks that may backfire elsewhere.)

\documentclass{article}
\usepackage{tikz}

\makeatletter
\newbox\pgfnodepartlowerbox 
\newbox\pgfnodeparttextbox  
\pgfkeys{/tikz/lower color/.initial=\pgfutil@empty,
/tikz/nodepart draw/.code={\begingroup\globaldefs=1\relax%from https://tex.stackexchange.com/a/371193/121799
\tikzset{lower color=#1}
\endgroup}}% Default value indicates undefined

\pgfdeclareshape{doublecirc}{%
\nodeparts{text,lower}%
 \savedanchor\centerpoint{%
\pgf@x=.5\wd\pgfnodeparttextbox% 
\pgf@y=.5\ht\pgfnodeparttextbox% 
  }
 \savedanchor\lower{%
\pgf@x=.5\wd\pgfnodepartlowerbox% 
\pgf@y=.5\ht\pgfnodepartlowerbox% 
\advance\pgf@y by -2.0\ht\pgfnodeparttextbox%
\advance\pgf@x by 2.0\wd\pgfnodeparttextbox%
 }
  \anchor{center}{\centerpoint}%
 \anchor{lower}{\lower}
 \backgroundpath{
  \pgfpathcircle{\centerpoint}{\wd\pgfnodeparttextbox}%
  \pgfusepath{stroke}%
  \edef\tikz@temp{\pgfkeysvalueof{/tikz/lower color}}%
  \ifx\tikz@temp\pgfutil@empty\relax
  \else
  \pgfsetstrokecolor{\tikz@temp}%
  \fi
  \pgfpathcircle{\lower}{1.5\wd\pgfnodepartlowerbox}%
  \pgfusepath{stroke}%
}
  \anchorborder{\centerpoint}
  }
\makeatother

\begin{document}
\begin{tikzpicture}
\node[name=a,shape=doublecirc,style={draw},red] at (0,0) {A\nodepart{lower}{B}};  % Inherit color from .
\node[name=b,shape=doublecirc,style={draw},draw=red] at (2,0)
{A\nodepart[nodepart draw=green]{lower}{B}};  % Inherit color from draw
\node[name=c,shape=doublecirc,style={draw},draw=red,lower color = blue] at (4,0) {A\nodepart{lower}{B}}; % Specify color with parameter
\node[name=d,shape=doublecirc,style={draw},red] at (6,0) {A\nodepart[blue]{lower}{B}}; % Specify color with parameter
\end{tikzpicture}
\end{document}

enter image description here

| improve this answer | |
  • Thanks. This is a good point. In an earlier version, I simply set the color using my local pgf key. In moving beyond that I had assumed that I'd need separate code to handle the case where the color was set via draw=. It still seems to me that the use of empty here is working around pgfkeys - but the code is certainly much simpler. I also didn't realize that text in node parts could be colored (separately). Thanks again! – democritus Sep 5 '18 at 15:49
  • Thanks again. Using \pgfutil@empty is definitely an improvement. – democritus Sep 5 '18 at 18:38
  • Possibly stating the obvious, it would be nice if: \nodepart[blue,lower color = blue]{lower}{B} or even \nodepart[draw = blue]{lower}{B} had the desired effect. That said, I'm quite content with the current solution: 'natural' default behavior, with the possibility to customize with a bit of work! – democritus Sep 5 '18 at 18:45
  • @democritus Yes, this can be made working, too. However, the solution I found is quite a hack. This has to do with the fact that the node part is inside a group. So one way to go would be to make the key global. However, that has undesired consequences (which are, however, not uncurable). Note also that tikz.code.tex is full of smuggles, which I never fully understood (nor tried to understand). It might well be that you could "smuggle" the key out of the group, but I can't say much on this. – user121799 Sep 5 '18 at 19:28

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.