10

I would very much like to have one object encapsulate a bunch of data, and then pass that object to a function.

For example, a figure might have an URL to a file, a caption, and a width. In Python, one would write:

class Figure():
    url = 'path/to/file.pdf'
    caption = 'This is a figure.'
    width = 0.1  # fraction of \linewidth
    label = 'a_nice_label'
figure = Figure()

Now, I realize the futility of going full OOP with LaTeX, but at least bundling together some data and passing it to a function would be really nice. For example, a simple function taking a figure object would look like:

\newcommand{plotfigure}[1]
    figure = (somehow read from #1)
    \begin{\figure}[hbtp]
      \begin{center}
        \includegraphics[width=figure.width\linewidth]{figure.url}
        \caption{
            figure.caption
            }
        \label{figure.label}
      \end{center}
    \end{figure}

(Clearly this way of accessing attributes makes little sense for LaTeX, but you get the point).

Is there any sort of implementation in LaTeX that allows basic variable-bundling? (Ideally it would even allow methods and inheritance, but already bundling would be so, so useful.)

TeXnician
  • 33,589
  • I don't quite see how it would be useful, as (to stay with your exemple) most figures are only used once in an article or even book. And you'd run into problems with your use of label on a second use of the data object. – remco Mar 29 '18 at 13:47
  • Fair enough: I mean of course some sort of reusable data structure (i.e. where I can overwrite the attributes, or I can instantiate with different values). The goal here is to remove the boiler plate of \begin{figure}..., without creating a function that takes ~20 input arguments.

    In the example I gave it concerns a plot, but if something like this exists I would use it for many other things as well (tables being a particular example).

    – ComboCosmo Mar 29 '18 at 14:06

4 Answers4

7

PGF has an object-oriented engine, that supports - according to the manual

classes, methods, constructors, attributes, objects, object identities, inheritance and overloading.

Here's what it looks like:

\pgfooclass{stamp}{
    % This is the class stamp
    \method stamp() { % The constructor
    }
    \method apply(#1,#2) { % Causes the stamp to be shown at coordinate (#1,#2)
        % Draw the stamp:
        \node [rotate=20,font=\huge] at (#1,#2) {Passed};
    }
}
\pgfoonew \mystamp=new stamp()
\begin{tikzpicture}
    \mystamp.apply(1,2)
    \mystamp.apply(3,4)
\end{tikzpicture}
marczellm
  • 11,809
2

While this is usually probably not the best way of doing things, you could just emulate the namespace of a struct by creating appropriate macro names:

\documentclass{article}

\usepackage{mwe}    % <-- only for example image
\usepackage{xparse} % <-- only for \setfigurestruct

\makeatletter
    \def\setstructfield#1#2#3{\expandafter\def\csname @struct@#1@field@#2\endcsname{#3}}
    \def\getstructfield#1#2{\csname @struct@#1@field@#2\endcsname}
    \def\ifstructhasfield#1#2{\ifcsname @struct@#1@field@#2\endcsname \expandafter\@firstoftwo\else \expandafter\@secondoftwo\fi}
\makeatother

% \setfigurestruct{name}{path}[width as fraction of \linewidth]{caption}[label]
\NewDocumentCommand\setfigurestruct{m O{.8} m m o}{%
    \setstructfield{#1}{width}{#2\linewidth}%
    \setstructfield{#1}{path}{#3}%
    \setstructfield{#1}{caption}{#4}%
    \IfValueT{#5}{%
        \setstructfield{#1}{label}{#5}%
    }%
}

% \plotfigure{figure struct name}
\newcommand*\plotfigure[1]{%
    \begin{figure}%
        \centering
        \includegraphics[width=\getstructfield{#1}{width}]{\getstructfield{#1}{path}}%
        \caption{\getstructfield{#1}{caption}}%
        \ifstructhasfield{#1}{label}{\label{\getstructfield{#1}{label}}}{}%
    \end{figure}%
}

\begin{document}

\setfigurestruct{testfig}{example-image-a}{A test image.}[fig:test]

Look at figure~\ref{fig:test}, please.

\plotfigure{testfig}

\end{document}

The output of the code above

The fields are not really stored as a structure here, but the interface acts as if they were.


A more powerful alternative would be using pgfkeys. You could write some wrapper functions emulating the "feel" of structs (similar to what I did above) if you want that, although I don't really see the sense in that.

schtandard
  • 14,892
2

A riskier alternative to schtandard's solution is to make delimited macros. This will*-ish* stick to the class.property syntax. We just have to define a delimited macro that expects a dot after it:

\documentclass{article}

\usepackage{graphicx}

\makeatletter
\def\newobj#1{%
  \ifcsname #1\endcsname
    \errmessage{Cannot define #1. Control sequence already defined.}
  \fi%
  \expandafter\def\csname obj@#1\endcsname{}%
  \expandafter\def\csname #1\endcsname.##1 {%
    \csname obj@#1@##1\endcsname%
  }%
}
\def\addtoobj#1#2#3{%
  \ifcsname obj@#1\endcsname\else
    \errmessage{Object #1 undefined. Use \string\newobj\{#1\}}
  \fi%
  \expandafter\edef\csname obj@#1@#2\endcsname{#3}%
}
\makeatother

\newcommand{\plotfigure}[1]{%
  \begin{figure}
    \centering
    \edef\Fw{[width=}
    \expandafter\expandafter\expandafter\includegraphics\expandafter\Fw#1.width ]{#1.url }
    \caption{#1.caption }
    \label{#1.label }
  \end{figure}
}

\begin{document}
\pagenumbering{gobble}

\newobj{Figure}
\addtoobj{Figure}{url}{example-image}
\addtoobj{Figure}{caption}{This is a figure.}
\addtoobj{Figure}{width}{0.5\linewidth}
\addtoobj{Figure}{label}{a_nice_label}

\plotfigure{\Figure}

See figure \ref{\Figure.label }

\end{document}

With this approach you create a "class" with \newobj{<name>} and add properties to it with \addtoobj{<name>}{<property>}{<value>}.

When you use, for example, \newobj{Figure}, a macro \Figure.#1␣ ( is a space) is created. This macro expands to \obj@Figure@#1. Then, when you call \addtoobj{Figure}{url}{example-image}, the macro \obj@Figure@url is defined to be example-image. Then, a call to \Figure.url␣ (notice the space!) will expand to example-image.

Be careful, whenever you use this, the \Figure macro must be followed by a ., then by a (valid) property name, then by a space. The space is part of the command!


As for your \plotfigure macro, we can use the \Figure.#1␣ without many problems, except for the optional argument to \includegraphics. The optional argument has to be expanded before the call to \includegraphics, so an \expandafter trickery is needed here. Other than that, \plotfigure{\Figure} works like a charm :)

1

The question is: where do you get these figures?

Prepared figures

If you have prepared figures and want to customize their rendering in the document, the best way is just to copy and paste (sorry) the code for their inclusion, and adjust parameters in place. I find it better for the reasons of

  • simplicity. What is the purpose of separating the "width" in one place and using that value in another one? It looks like an over-complication.

  • flexibility. What if for one figure you want to specify not only the width, but also another parameter? Functions are good for repeated things, but not so much for unique ones.

As I read pgfmanual for version 3.1.5b, Section 99.6, Attributes says:

Attributes can be set and read only inside methods, it is not possible to do so using an object handle. Spoken in terms of traditional object-oriented programming, attributes are always private. You need to define getter and setter methods if you wish to read or modify attributes.

Reading and writing attributes is not done using the “dot-notation” that is used for method calls. This is mostly due to efficiency reasons. Instead, a set of special macros is used, all of which can only be used inside methods.

So it looks a bit complicated (as the manual goes, TeX was not created with OOP in mind). If we return to the example by @marczellm above, I would note these lines:

\begin{tikzpicture}
    \mystamp.apply(1,2)
    \mystamp.apply(3,4)
\end{tikzpicture}

The problem for me is that there are no names of the attributes (like mystamp.apply(x=1,y=2)). For two coordinates one may guess, but if you have a bunch of properties of a figure, it will be very difficult to remember the order of all these parameters, and there will be many bugs if you decide to change them (e.g. add new ones). The same problem I can see with a solution by @schtandard

\setfigurestruct{testfig}{example-image-a}{A test image.}[fig:test]

- even though you can guess the meaning of different arguments, this is not evident from the first sight.

The code by @Phelype Oleinik looks great in this regard:

\newobj{Figure}
\addtoobj{Figure}{url}{example-image}
\addtoobj{Figure}{caption}{This is a figure.}
\addtoobj{Figure}{width}{0.5\linewidth}
\addtoobj{Figure}{label}{a_nice_label}

\plotfigure{\Figure}

See figure \ref{\Figure.label }

But a problem remains: are these 6 lines of code any better than a simple (and, as I said, more flexible) inclusion of a figure in 7 lines?

\begin{\figure}[hbtp]
  \begin{center}
    \includegraphics[width=0.5\linewidth]{example-image}
    \caption{This is a figure.}
    \label{a_nice_label}
  \end{center}
\end{figure}

See figure \ref{a_nice_label}.

The OOP code would be better only if you plan to insert one figure several times. Othewise I'd use other solutions:

  • if you want to reuse an attribute of one figure (probably you don't need to copy all its attributes?), create a variable and use that in the figure and in other places,
  • if you want to reuse an attribute in all figures (say, the width), create a variable and use that for all figures (for example, if you decide later to adjust the width of all figures).

Generated figures

By generated I mean figures, attributes of which (data points on a plot, axis labels, etc.) were produced by a program. I do data analysis and create a lot of figures. I render them in TeX because of the resulting quality. However, TeX is not a language for general programming, so I have to pass computed parameters to its engine.

First I tried to input parameters to TeX during its run, but found that too complicated as their number grew. Then I went to rendering templates, and found that great.

In the very developed area of Web programming templates are used to render html pages with specific data from the program (see e.g. Django tutorials).

The same is possible with LaTeX (or any other text data).

I copy an example from a tutorial on a data analysis framework Lena (which I develop):

% histogram_1d.tex
\documentclass{standalone}
%%% include tikz, pgfplots, etc.
\begin{tikzpicture}
\begin{axis}[]
\addplot [
    const plot,
]
table [col sep=comma, header=false] {\VAR{ output.filepath }};
\end{axis}
\end{tikzpicture}
%%%

The variable to be rendered here is \VAR{ output.filepath } (where output.filepath you provide through the context, which is a simple Python dictionary). A dictionary is an object and in jinja templates its keys (and subkeys) can be accessed as attributes.

Of course, more complicated examples are possible:

\BLOCK{ set var = variable if variable else '' }
\begin{tikzpicture}
\begin{axis}[
    \BLOCK{ if var.latex_name }
        xlabel = { $\VAR{ var.latex_name }$
        \BLOCK{ if var.unit }
            [$\mathrm{\VAR{ var.unit }}$]
        \BLOCK{ endif }
        },
    \BLOCK{ endif }
]
...

The syntax \BLOCK or \VAR looks a bit verbose. It is easier-to-read for html templates, but for LaTeX it had to be changed so that it didn't mess with LaTeX code. However, I very rarely edit and see my templates: once they are general enough, you don't need to add something else.

Even though new constructs complicate LaTeX code, it still looks pretty explicit. Context attributes are read clearly (var.unit), and they won't be confused one for another (as could be with positional arguments).

It is fairly surprising that I didn't have to create any other templates for my 1-dimensional histograms. Even in more complicated cases when I want to add something (like a line) to the plot, I edit that single template: I can conditionally turn on some rendering only for specific plots (which is defined through the context, which I control easily). For 2d plots I use a separate template (because the code for rendering is somewhat different).

For more details about template rendering refer to Jinja 2 documentation or to Lena tutorial.

With all that said, even though I generate many figures in a program, I still do all other stuff manually. I select figures to be included in my documents. I write every caption myself. I also tune their attributes in the final document when needed. For an automatically generated report it would be different, but I would still write pretty much code to customize it, to make it better and more interesting to the reader (even if an occasional one).