6

I'm trying to define a new environment that will draw a rectangle around its content. The problems begin when those environments are nested because the "local bounding box" is a kind of a global thing. Using unique names for outer and inner scopes solves the problem.

\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{tikz}
\usetikzlibrary{fit}

\newenvironment{myscope0}[0]{
    \begin{scope}[draw, local bounding box=bounding box 0]
}{
    \end{scope}
    \node [draw, fit=(bounding box 0)] {};
}

\newenvironment{myscope1}[0]{
    \begin{scope}[draw, local bounding box=bounding box 1]
}{
    \end{scope}
    \node [draw, fit=(bounding box 1)] {};
}

\begin{document}
\begin{tikzpicture}
    \begin{myscope0}
        \begin{myscope1}
            \node at (0,0) {test 1};
        \end{myscope1}

        \begin{myscope1}
            \node at (1,1) {test 2};
        \end{myscope1}
    \end{myscope0}
\end{tikzpicture}
\end{document}

expected

What I want is to avoid defining different environments for every possible nesting level because I'm going to draw complex diagrams using this.

I found another topic that solves a very similar problem: Passing current counter value to stack data structure

They define some sort of a stack to store a counter and use that counter to form a name. The problem is that it does not really work with scopes:

\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{tikz}
\usetikzlibrary{fit}

%Define stack data structure commands (\push, \pop, \splitstack)
\newtoks\mystack
\mystack={\empty}

\def\push#1#2{%
    \def\tmp{{#1}}% 
    \expandafter\expandafter\expandafter%
    #2\expandafter\expandafter\expandafter{\expandafter\tmp\the#2}%
    \ignorespaces
}

\def\pop#1#2{%
    \expandafter\splitstack\the#1\stop{#1}{#2}%
}

\def\splitstack#1#2\stop#3#4{% 
    \def\tmp{#1}
    \ifx\tmp\empty 
    \else
        \def#4{#1}\global#3={#2}%
    \fi
}

%Define bracket pair counting commands (\openbracket, \closebracket)
\newcounter{mycounter}

\newenvironment{myscope}[0]{
    \global\expandafter\edef\csname beginscopenumber\endcsname{\themycounter}%
    \push{\beginscopenumber}{\mystack}%
    \stepcounter{mycounter}%
        
    \begin{scope} [local bounding box=bounding box \beginscopenumber]
}{
    \end{scope}
    \pop{\mystack}{\endscopenumber}%
    \node [draw, fit=(bounding box \endscopenumber)] {};
}

\begin{document}
\begin{tikzpicture}
    \begin{myscope}
        \begin{myscope}
            \node at (0,0) {test 1};
        \end{myscope}

        \begin{myscope}
            \node at (1,1) {test 2};
        \end{myscope}
    \end{myscope}
\end{tikzpicture}
\end{document}

actual

The outer rectangle surrounds only the last nested scope. I will appreciate any help with this. Thank you.

6
  • Welcome to TeX.SX! Would it not be possible to just use a node with a border? A node can contain arbitrary contents if you put a minipage in it for example. Aug 5, 2021 at 14:21
  • @leonidovich Can you give a specific figure that appears that situation?
    – Black Mild
    Aug 5, 2021 at 15:02
  • @JasperHabicht Didn't know it can work, let me try that one. Aug 5, 2021 at 15:14
  • @BlackMild It is actually at the bottom of the post, right after the second piece of the code. What I want to get is the first picture, after the first piece of the code. Aug 5, 2021 at 15:15
  • Node names in general are global. It's not limited to local bounding box. Aug 5, 2021 at 16:23

2 Answers 2

3
+50

Here is a simple method with a counter:

\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{tikz}
\usetikzlibrary{fit}

\newcounter{myscopelevel}
\setcounter{myscopelevel}{0}
\newenvironment{myscope}[0]
{
  \stepcounter{myscopelevel}
  %\typeout{myscopelevel:\themyscopelevel}
  \begin{scope}[local bounding box/.expanded=bounding box \themyscopelevel]
  }{
  \end{scope}
  \node [draw, fit=(bounding box \themyscopelevel)] {};
  \addtocounter{myscopelevel}{-1}
}

\begin{document}
\begin{tikzpicture}
    \begin{myscope}
        \begin{myscope}
            \node at (0,0) {test 1};
        \end{myscope}

        \begin{myscope}
            \node at (1,1) {test 2};
        \end{myscope}
    \end{myscope}
\end{tikzpicture}
\end{document}

And here's an even simpler version using the locality of a macro within a group:

\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{tikz}
\usetikzlibrary{fit}

\pgfmathsetmacro\myscopelevel{0}
\newenvironment{myscope}[0]
{
  \pgfmathsetmacro\myscopelevel{int(\myscopelevel+1)}
  %\typeout{myscopelevel:\myscopelevel}
  \begin{scope}[local bounding box/.expanded=bounding box \myscopelevel]
  }{
  \end{scope}
  \node [draw, fit=(bounding box \myscopelevel)] {};
}

\begin{document}
\begin{tikzpicture}
    \begin{myscope}
        \begin{myscope}
            \node at (0,0) {test 1};
        \end{myscope}

        \begin{myscope}
            \node at (1,1) {test 2};
        \end{myscope}
    \end{myscope}
\end{tikzpicture}
\end{document}
4
  • Could you maybe briefly explain why this is working? I guess, it has to do with expansion, as you added \.expanded to local bounding box. Sep 6, 2021 at 11:37
  • 2
    @JasperHabicht With the ./expanded handler, the name of the local bounding box with the value of the counter is immediately evaluated and stored for its future usage (the coutner can be modified by a nested environment before its use). The second use of /.expanded is not strictly necessary. Sep 6, 2021 at 12:04
  • @JasperHabicht I removed the second use of /.expanded and added an even simpler solution with a simple macro. Sep 6, 2021 at 22:35
  • I knew it must be something simple! Thank you @PaulGaborit, this works like a charm. Sep 8, 2021 at 10:18
1

One of the main principle of TikZ drawing is: putting nodes and pics along the paths. There is so-called balance between them: node is with full anchors and names, limited shapes; and pic is with any shape but "hackery" anchors and names.

Your question can be answered via like-node pic as described in this answer. I define a pic with 3 parameters: #1 is the text for the above right, #2 is the text for the below left, #3 is the name of the covering node that can be referred later.

Hope this helps! PS: local bounding box is always of rectangle shapes, so it is limited.

enter image description here

\documentclass[tikz,border=5mm]{standalone}
\begin{document}
\begin{tikzpicture}
\tikzset{pics/leonidovic/.style args=
{above right #1 below left #2 nodename #3}{code={%
\path 
(0,0) node[draw,minimum height=15mm,minimum width=20mm] (#3) {}
(#3.north east)+(-.6,-.35) node[draw,minimum height=5mm,minimum width=10mm]{#1}
(#3.south west)+(.6,.35) node[draw,minimum height=5mm,minimum width=10mm]{#2};%
}}} 
        
\path
(0,0) pic{leonidovic=above right test1 below left test2 nodename A}
(5,2) pic{leonidovic=above right test3 below left test4 nodename B}
;
\draw (A)--(B);
\draw[->] (A) -| (B);
\end{tikzpicture}
\end{document}  
3
  • This is not how understand OP question. OP writes "every possible nesting level". A node placed "above right" and "below left" is just an example for something that needs to be boxed!? (I can not solve the problem! - is it solvable?) Aug 7, 2021 at 11:49
  • @hpekristiansen Thanks, that's exactly it, those pictures are just examples to demonstrate the problem. Aug 9, 2021 at 11:46
  • 1
    I'm developing a package for building diagrams like this one: c4model.com/img/bigbankplc-LiveDeployment.png I have completed almost all I need: new shapes, styles, commands, and environments. Everything works perfectly except for one thing - those bounding rectangles, called Deployment Nodes. The only way to create them I can see is using scopes and a local bounding box. But as I stated, they mess up when nested. Aug 9, 2021 at 12:13

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.