3

I want to generate TikZ \pics dynamically. All information I need to setup the pic (name, x-coord, y-coord) are given in a tabular.

Is there a way to inject these information to the TikZ environment and draw them? Otherwise I always have to synchronize the graphical information in TikZ and the text information in my tabular environment.

I'm also open to define these information in a complete other format and then inject it to the tabular AND TikZ environment. I just want to keep a single source of truth.

\documentclass{article}%
\usepackage[a3paper,left=10mm,right=10mm,top=10mm,bottom=10mm]{geometry}%
\usepackage{courier}%
\usepackage[T1]{fontenc}%
\usepackage[parfill]{parskip}%
\usepackage[utf8]{inputenc}%
\usepackage{caption}%
\usepackage{graphicx}%
\usepackage{tikz}%
\tikzset{
PowerSupply/.pic={
\node{\includegraphics[scale=0.05,angle=270]{powerSupply.png}};},
}

\begin{document} \section{Electrical Installation Plan} \subsection{Component List}

\begin{center} \begin{tabular}{|c|c|c|c|} \hline \textbf{ID} & \textbf{Description} & \textbf{X-Pos} & \textbf{Y-Pos} \ \hline A1F5F & PowerSupply1 & 3400 & 2000 \ \hline %Input 1 7ZF3I & PowerSupply2 & 3400 & 2500 \ \hline %Input 2 \hline \end{tabular} \end{center}

\pagenumbering{gobble} \begin{figure}[h!] \centering \scalebox{2.0}{ \begin{tikzpicture}[x=0.01mm, y=-0.01mm] \node[inner sep=0pt] (ground_floor) {\includegraphics[width=99.0mm,height=107.4mm]{groundFloor.png}}; \tikzset{shift=(ground_floor.north west)}

\draw[blue, ->, line width=1pt] (0,0) -- (10000,0); \draw[blue, line width=1pt] (10000,0) --node[scale=0.5,black,above=1mm,align=center]{[mm]}(10000,0); \foreach \x in {0,1000,...,9000} \draw[blue, line width=1pt] (\x,-100) --node[scale=0.5,black,above=1mm,align=center]{\x} (\x,100);

\draw[blue, ->, line width=1pt] (0,0) -- (0,11000); \draw[blue, line width=1pt] (0,11000) --node[scale=0.5,black,left=1mm,align=center]{[mm]}(0,11000); \foreach \y in {0,1000,...,10000} \draw[blue, line width=1pt] (-100,\y) --node[scale=0.5,black,left=1mm,align=center]{\y} (100,\y);

\pic at (3450,2000) {PowerSupply}; %This must be generated automatically from line 2 (ID: A1F5F) \pic at (3450,2500) {PowerSupply}; %This must be generated automatically from line 3 (ID: 7ZF3I) \end{tikzpicture}} \caption{Ground Floor} \end{figure} \vfill \clearpage \end{document}

Example

EXAMPLE IMAGES

powerSupply groundFloor

PascalS
  • 826

2 Answers2

4

If we are using OpTeX, we don't need to use TikZ, because there is \puttext macro. The powerSupply.png image is saved to the \pwbox and this box is copied (and rotated) in the \fornum loop.

\newcount\pwtotal
\def\d #1, #2, #3, #4, #5 {\advance\pwtotal by1
   \sxdef{\pws:id}{#1}\sxdef{\pws:d}{#2}\sxdef{\pws:x}{#3}\sxdef{\pws:y}{#4}\sxdef{\pws:r}{#5}%
}
\def\pws{pws:\the\pwtotal}

\def\putpw#1{\pdfsave\pdfrotate{#1}\copy\pwbox\pdfrestore} \newdimen\pwunit \pwunit=.01mm

%% Save power supply image:

\newbox\pwbox \setbox\pwbox=\hbox to0pt{\hss \picw=5mm \vbox to0pt{\inspic{powerSupply.png}\vss}\hss}

%% Save data:

\d A1F5F, PowerSupply1, 3590, 2000, 270 \d 7ZF3I, PowerSupply2, 3590, 2500, 270 \d O7PYQ, PowerSupply3, 4250, 6070, 180

%% Print table:

\noindent\hfil\table{|c|c|c|c|}{\crl \bf Id & \bf Description & \bf X-Pos & \bf Y-pos \crl \fornum 1..\pwtotal \do{\cs{pws:#1:id}&\cs{pws:#1:d}&\cs{pws:#1:x}&\cs{pws:#1:y}\crl} } \bigskip

%% Draw final image:

\noindent\hfil\vbox{ \picwidth=99mm \picheight=107.4mm \vbox to0pt{\inspic{groundFloor.png}\vss} % load background image \fornum 1..\pwtotal \do{ \puttext \cs{pws:#1:x}\pwunit -\cs{pws:#1:y}\pwunit {\putpw{\cs{pws:#1:r}}} } \kern \picheight } \bye

wipet
  • 74,238
2

You can make use of the pgfplotstable package (which loads pgfplots and tikz in the background) and read in a CSV file which you can then process and use to place \pics or whatever you want to do. I'd probably add a column for the rotation, so you can place the \pic at other places as well.

For the following MWE, I also included parts of my answer to your other question:

\documentclass{article}
\usepackage[a4paper, margin=10mm]{geometry}
\usepackage{pgfplotstable}
\pgfplotsset{compat=newest}

\begin{filecontents}{data.csv} id, name, x-coord, y-coord, rotation A1F5F, PowerSupply1, 3590, 2000, 270 7ZF3I, PowerSupply2, 3590, 2500, 270 O7PYQ, PowerSupply3, 4250, 6070, 180 \end{filecontents}

\pgfplotstableread[col sep=comma]{data.csv}{\csvdata} \pgfplotstablegetrowsof{\csvdata} \pgfmathtruncatemacro\CSVDataRows{\pgfplotsretval-1}

\tikzset{ PowerSupply/.pic={ \node[anchor=north, inner sep=0pt] {\includegraphics[scale=0.09]{powerSupply}}; }, }

\begin{document} \pagenumbering{gobble}

\section{Electrical Installation Plan} \subsection{Component List}

\begin{center} \pgfplotstabletypeset[ col sep=comma, columns={id, name, x-coord, y-coord}, columns/id/.style={ column name=ID, string type }, columns/name/.style={ column name=Description, string type }, columns/x-coord/.style={ column name=X-Pos }, columns/y-coord/.style={ column name=Y-Pos, column type/.add={}{|} }, column type/.add={|}{}, after row={\hline}, every head row/.style={before row=\hline}, ]{data.csv} \end{center}

\begin{figure}[h!] \centering \begin{tikzpicture}[x=0.01mm, y=-0.01mm] \node[inner sep=0pt] (ground_floor)
{\includegraphics[width=99.0mm, height=107.4mm]{groundFloor}};

\begin{scope}[shift=(ground_floor.north west)]
    \draw[blue, ->, line width=1pt] (0,0) -- (10000,0);
    \draw[blue, ->, line width=1pt] (0,0) -- (0,11000);

    \foreach \x in {0,1000,...,9000} {
        \draw[blue, line width=1pt] (\x,-100) -- 
            node[black, above=1mm] {\tiny\x mm} (\x,100);
    }
    \foreach \y in {0,1000,...,10000} {
        \draw[blue, line width=1pt] (-100,\y) --
            node[black, left=1mm]{\tiny\y mm} (100,\y);
    }

    \foreach \row in {0,...,\CSVDataRows} {
        \pgfplotstablegetelem{\row}{x-coord}\of{\csvdata}
        \pgfmathsetmacro{\x}{\pgfplotsretval}
        \pgfplotstablegetelem{\row}{y-coord}\of{\csvdata}
        \pgfmathsetmacro{\y}{\pgfplotsretval}
        \pgfplotstablegetelem{\row}{rotation}\of{\csvdata}
        \pgfmathsetmacro{\r}{\pgfplotsretval}
        \pic[rotate=\r, transform shape] at (\x,\y) {PowerSupply};
    }
\end{scope}

\end{tikzpicture} \caption{Ground Floor} \end{figure} \vfill \clearpage \end{document}

enter image description here


Extension to add a label to the node. Since you want to use a node to include the image, you need to add transform shape to the \pic which, however, would also rotate a label attached to the node. If you want the label not to be rotated, you need to un-rotate it.

I extended the \pic code so that the \pic can now take arguments. These can be used to add a label and position this label to the parent node.

\documentclass{article}
\usepackage[a4paper, margin=10mm]{geometry}
\usepackage{pgfplotstable}
\pgfplotsset{compat=newest}

\begin{filecontents}{data.csv} id, name, x-coord, y-coord, rotation A1F5F, PowerSupply1, 3590, 2000, 270 7ZF3I, PowerSupply2, 3590, 2500, 270 O7PYQ, PowerSupply3, 4250, 6070, 180 \end{filecontents}

\pgfplotstableread[col sep=comma]{data.csv}{\csvdata} \pgfplotstablegetrowsof{\csvdata} \pgfmathtruncatemacro\CSVDataRows{\pgfplotsretval-1}

\tikzset{ pics/PowerSupply/.style={ code={ \tikzset{PowerSupply/.cd, #1} \node[anchor=north, inner sep=0pt] (-p) {\includegraphics[scale=0.09]{powerSupply}}; \node[ inner sep=0pt, anchor={90+\pgfkeysvalueof{/tikz/PowerSupply/label position}}, rotate={-1*\pgfkeysvalueof{/tikz/PowerSupply/label position}} ] at (-p.south) {\tiny \pgfkeysvalueof{/tikz/PowerSupply/label}}; } }, PowerSupply/label/.initial={}, PowerSupply/label position/.initial={0} }

\begin{document} \pagenumbering{gobble}

\section{Electrical Installation Plan} \subsection{Component List}

\begin{center} \pgfplotstabletypeset[ col sep=comma, columns={id, name, x-coord, y-coord}, columns/id/.style={ column name=ID, string type }, columns/name/.style={ column name=Description, string type }, columns/x-coord/.style={ column name=X-Pos }, columns/y-coord/.style={ column name=Y-Pos, column type/.add={}{|} }, column type/.add={|}{}, after row={\hline}, every head row/.style={before row=\hline}, ]{data.csv} \end{center}

\begin{figure}[h!] \centering \begin{tikzpicture}[x=0.01mm, y=-0.01mm] \node[inner sep=0pt] (ground_floor)
{\includegraphics[width=99.0mm, height=107.4mm]{groundFloor}};

\begin{scope}[shift=(ground_floor.north west)]
    \draw[blue, ->, line width=1pt] (0,0) -- (10000,0);
    \draw[blue, ->, line width=1pt] (0,0) -- (0,11000);

    \foreach \x in {0,1000,...,9000} {
        \draw[blue, line width=1pt] (\x,-100) -- 
            node[black, above=1mm] {\tiny\x mm} (\x,100);
    }
    \foreach \y in {0,1000,...,10000} {
        \draw[blue, line width=1pt] (-100,\y) --
            node[black, left=1mm]{\tiny\y mm} (100,\y);
    }

    \foreach \row in {0,...,\CSVDataRows} {
        \pgfplotstablegetelem{\row}{x-coord}\of{\csvdata}
        \pgfmathsetmacro{\x}{\pgfplotsretval}
        \pgfplotstablegetelem{\row}{y-coord}\of{\csvdata}
        \pgfmathsetmacro{\y}{\pgfplotsretval}
        \pgfplotstablegetelem{\row}{rotation}\of{\csvdata}
        \pgfmathsetmacro{\r}{\pgfplotsretval}
        \pgfplotstablegetelem{\row}{id}\of{\csvdata}
        \pgfmathsetmacro{\i}{"\pgfplotsretval"}
        \pic[rotate=\r, transform shape] at (\x,\y) {
            PowerSupply={
                label={\i},
                label position={\r}
            }
        };
    }
\end{scope}

\end{tikzpicture} \caption{Ground Floor} \end{figure} \vfill \clearpage \end{document}

enter image description here


You can also rename the \pic to, say, ElectricComponent and add another column to your list for the type of the component. Then you can use the same \pic to place different pictures:

\documentclass{article}
\usepackage[a4paper, margin=10mm]{geometry}
\usepackage{pgfplotstable}
\pgfplotsset{compat=newest}

\begin{filecontents}{data.csv} id, name, type, x-coord, y-coord, rotation A1F5F, PowerSupply1, PowerSupply, 3590, 2000, 270 7ZF3I, PowerSupply2, PowerSupply, 3590, 2500, 270 O7PYQ, PowerSupply3, PowerSupply, 4250, 6070, 180 A7HAZ, Switch1, singleSwitch, 5700, 6070, 180 \end{filecontents}

\pgfplotstableread[col sep=comma]{data.csv}{\csvdata} \pgfplotstablegetrowsof{\csvdata} \pgfmathtruncatemacro\CSVDataRows{\pgfplotsretval-1}

\tikzset{ pics/ElectricComponent/.style={ code={ \tikzset{ElectricComponent/.cd, #1} \node[anchor=north, inner sep=0pt] (-p) {\includegraphics[scale=0.09]{\pgfkeysvalueof{/tikz/ElectricComponent/type}}}; \node[ inner sep=0pt, anchor={90+\pgfkeysvalueof{/tikz/ElectricComponent/label position}}, rotate={-1*\pgfkeysvalueof{/tikz/ElectricComponent/label position}} ] at (-p.south) {\tiny \pgfkeysvalueof{/tikz/ElectricComponent/label}}; } }, ElectricComponent/type/.initial={PowerSupply}, ElectricComponent/label/.initial={}, ElectricComponent/label position/.initial={0} }

\begin{document} \pagenumbering{gobble}

\section{Electrical Installation Plan} \subsection{Component List}

\begin{center} \pgfplotstabletypeset[ col sep=comma, columns={id, name, x-coord, y-coord}, columns/id/.style={ column name=ID, string type }, columns/name/.style={ column name=Description, string type }, columns/x-coord/.style={ column name=X-Pos }, columns/y-coord/.style={ column name=Y-Pos, column type/.add={}{|} }, column type/.add={|}{}, after row={\hline}, every head row/.style={before row=\hline}, ]{data.csv} \end{center}

\begin{figure}[h!] \centering \begin{tikzpicture}[x=0.01mm, y=-0.01mm] \node[inner sep=0pt] (ground_floor)
{\includegraphics[width=99.0mm, height=107.4mm]{groundFloor}};

\begin{scope}[shift=(ground_floor.north west)]
    \draw[blue, ->, line width=1pt] (0,0) -- (10000,0);
    \draw[blue, ->, line width=1pt] (0,0) -- (0,11000);

    \foreach \x in {0,1000,...,9000} {
        \draw[blue, line width=1pt] (\x,-100) -- 
            node[black, above=1mm] {\tiny\x mm} (\x,100);
    }
    \foreach \y in {0,1000,...,10000} {
        \draw[blue, line width=1pt] (-100,\y) --
            node[black, left=1mm]{\tiny\y mm} (100,\y);
    }

    \foreach \row in {0,...,\CSVDataRows} {
        \pgfplotstablegetelem{\row}{x-coord}\of{\csvdata}
        \pgfmathsetmacro{\x}{\pgfplotsretval}
        \pgfplotstablegetelem{\row}{y-coord}\of{\csvdata}
        \pgfmathsetmacro{\y}{\pgfplotsretval}
        \pgfplotstablegetelem{\row}{rotation}\of{\csvdata}
        \pgfmathsetmacro{\r}{\pgfplotsretval}
        \pgfplotstablegetelem{\row}{type}\of{\csvdata}
        \pgfmathsetmacro{\t}{"\pgfplotsretval"}
        \pgfplotstablegetelem{\row}{id}\of{\csvdata}
        \pgfmathsetmacro{\i}{"\pgfplotsretval"}
        \pic[rotate=\r, transform shape] at (\x,\y) {
            ElectricComponent={
                type={\t},
                label={\i},
                label position={\r}
            }
        };
    }
\end{scope}

\end{tikzpicture} \caption{Ground Floor} \end{figure} \vfill \clearpage \end{document}

enter image description here


Adding the icons to the table can be achieved without any TikZ magic:

\documentclass{article}
\usepackage[a4paper, margin=10mm]{geometry}
\usepackage{pgfplotstable}
\pgfplotsset{compat=newest}

\begin{filecontents}{data.csv} id, name, type, x-coord, y-coord, rotation A1F5F, PowerSupply1, powerSupply, 3590, 2000, 270 7ZF3I, PowerSupply2, powerSupply, 3590, 2500, 270 O7PYQ, PowerSupply3, powerSupply, 4250, 6070, 180 A7HAZ, Switch1, singleSwitch, 5700, 6070, 180 \end{filecontents}

\pgfplotstableread[col sep=comma]{data.csv}{\csvdata}

\begin{document} \pagenumbering{gobble}

\begin{center} \pgfplotstabletypeset[ col sep=comma, columns={id, name, type, x-coord, y-coord}, columns/id/.style={ column name=ID, string type }, columns/name/.style={ column name=Description, string type }, columns/type/.style={ column name=Type, assign cell content/.code={ \pgfkeyssetvalue{/pgfplots/table/@cell content}{ \includegraphics[scale=0.09]{##1} } } }, columns/x-coord/.style={ column name=X-Pos }, columns/y-coord/.style={ column name=Y-Pos, column type/.add={}{|} }, column type/.add={|}{}, after row={\hline}, every head row/.style={before row=\hline}, ]{data.csv} \end{center}

\end{document}

enter image description here

  • @PascalS Note that the part between \begin{filecontents} and \end{filecontents} will create a file data.csv in the same directory where the .tex file is stored. Once this file exists, your operating system might block overwriting this file. But you can also delete the part between \begin{filecontents} and \end{filecontents} and do changes in the file data.csv directly which should work in any case. – Jasper Habicht Feb 17 '24 at 20:11
  • That's it! Thank you! – PascalS Feb 17 '24 at 20:12
  • 1
    @PascalS I added a link to the pgfplotstable package at CTAN where you can also find its documentation. The package has a lot of functionality. You may want to style the table differently or format the numbers in another way. – Jasper Habicht Feb 17 '24 at 20:14
  • Can you also tell me, how I can add a text to the generated \pic? This works it works rudimentarily '\pic[rotate=\r,pic text options=below,pic text=\tiny ABCDE] at (\x,\y) {PowerSupply};' But how can I set the text to the left or right side, depending on the given rotation angle? The final solution would be to add the ID value from the tabulator environment next to the \pic. – PascalS Feb 17 '24 at 22:30
  • 1
    @PascalS I added a variant to the original code. – Jasper Habicht Feb 17 '24 at 22:55
  • Works pretty well! One last question: Right now, the name PowerSupply is directly part of the TikZ picture environment in \pic[rotate=\r, transform shape] at (\x,\y) {PowerSupply={label={\i},label position={\r}}}; and for this example this is absolutely fine. But I would like to use this setup for different floors with different kind of electrical symbols where not everywhere exists every electrical symbol. Is this possible? If this is too much here, I can also create a new question :) – PascalS Feb 18 '24 at 05:20
  • @PascalS Yes, you can of course do that. See my second edit. – Jasper Habicht Feb 18 '24 at 06:59
  • How can I feed my ElectricComponent with different images? I got the error message, that PowerSupply and singleSwitch can not be found. So the easiest way would be to inject these graphic links via the css file or? – PascalS Feb 18 '24 at 08:31
  • @PascalS In my last edit I added a new column to the CSV file where you can put the type. This is essentially just the name of the file. So, if you put singleSwitch as type, there should be a file named singleSwitch.png or singleSwitch.jpg in the same directory. – Jasper Habicht Feb 18 '24 at 08:52
  • 1
    Got it! {\includegraphics[scale=0.09]{../Images/ElectricComponents/\pgfkeysvalueof{/tikz/ElectricComponent/type}}}; I didn't recognize that the images are stored in a sub folder. Thank you so much, I really appreciate! – PascalS Feb 18 '24 at 09:04
  • @PascalS Sure, see my last edit. – Jasper Habicht Feb 22 '24 at 21:41
  • Crazy... You are faster with the solution than I can refactor my comment :D Looks great! – PascalS Feb 22 '24 at 21:50
  • 1
    @PascalS I try to be better than ChatGPT =P – Jasper Habicht Feb 22 '24 at 21:54
  • In case i have more than one component at the same X and Y Pos (PowerSupply and Switch e.g.) Is it possible to automatically add some space and draw the second componend next to the first one? – PascalS Feb 22 '24 at 22:13
  • I mean, I could also add it by hand, but then the „documented“ position in my table would be wrong. – PascalS Feb 22 '24 at 22:14
  • @PascalS Like stacking? Well, I think, you could just add a stack shift option to the \pic (with ElectricComponent/stack shift/.initial={0}) and then add yshift={\pgfkeysvalueof{/tikz/ElectricComponent/stack shift}*-7mm} as option to the node in the pic or so. Then you can say stack shift=1 and the symbol would be "on top" (or at the right/left/bottom, depending on the rotation) of the other. Change 5cm to any size that fits. – Jasper Habicht Feb 23 '24 at 06:06
  • Ok, shifting is working now, depending of a shiftLevel Column I can also setup the stack depth. But depending on the rotation parameter the same shift length has different impact. Depending if the text is vertically or horizontally orientated. => Can we set the label orientation always to 90 degrees to the image? – PascalS Feb 23 '24 at 15:13
  • 1
    @PascalS You can remove (or at least change) rotate={-1*\pgfkeysvalueof{/tikz/ElectricComponent/label position}. Currently it reverts the rotation of the image. If you delete it, the text will rotate together with the image. – Jasper Habicht Feb 23 '24 at 15:17
  • Works now, just for the case that the rotation is 180° it would be more pretty, to set the label rotation to 0°, to have the text always readable. Something like this: \newcommand{\labelRotation}[1] { \ifthenelse{\equal{#1}{180}}{0}{#1} } But this seems not be useable within the tikz ElectricComponent={ type={\t},label={\i}, label position={\labelRotation{\r}} – PascalS Feb 23 '24 at 17:30
  • @PascalS Try PGF‘s own ifthenelse: https://tikz.dev/math-parsing#sec-94.3 – Jasper Habicht Feb 23 '24 at 21:44
  • 1
    I got it! Introducing a new label rotation variable did the trick for me! \ifthenelse{\r = 180} {\pgfmathtruncatemacro{\lr}{180}} {\pgfmathtruncatemacro{\lr}{0}} \pic[rotate=\r, transform shape] at (\x,\y) { ElectricComponent={ type={\t}, label={\i}, label position={\lr}, stack shift={\s} }};} Thanks again for your never ending support! – PascalS Feb 24 '24 at 07:46
  • Maybe you can have a short look to this follow up question :) – PascalS Feb 26 '24 at 13:51