# Coordinate and Canvas Transformations by given angle of rotation and shifting

I'm new to TikZ so I was reading the TikZ documentation. On page 1155 (chaper 108) I found information about transformations. The CTM (coordinate transformation matrix) consist on the four numbers a, b, c and d. So I tried this with a matrix I tested many years ago in postscript. But unfortunately I could not get the desired result. I can't see what I did wrong, so I'm asking for help. The y'- and z'-axis seems to be correct, but the x'-axis is not in the right direction. Here is my MWE:

\documentclass[tikz]{standalone}

\def\Angtheta{0}%       Rotation arround z-Axis bzw. vertical axis through (1cm, 0, 0) -> see x-shift
\def\Angphi{60}%        Rotation to x-y-plane: 90-\phi
\def\AngLG{45}%         Angle for the x-Axis
\def\VK{0.5*sqrt(2)}%   shortening of the x-Axis is 0.5*sqrt(2)
\def\xO{1cm}%           shift in x-direction (in x-y-z-coordinate-system)
\def\yO{0cm}%           shift in y-direction (in x-y-z-coordinate-system)
\def\zO{0cm}%           shift in z-direction (in x-y-z-coordinate-system)

\begin{document}

\begin{tikzpicture}
[%
x={({cos(180-\AngLG)*\VK*1cm},{-sin(180-\AngLG)*\VK*1cm})},% (-0.5cm,-0.5cm) means in this case the same
y={(1cm,0cm)},% (1cm,0cm)
z={(0 cm, 1 cm)}
]

\coordinate (O) at (0,0,0);
\coordinate (B) at (0,2,1.5);
\coordinate (X) at (2,0,0);
\coordinate (Y) at (0,2,0);
\coordinate (Z) at (0,0,1.5);

\draw[->] (O) -- (X) node[pos=1.1]{$x$};
\draw[->] (O) -- (Y) node[pos=1.1]{$y$};
\draw[->] (O) -- (Z) node[pos=1.1]{$z$};

\begin{scope}
% \pgftransformreset% seems to have no effect here
\pgftransformcm {cos(\Angtheta)+\VK*sin(\Angtheta)*cos(\AngLG)}%    a in xT = a*x + c*y + s
{\VK*sin(\Angtheta)*sin(\AngLG)}%                   b in yT = b*x + d*y + t
{-sin(\Angphi)*sin(\Angtheta)+\VK*sin(\Angphi)*cos(\Angtheta)*cos(\AngLG)}% c
{cos(\Angphi)+\VK*sin(\Angphi)*cos(\Angtheta)*sin(\AngLG)}% d
{\pgfpoint
{\yO-\VK*\xO*cos(\AngLG)}%  s
{\zO-\VK*\xO*sin(\AngLG)}%  t
}%
\pgfgettransformentries{\macroA}{\macroB}{\macroC}{\macroD}{\macroE}{\macroF}
\pgftransformreset
\pgfsettransformentries{\macroA}{\macroB}{\macroC}{\macroD}{\macroE}{\macroF}
\pgflowlevelsynccm %%  command to concatenate the canvas transformation matrix
%  with the current coordinate transformation matrix (CTM)

\draw[->,thick,color=blue] (O) -- (X) node[pos=1.1]{$x'$};
\draw[->,thick,color=blue] (O) -- (Y) node[pos=1.1]{$y'$};
\draw[->,thick,color=blue] (O) -- (Z) node[pos=1.1]{$z'$};
\end{scope}
\end{tikzpicture}
\end{document}


And here is an NMWE:

\documentclass[tikz]{standalone}
\usetikzlibrary{angles,arrows,arrows.meta,patterns}

\def\Angtheta{80}%      Rotation arround z-Axis bzw. vertical axis through (1cm, 0, 0) -> see x-shift
\def\Angphi{60}%        Rotation to x-y-plane: 90-\phi
\def\AngLG{45}%         Angle for the x-Axis
\def\VK{0.5*sqrt(2)}%   shortening of the x-Axis is 0.5*sqrt(2)
\def\xO{1cm}%           shift in x-direction (in x-y-z-coordinate-system)
\def\yO{0cm}%           shift in y-direction (in x-y-z-coordinate-system)
\def\zO{0cm}%           shift in z-direction (in x-y-z-coordinate-system)

\begin{document}

\begin{tikzpicture}
[%
x={({cos(180-\AngLG)*\VK*1cm},{-sin(180-\AngLG)*\VK*1cm})},% (-0.5cm,-0.5cm)
y={(1cm,0cm)},% (1cm,0cm)
z={(0 cm, 1 cm)}
]

\coordinate (O) at (0,0,0);
\coordinate (B) at (0,2,1.5);
\coordinate (X) at (2,0,0);
\coordinate (Y) at (0,2,0);
\coordinate (Z) at (0,0,1.5);

\draw[->] (O) -- (X) node[pos=1.1]{$x$};
\draw[->] (O) -- (Y) node[pos=1.1]{$y$};
\draw[->] (O) -- (Z) node[pos=1.1]{$z$};

\begin{scope}
% \pgftransformreset% seems to have no effect here
\pgftransformcm {cos(\Angtheta)+\VK*sin(\Angtheta)*cos(\AngLG)}%    a in xT = a*x + c*y + s
{\VK*sin(\Angtheta)*sin(\AngLG)}%                   b in yT = b*x + d*y + t
{-sin(\Angphi)*sin(\Angtheta)+\VK*sin(\Angphi)*cos(\Angtheta)*cos(\AngLG)}% c
{cos(\Angphi)+\VK*sin(\Angphi)*cos(\Angtheta)*sin(\AngLG)}% d
{\pgfpoint
{\yO-\VK*\xO*cos(\AngLG)}%  s
{\zO-\VK*\xO*sin(\AngLG)}%  t
}%
\pgfgettransformentries{\macroA}{\macroB}{\macroC}{\macroD}{\macroE}{\macroF}
\pgftransformreset
\pgfsettransformentries{\macroA}{\macroB}{\macroC}{\macroD}{\macroE}{\macroF}
\pgflowlevelsynccm %%  command to concatenate the canvas transformation matrix
%  with the current coordinate transformation matrix (CTM)

\draw[->,thick,color=blue] (O) -- (X) node[pos=1.1]{$x'$};
\draw[->,thick,color=blue] (O) -- (Y) node[pos=1.1]{$y'$};
\draw[->,thick,color=blue] (O) -- (Z) node[pos=1.1]{$z'$};

\draw[color=red,pattern=crosshatch,pattern color=gray!20] (O) rectangle (B) ;
\draw[dashed,gray] (O) -- (B) ;

\draw %(Z) -- (B) -- (Y) % draw right angle
pic (right1) [draw=green!50!black,fill=yellow,fill opacity=0.4,angle eccentricity=0.55,
angle radius=5mm,pic text=] {angle = Z--B--Y};
\draw %(X) -- (O) -- (B) % draw angle beta
pic (beta) [->,>={Latex[length=2pt]},draw=cyan!30!black,fill=cyan,fill opacity=0.4,angle eccentricity=0.7,
text opacity=0.6, angle radius=7mm,pic text={\scriptsize$\beta$}] {angle = Y--O--B};

\pgftransformshift{\pgfpoint{1cm}{1.3cm}}%  shifting the text in the new coordinate-system
\pgftext{test}
\end{scope}
\end{tikzpicture}
\end{document}


\gamma is the angle between the negative x-Axis and the y-Axis

shortening coefficient r to shorten the x-axis

Rotation arround z-Axis bzw. vertical axis \theta

Rotation to x-y-plane: 90-\phi

In the next example I choose phi=90 and theta=-90 In this case, the x'-axis should be parallel to the z-axis.

EDIT

More precise formulation of the problem

I need a 3D coordinate system for a parallel projection where the y-axis is to the right, the z-axis is up, and the negative x-axis is at 45° to the y-axis (in some cases this may vary ). I also need a foreshortening factor for the x-axis, which will usually be 0.5*sqrt(2) (sometimes 0.5). Various objects are to be drawn in this coordinate system, e.g. B. a pyramid or planes. The angles should be drawn into these objects in the correct perspective. In this case it is impractical to use the angles theta and phi directly, but these two angles can be calculated from a normal vector or even from 3 points or two direction vectors. In the example shown, a calculation from three points would be appropriate, with one point mapping the origin of coordinates to the vertex of the angle. I have not calculated this systematically in the following example.

I would like a macro that does this transformation by specifying 3 points that were previously defined as nodes (or 2 angles, or 2 direction vectors, or a normal vector).

Code

\documentclass[tikz]{standalone}
\usetikzlibrary{angles,arrows,arrows.meta,patterns,calc}

\def\Angtheta{-45}%     Rotation arround z-Axis bzw. vertical axis through (1cm, 0, 0) -> see x-shift
\def\Angphi{0}%         Rotation to x-y-plane: 90-\phi
\def\AngLG{45}%         Angle for the x-Axis
\def\VK{0.5*sqrt(2)}%   shortening of the x-Axis is 0.5*sqrt(2)
\def\xO{0cm}%           shift in x-direction (in x-y-z-coordinate-system)
\def\yO{0cm}%           shift in y-direction (in x-y-z-coordinate-system)
\def\zO{0cm}%           shift in z-direction (in x-y-z-coordinate-system)

% for schools in german: parallel projection with #1 angle between
% negative x-axis and y-axis and #2=shortening of x-axis
\tikzset{%
3D-KoSys/.code n args={2}{%
\tikzset{%
x={({cos(180-#1)*(#2)*1cm},{-sin(180-#1)*(#2)*1cm})},% (-0.5cm,-0.5cm)
y={(1cm,0cm)},%
z={(0 cm, 1 cm)}%
}%
}%
}

\tikzset{%
projectTP/.code n args={5}{% #1,#2,#3 -> xO,yO,zO shifting;  #4=theta, #5=phi
\pgftransformcm
{cos(#4)+\VK*sin(#4)*cos(\AngLG)}%                      a in xT = a*x + c*y + s
{\VK*sin(#4)*sin(\AngLG)}%                              b in yT = b*x + d*y + t
{-sin(#5)*sin(#4)+\VK*sin(#5)*cos(#4)*cos(\AngLG)}%     c in xT = a*x + c*y + s
{cos(#5)+\VK*sin(#5)*cos(#4)*sin(\AngLG)}%              d in yT = b*x + d*y + t
{\pgfpoint
{#2-\VK*#1*cos(\AngLG)}%                            s in xT = a*x + c*y + s
{#3-\VK*#1*sin(\AngLG)}%                            t in yT = b*x + d*y + t
}%
\pgflowlevelsynccm%
}%
}%

\def\myprojection(#1,#2,#3)#4#5{% #1,#2,#3 -> xO,yO,zO shifting;  #4=theta, #5=phi, see below
projectTP={#1}{#2}{#3}{#4}{#5}%
}

\def\germanrightangle(#1,#2,#3){%
\draw % % draw german right angle
pic (right1) [draw=black!70,fill=gray!50,fill opacity=0.4,angle eccentricity=0.55,
angle radius=2mm,pic text=] {angle = #1--#2--#3};%
}

\def\markmyangle(#1,#2,#3)#4{%
\draw % % draw angle
pic (beta) [->,>={Latex[length=2pt]},draw=black!70,fill=cyan!40,fill opacity=0.4,angle eccentricity=0.7,
text opacity=0.6, angle radius=7mm,pic text={\scriptsize#4}] {angle = #1--#2--#3};%
}

\begin{document}

\begin{tikzpicture}[%
3D-KoSys={45}{0.5*sqrt(2)},
]

\coordinate (O) at (0,0,0);
\coordinate (A) at (2,0,0);
\coordinate (B) at (2,2,0);
\coordinate (C) at (0,2,0);
\coordinate (M) at (1,1,0);
\coordinate (S) at (1,1,2.5);
\coordinate (X) at (3,0,0);
\coordinate (Y) at (0,3,0);
\coordinate (Z) at (0,0,2.5);

% set new origin O'= M', so that x --> 0, y --> y'*cos(45)???, z --> z'
\coordinate (M') at (0,0,0);%    (0, 0 , 0)
\coordinate (S') at (0,0,2.5);%  (0, 0, 2.5)
\coordinate (C') at (0,1.4142,0);%  (0, y*cos(45), 0)

\draw[->] (O) -- (X) node[pos=1.1]{$x$};
\draw[->] (O) -- (Y) node[pos=1.1]{$y$};
\draw[->] (O) -- (Z) node[pos=1.1]{$z$};

\draw[gray] (O) -- (A) -- (B) -- (C) -- cycle ;
\draw[gray,dashed] (O) -- (S) (M) -- (S) (A) -- (C) (O) -- (B);
\draw[gray] (A) -- (S) (B) -- (S) (C) -- (S);
\fill[yellow,fill opacity=0.4] (A) -- (B) -- (S) -- cycle ;

% set new origin O'= M', so that x --> 0, y --> y'*cos(45)???, z --> z'
\coordinate (M') at (0,0,0);%    (0, 0 , 0)
\coordinate (S') at (0,0,2.5);%  (0, 0, 2.5)
\coordinate (C') at (0,1.4142,0);%  (0, y*cos(45), 0)

\begin{scope}[projectTP={1cm}{1cm}{0cm}{45}{0}]
\markmyangle(S',C',M'){$\beta$}
\germanrightangle(C,M',S')
%    \pgftransformshift{\pgfpoint{1cm}{1.3cm}}%  shifting the text in the new coordinate-system
\pgftext{\tiny O}
\end{scope}

\coordinate (B'') at (0,1.4142,0);%  (0, y*cos(-45), 0)

\begin{scope}[projectTP={1cm}{1cm}{0cm}{-45}{0}]
\markmyangle(S',B'',M'){$\alpha$}
% \germanrightangle(B'',M',S')
\end{scope}

\coordinate (A''') at (0,0,0);%  (0, y*cos(-45), 0)
\coordinate (B''') at (0,2,0);%  (0, y*cos(-45), 0)
\coordinate (S''') at (0,1,2.6926);%  (0, z/sin(68,1986), 0)

\begin{scope}[projectTP={2cm}{0cm}{0cm}{0}{21.8}]% arctan(2.5/1)=68.1986
\markmyangle(B''',A''',S'''){$\delta$}
\pgftransformshift{\pgfpoint{1cm}{1.2cm}}%  shifting the text in the new coordinate-system
\pgftext{\shortstack{\tiny Thank you!\\
\tiny Qrrbrbirlbel\\ \tiny + Andrew S.}}
\end{scope}

\end{tikzpicture}

\end{document}


Thank you for helping!

• "But unfortunately I could not get the desired result." What is your desired result? The \pgftransformreset at the begin of your scope has no effect because no transformations have been used before. The keys x, y and z only setup the xyz coordinate system. Commented Aug 8, 2022 at 10:28
• @Qrrbrbirlbel thanks for that explanation for \pgftransformreset. I thought the setup of x, y and z is an transformation. The desired result is the correct rotation of all three axis. In the picture you can see, that the x-axis is not correct transformed (for example with \phi = 60). Commented Aug 8, 2022 at 11:03
• \tikz[x={(1cm,1cm)}, y={(-1cm,1cm)}] {\draw (1cm,0cm) -- (0cm,1cm); \draw[blue] (1,0) -- (0,1);} will show how the xyz coordinate system and its keys work (in blue), coordinates in this system will be converted (yeah, that's kind of a transformation) into the canvas system (the one with units). All transformations ( rotate, shift, \pgftransform…) apply to the canvas system. Commented Aug 8, 2022 at 11:49
• The problem seems to be a wrong \pgftransformcm{a}{b}{c}{d}{\pgfpoint{s}{t}} but I can't find the correct a, b, c and d. Commented Aug 8, 2022 at 12:16
• With your update, I'm not sure whether or not you still are unsure about something or whether you have figured it all out. Your final diagram looks really good. Is there still something to answer here, or is it cleared up now? Commented Aug 9, 2022 at 15:23

I believe what you're trying to achieve won't work in TikZ.

To begin with, your formula only uses X and Y, never Z. The angle γ probably means a rotation about the paper axis (orthogonal to paper x [always to the right] and paper y [always upwards])?

The keys x, y and z only transform a 3D point into a 2D point on your paper and then the PGF transformation takes place.

For example, with a xyz coordinate system setup like

x = (-135, sqrt(2)) = (-1cm, -1cm)
y = ( 0: 1cm)       = ( 1cm,  0cm)
z = (90: 1cm)       = ( 0cm,  1cm)


the coordinates (-1, 0, 0) and (0, 1, 1) are exactly the same:

canvas x = 1cm
canvas y = 1cm


or simply (1cm, 1cm) in the canvas cs.

At that point, neither TikZ nor PGF nor any transformation will be able to distinguish between those two.

In the code below, I converted you formula into a \pgftramsformcmRot macro. Whatever value you choose for the three angle, the black ((-1, 0, 0)) and the red line ((0, 1, 1)) will always be the same.

If you really want to do fancy 3D graphics choose a different tool that is able to achieve this, for example

## Code

\documentclass[tikz]{standalone}
\newcommand*\pgftransformcmRot[4][.70710678118]{% #2 = theta
% #3 = phi
% #4 = gamma
\pgftransformcm {cos(#2)+#1*sin(#2)*cos(#4)}
{#1*sin(#2)*sin(#4)}
{-sin(#3)*sin(#2)+#1*sin(#3)*cos(#2)*cos(#4)}
{cos(#3)+#1*sin(#3)*cos(#2)*sin(#4)}}
\begin{document}
\begin{tikzpicture}[x={(-1cm,-1cm)}, y=(0:1cm), z=(90:1cm)]
\path[->, ultra thick, blue] (0,0,0) edge (1,0,0) edge (0,1,0) edge (0,0,1);

\draw[thick] (0,0,0) -- (-1,0,0);
\draw[red]   (0,0,0) -- ( 0,1,1);
\end{tikzpicture}

\begin{tikzpicture}
\useasboundingbox (-2,-2) (2,2);
\tikzset{x={(-1cm,-1cm)}, y=(0:1cm), z=(90:1cm)}
\pgftransformcmRot{0}{45}{0}{\pgfpointorigin}\pgflowlevelsynccm
\path[->, ultra thick, blue] (0,0,0) edge (1,0,0) edge (0,1,0) edge (0,0,1);

\draw[thick] (0,0,0) -- (-1,0,0);
\draw[red]   (0,0,0) -- ( 0,1,1);
\end{tikzpicture}
\end{document}

• "+1", Thank you for your suggested solution. Commented Aug 9, 2022 at 4:07
• That helped me a lot. I now understand that I made a mistake in reasoning. Commented Aug 9, 2022 at 4:39

It is important to remember that TikZ/PGF is designed as a 2D drawing system and the ability to use 3D coordinates is an extra layer on top. This means that all its methods are set up for 2D technology.

To understand the implications of that, it is worth looking through how TikZ/PGF processes coordinates.

1. A coordinate of the type (x,y) or (x,y,z) is interpreted as x lots of the current x-vector, plus y of the y-vector, and z of the z-vector. These vectors are all 2D so the result is a 2D vector, regardless of whether the original is 2D or 3D.
2. The current coordinate transformation matrix is applied to this vector. This is a 2D matrix and 2D vector (representing the translation).

Now, that has sufficient flexibility to implement any affine transformation from R^3 to R^2 but you can't use its native transformation techniques to manipulate the 3D part of the transformation matrix. You have to specify it manually.

The following code implements 3D rotation matrices (specified by Tait-Bryan angles), with the projection to 2D being given by:

[1 0 1/sqrt(2)]
[0 1 1/sqrt(2)]


This is just an example to show that it is possible. If you wanted to do this more comprehensibly then you could implement actual 3x3 matrix multiplication to make it easier to specify the transformations.

\documentclass{article}
%\url{https://tex.stackexchange.com/q/653314/86}
\usepackage{tikz}

\makeatletter

\tikzset{
show cm/.code={
\pgfgettransform\@cm\show\@cm
},
show xyz/.code={
\edef\@xyz{%
(\the\pgf@xx,\the\pgf@xy), %
(\the\pgf@yx,\the\pgf@yy), %
(\the\pgf@zx,\the\pgf@zy)%
}%
\show\@xyz
},
xy/.code 2 args={%
\tikz@scan@one@point\pgfutil@firstofone#1\relax
\pgf@xa=\pgf@x
\pgf@ya=\pgf@y
\tikz@scan@one@point\pgfutil@firstofone#2\relax
\pgf@yx=\pgf@x
\pgf@yy=\pgf@y
\pgf@xx=\pgf@xa
\pgf@xy=\pgf@ya
},
xyz/.code n args={3}{%
\tikz@scan@one@point\pgfutil@firstofone#1\relax
\pgf@xa=\pgf@x
\pgf@ya=\pgf@y
\tikz@scan@one@point\pgfutil@firstofone#2\relax
\pgf@xb=\pgf@x
\pgf@yb=\pgf@y
\tikz@scan@one@point\pgfutil@firstofone#3\relax
\pgf@zx=\pgf@x
\pgf@zy=\pgf@y
\pgf@yx=\pgf@xb
\pgf@yy=\pgf@yb
\pgf@xx=\pgf@xa
\pgf@xy=\pgf@ya
\pgf@xa=\pgf@x
\pgf@ya=\pgf@y
},
rotate 3D/.code n args={3}{%
\tikzset{
xyz={%
(
{cos(#1) * cos(#2) - sin(#2)/sqrt(2)},
{sin(#1) * cos(#2) - sin(#2)/sqrt(2)},
)
}{%
(
{cos(#1) * sin(#2) * sin(#3) - sin(#1) * cos(#3) + cos(#2) * sin(#3)/sqrt(2)},
{sin(#1) * sin(#2) * sin(#3) + cos(#1) * cos(#3) + cos(#2) * sin(#3)/sqrt(2)},
)
}{%
(
{cos(#1) * sin(#2) * cos(#3) + sin(#1) * sin(#3) + cos(#2) * cos(#3)/sqrt(2)},
{sin(#1) * sin(#2) * cos(#3) - cos(#1) * sin(#3) + cos(#2) * cos(#3)/sqrt(2)},
)
}
}
}
}

\makeatother

\begin{document}
\begin{tikzpicture}[
rotate 3D={20}{40}{10},
show xyz,
show cm,
]

\draw[->] (0,0,0) -- (1,0,0) node[above] {$$x$$};
\draw[->] (0,0,0) -- (0,1,0) node[above] {$$y$$};
\draw[->] (0,0,0) -- (0,0,1) node[above] {$$z$$};

\end{tikzpicture}
\end{document}


It also has a few added extras. The keys show cm and show xyz are diagnostic so you can see what the current vectors and matrix are in the log file or on the console. The keys xy and xyz allow you to specify the two or three vectors all in one go (there's an issue with the fact that TikZ provides a way only to specify one vector at a time - for example, x={(0,1)}, y={(1,0)} should swap x and y` but it doesn't).

Now, in your code you also transfer your coordinate transformation to the canvas. Normally, that's not what you want since that also transforms node text so I've not included that part.

• "+1" Thank you for your suggested solution and your detailed explanations. This helped me a lot in understanding my problem. However, this was a misconception on my part. I agree with @Qrrbrbirlbel: "The keys x, y and z only transform a 3D point into a 2D point on your paper ..." I assumed nodes would be stored internally in 2D screen coordinates. If I understand correctly, that is not the case. Now I might be able to formulate my question more precisely. I will explain the question above in more detail with an example Commented Aug 9, 2022 at 12:21
• Nodes are stored internally in 2D screen coordinates. But if you use a canvas transformation then that applies to everything, including distorting nodes. In your updated question then you use this to good effect with the angle labels. But usually one doesn't want the text of nodes to be transformed so it's more common just to use coordinate transformations. Commented Aug 9, 2022 at 15:22