Is there a more elegant implementation of this fit-to-bounding-box coordinate transform in TikZ/PGF?

I have a plottable function whose normalized domain is [0,1] and whose normalized range is [-1,1]. I also have two TikZ coordinates, (BOTLEFT) and (TOPRIGHT) defining the bottom left and top right of a bounding box where I would like the plot to appear.

I would like to effect a skewless, rotationless affine linear coordinate transform such that the plot domain/range is fitted exactly to the bounding box. That is, each point (x,y) is mapped to

(x,y) → (BOTLEFT) + ((TOPRIGHT) - (BOTLEFT)) · ((x,y) - (0,-1)) ÷ ((1,1) - (0,-1))

For example, the sine wave

\draw[domain = 0:1, samples = 256] plot (\x,{sin(\x*4*pi r)});


when subject to the transform, should fit exactly inside the bounding box

\draw[red] (-2,5) rectangle (1,8);


if (BOTLEFT) is (-2,5) and (TOPRIGHT) is (1,8).

TikZ supports the xshift, yshift, xscale and yscale properties to implement the coordinate transform. However, computing the xscale and yscale properties manually using coordinate arithmetic would appear to require isolating the x and y components of (BOTLEFT) and (TOPRIGHT). I know this can be done (e.g., by the let directive):

\coordinate (BOTLEFT) at ({-.2},.5);
\coordinate (TOPRIGHT) at (.1,.8);

\draw let
\p1 = (BOTLEFT),
\p2 = (TOPRIGHT) in [domain = 0:1, samples = 256,
xshift = \x1,
xscale = (\x2 - \x1)\pts,
yshift = (\y1 + (\y2 - \y1)/2),
yscale = ((\y2 - \y1)/2)\pts
] plot (\x,{sin(\x*4*pi r)});
\draw[red] (BOTLEFT) rectangle (TOPRIGHT);


where \pts is a macro that expands to *<constant> to convert from pts to the scaling units for the figure, but this seems extremely messy, especially for TikZ/PGF. I can't help but think that TikZ has some kind of built-in to handle this. It would be especially useful if the transform could be scope-d, too. That is,

\begin{scope}[<set transform here>]
\draw[domain = 0:1, samples = 256] plot (\x,{sin(\x*4*pi r)});
<more commands in same coordinate frame>
...
\end{scope}


Is there a more elegant way of handling this in TikZ, or am I stuck with hacking the coordinate math in this way?

Clarification #1

The coordinates (BOTLEFT) and (TOPRIGHT) are computed using expressions involving node anchor points, not numeric literals as in the example.

• \pgfpointanchor{BOTLEFT}{center} will let \pgf@x and \pgf@y be the coordinate of the lower-left corner. That way you can isolate x and y. Oct 12, 2021 at 19:15
• I don't see how this is “extremely messy”. This is a non-trivial transformation and you have to do the math for it in one way or another. Oct 12, 2021 at 19:26
• You can use [shift=(BOTLEFT)] assuming (BOTLEFT) is a coordinate. Scaling not so easy. Oct 12, 2021 at 23:15
• @HenriMenke: It may indeed be the best/only way to do it. But in my experience, any time I wind up hacking coordinate math in this way, TikZ has a much simpler solution.
– COTO
Oct 12, 2021 at 23:16
• @Symbol1: I'll try that in conjunction with Henri's suggestion. Thank you.
– COTO
Oct 12, 2021 at 23:19

This fits (0,0) to (1,1) into (BOTLEFT) to (TOPRIGHT). It involves the fewest math or conversion steps I could think of.

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}

\begin{document}
\begin{tikzpicture}
\coordinate (BOTLEFT) at (-1,-1);
\coordinate (TOPRIGHT) at (2,1);
\fill[black] (BOTLEFT) rectangle (TOPRIGHT);

\coordinate (BOTRIGHT) at (BOTLEFT-|TOPRIGHT);
\coordinate (xscale) at ($(BOTRIGHT)-(BOTLEFT)$);% (3,0)
\coordinate (yscale) at ($(TOPRIGHT)-(BOTRIGHT)$);% (0,2)

\pgfscope
\pgfsetxvec{\pgfpointanchor{xscale}{center}}%
\pgfsetyvec{\pgfpointanchor{yscale}{center}}%
\begin{scope}[shift=(BOTLEFT)]
\node[gray] at (0.5,0.5) {test};
\draw[red] (0,0) rectangle (1,1);
\end{scope}
\endpgfscope
\end{tikzpicture}
\end{document}

• This works as required. Thanks.
– COTO
Oct 13, 2021 at 2:35

I'd just apply the transformation in the plot.

\documentclass{article}
\usepackage{tikz}
\begin{document}

\begin{tikzpicture}[
declare function={
xmin = -.2;
xmax = .1;
ymin = .5;
ymax = .8;
xaxis(\x) = (xmax - xmin) * \x + xmin;
yaxis(\y) = (ymax - ymin) * (\y + 1) / 2 + ymin;
}]

\draw (xmin,ymin) rectangle (xmax,ymax);
\draw plot[domain=0:1,samples=256] ({xaxis(\x)},{yaxis(sin(\x*4*pi r))});

\end{tikzpicture}

\end{document}


• The actual (BOTLEFT) and (TOPRIGHT) coordinates are computed using \coordinate commands with some fairly involved coordinate math (including anchor points on nodes, etc.), not simple numeric values. Is there a simple variation for this case?
– COTO
Oct 12, 2021 at 23:05