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. Commented Aug 5, 2021 at 14:21
  • @leonidovich Can you give a specific figure that appears that situation?
    – Black Mild
    Commented Aug 5, 2021 at 15:02
  • @JasperHabicht Didn't know it can work, let me try that one. Commented 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. Commented Aug 5, 2021 at 15:15
  • Node names in general are global. It's not limited to local bounding box. Commented Aug 5, 2021 at 16:23

2 Answers 2

4
+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. Commented 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. Commented Sep 6, 2021 at 12:04
  • @JasperHabicht I removed the second use of /.expanded and added an even simpler solution with a simple macro. Commented Sep 6, 2021 at 22:35
  • I knew it must be something simple! Thank you @PaulGaborit, this works like a charm. Commented 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?) Commented Aug 7, 2021 at 11:49
  • @hpekristiansen Thanks, that's exactly it, those pictures are just examples to demonstrate the problem. Commented 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. Commented Aug 9, 2021 at 12:13

You must log in to answer this question.

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