TikZ has a built-in 3d coordinate system. You can set the directions of the unit vectors with the x
, y
and z
options. Then you could just do the math by hand and draw the points and lines. Or, you can define some macros to do the job:
\documentclass{article}
\usepackage{tikz}
\makeatletter
% Set some defaults
\tikzset{
plane max x/.initial=2,
plane max y/.initial=2,
plane max z/.initial=2
}
\tikzset{plane/.style={fill opacity=0.5}}
% Define a plane.
% #1 = name of the plane
% #2*x + #3*y + #4*z = #5 is the equation of the plane
\newcommand*\definePlaneByEquation[5]{
\expandafter\gdef\csname tsx@plane@#1\endcsname{
\def\tsx@plane@xcoeff{#2}
\def\tsx@plane@ycoeff{#3}
\def\tsx@plane@zcoeff{#4}
\def\tsx@plane@scalar{#5}
}
}
% Draw a plane.
% The optional first argument is passed as options to TikZ.
% The mandatory second argument is the name of the plane.
\newcommand\drawPlane[2][]{
\tikzset{plane max x/.get=\tsx@plane@maxx}
\tikzset{plane max y/.get=\tsx@plane@maxy}
\tikzset{plane max z/.get=\tsx@plane@maxz}
\csname tsx@plane@#2\endcsname
\ifdim\tsx@plane@xcoeff pt=0pt
\ifdim\tsx@plane@ycoeff pt=0pt
\ifdim\tsx@plane@zcoeff pt=0pt
%invalid plane
\else % x=0, y=0
\filldraw[plane,#1,shift={(0,0,\tsx@plane@scalar/\tsx@plane@zcoeff)}]
(0,0,0) --
(\tsx@plane@maxx,0,0) --
(\tsx@plane@maxx,\tsx@plane@maxy,0) --
(0,\tsx@plane@maxy,0) --
cycle;
\fi
\else % x=0, y != 0
\ifdim\tsx@plane@zcoeff pt=0pt % x=0, z=0
\filldraw[plane,#1,shift={(0,\tsx@plane@scalar/\tsx@plane@ycoeff,0)}]
(0,0,0) --
(\tsx@plane@maxx,0,0) --
(\tsx@plane@maxx,0,\tsx@plane@maxz) --
(0,0,\tsx@plane@maxz) --
cycle;
\else % x=0
\filldraw[plane,#1]
(0,\tsx@plane@scalar/\tsx@plane@ycoeff,0) --
(0,0,\tsx@plane@scalar/\tsx@plane@zcoeff) --
(\tsx@plane@maxx,0,\tsx@plane@scalar/\tsx@plane@zcoeff) --
(\tsx@plane@maxx,\tsx@plane@scalar/\tsx@plane@ycoeff,0) --
cycle;
\fi
\fi
\else % x!=0
\ifdim\tsx@plane@ycoeff pt=0pt % x!=0,y=0
\ifdim\tsx@plane@zcoeff pt=0pt % x!=0,y=0,z=0
\filldraw[plane,#1,shift={(\tsx@plane@scalar/\tsx@plane@xcoeff,0,0)}]
(0,0,0) --
(0,0,\tsx@plane@maxz) --
(0,\tsx@plane@maxy,\tsx@plane@maxz) --
(0,\tsx@plane@maxy,0) --
cycle;
\else % x!=0,y=0,z!=0
\filldraw[plane,#1]
(\tsx@plane@scalar/\tsx@plane@xcoeff,0) --
(0,0,\tsx@plane@scalar/\tsx@plane@zcoeff) --
(0,\tsx@plane@maxy,\tsx@plane@scalar/\tsx@plane@zcoeff) --
(\tsx@plane@scalar/\tsx@plane@xcoeff,\tsx@plane@maxy,0) --
cycle;
\fi
\else % x!=0,y!=0
\ifdim\tsx@plane@zcoeff pt=0pt % x!=0,y!=0,z=0
\filldraw[plane,#1]
(\tsx@plane@scalar/\tsx@plane@xcoeff,0) --
(0,\tsx@plane@scalar/\tsx@plane@ycoeff,0) --
(0,\tsx@plane@scalar/\tsx@plane@ycoeff,\tsx@plane@maxz) --
(\tsx@plane@scalar/\tsx@plane@xcoeff,0,\tsx@plane@maxz) --
cycle;
\else % x!=0,y!=0,z!=0
\filldraw[plane,#1]
(\tsx@plane@scalar/\tsx@plane@xcoeff,0,0) --
(0,\tsx@plane@scalar/\tsx@plane@ycoeff,0) --
(0,0,\tsx@plane@scalar/\tsx@plane@zcoeff) --
cycle;
\fi
\fi
\fi
}
% Define a point.
% #1 = name of the point
% (#2,#3,#4) is the location.
% Also creates a coordinate node of name #1 at the location.
\newcommand\definePointByXYZ[4]{
\coordinate (#1) at (#2,#3,#4);
\expandafter\gdef\csname tsx@point@#1\endcsname{
\def\tsx@point@x{#2}
\def\tsx@point@y{#3}
\def\tsx@point@z{#4}
}
}
% Project a point to a plane.
% #1 = name of the new point
% #2 = name of old point
% #3 = name of plane
\newcommand\projectPointToPlane[3]{{
\csname tsx@point@#2\endcsname
\csname tsx@plane@#3\endcsname
% square of norm of the normal vector
\pgfmathparse{\tsx@plane@xcoeff*\tsx@plane@xcoeff + \tsx@plane@ycoeff*\tsx@plane@ycoeff + \tsx@plane@zcoeff*\tsx@plane@zcoeff}
\let\nnormsq\pgfmathresult
% Calculate distance in terms of the (non-normalized) normal vector
\pgfmathparse{(\tsx@point@x*\tsx@plane@xcoeff + \tsx@point@y*\tsx@plane@ycoeff + \tsx@point@z*\tsx@plane@zcoeff - \tsx@plane@scalar) / \nnormsq}
\let\distance\pgfmathresult
% Calculate point
\pgfmathparse{\tsx@point@x - \distance*\tsx@plane@xcoeff}
\let\x\pgfmathresult
\pgfmathparse{\tsx@point@y - \distance*\tsx@plane@ycoeff}
\let\y\pgfmathresult
\pgfmathparse{\tsx@point@z - \distance*\tsx@plane@zcoeff}
\let\z\pgfmathresult
\definePointByXYZ{#1}{\x}{\y}{\z}
}}
\makeatother
\begin{document}
\begin{tikzpicture}[x={(240:0.8cm)}, y={(-10:1cm)}, z={(0,1cm)},
plane max z=3]
\draw[->] (0,0,0) -- (3,0,0);
\draw[->] (0,0,0) -- (0,3,0);
\draw[->] (0,0,0) -- (0,0,3);
\definePlaneByEquation{myplane}{1}{1.5}{0}{2}
\drawPlane[thick,fill=blue]{myplane}
\definePointByXYZ{mypoint}{1}{1.5}{2};
\draw (mypoint) circle [radius=1pt];
\projectPointToPlane{proj}{mypoint}{myplane}
\fill (proj) circle [radius=1pt];
\draw[->, shorten <=1pt,shorten >=1pt] (mypoint) -- (proj);
\end{tikzpicture}
\end{document}
Some comments:
- I hope I didn't mess up some case.
- With the
plane max x
/y
/z
options, you can specify how far a plane with zero parameters should extend.
If you want to draw a line through the point, normal to the plane, you can use
\definePlaneByEquation{myplane}{1}{1.5}{0}{2}
\definePointByXYZ{mypoint}{1}{1.5}{2};
\projectPointToPlane{proj}{mypoint}{myplane}
\draw ($(proj)!-2cm!(mypoint)$) -- (proj);
\drawPlane[thick,fill=blue]{myplane}
\draw (proj) -- ($(mypoint)!-2cm!(proj)$);
The order is important to get correct overpainting in case the plane is opaque or the line is non-black.
- The tests for 0 should really be tests for being smaller than epsilon.
- Maybe it would be useful to apply
\pgfmathparse
to the arguments of the definition macros.
You can use the pgfplotstable
package, which is part of PGFplots, to find the largest value in the data by sorting the table and extracting the first element of the sorted table
\documentclass{article}
\usepackage{filecontents}
\usepackage{pgfplots}
\usepackage{pgfplotstable}
\begin{filecontents}{sample-data.dat}
{Data Values} Count
0 6
0.109827 86
0.219654 300
0.329481 662
0.439308 1085
0.549135 1366
0.658962 1470
0.768789 1333
0.878616 1200
0.988443 904
1.09827 620
1.2081 417
1.31792 247
1.42775 164
1.53758 72
1.64741 30
1.75723 21
1.86706 8
1.97689 6
2.08671 2
2.19654 1
2.41619 0
\end{filecontents}
\pgfplotstablesort[sort key={[index] 1},sort cmp={float >}]{\sorted}{sample-data.dat}
\pgfplotstablegetelem{0}{[index] 1}\of{\sorted}
\let\maxvalue=\pgfplotsretval
\begin{document}
\begin{tikzpicture}
\begin{axis}[
xlabel=Data Values,
ylabel=Count,
]
\addplot table [
x=Data Values,
y=Count
] {sample-data.dat};
\draw[red,->,>=stealth] (axis cs:2,0) -- (axis cs:2,\maxvalue)
node [anchor=west,black] at (axis cs:2,\maxvalue) {\maxvalue};
\end{axis}
\end{tikzpicture}
\end{document}
And just to show what else is possible with pgfplotstable
, here's a macro that takes an x-value as the argument, finds the data point in the table with the nearest x-value, and assembles the two points for the arrow:
\documentclass{article}
\usepackage{filecontents}
\usepackage{pgfplots}
\usepackage{pgfplotstable}
\begin{filecontents}{sample-data.dat}
{Data Values} Count
0 6
0.109827 86
0.219654 300
0.329481 662
0.439308 1085
0.549135 1366
0.658962 1470
0.768789 1333
0.878616 1200
0.988443 904
1.09827 620
1.2081 417
1.31792 247
1.42775 164
1.53758 72
1.64741 30
1.75723 21
1.86706 8
1.97689 6
2.08671 2
2.19654 1
2.41619 0
\end{filecontents}
\newcommand{\assemblearrowpoints}[1]{
% Read table, save column names
\pgfplotstableread{sample-data.dat}{\datatable}
\pgfplotstablegetcolumnnamebyindex{0}\of{\datatable}\to{\xname}
\pgfplotstablegetcolumnnamebyindex{1}\of{\datatable}\to{\yname}
% Find maximum value in table by sorting and then extracting first element of sorted table
\pgfplotstablesort[sort key={\yname},sort cmp={float >}]{\sorted}{sample-data.dat}
\pgfplotstablegetelem{0}{\yname}\of{\sorted}
\let\maxy=\pgfplotsretval
% Create new "target" column with squared difference between x values and target x
\pgfplotstablecreatecol[
create col/expr={(\thisrow{\xname}-#1)^2}]{target}{\datatable}
% Sort from lowest to highest squared difference
\pgfplotstablesort[sort key={target},sort cmp={float <}]{\targettable}{\datatable}
\pgfplotstablegetelem{0}{\xname}\of{\targettable}
\let\lowerx=\pgfplotsretval
\pgfplotstablegetelem{0}{\yname}\of{\targettable}
\let\lowery=\pgfplotsretval
% Assemble points for arrow
\def\lowerpoint{axis cs:\lowerx,\lowery}
\def\highpoint{axis cs:\lowerx,\maxy}
}
\begin{document}
\assemblearrowpoints{2}
\begin{tikzpicture}
\begin{axis}[
xlabel=Data Values,
ylabel=Count,
]
\addplot table [
x=Data Values,
y=Count
] {sample-data.dat};
\draw[red,->,>=stealth] (\lowerpoint)
node [black,anchor=south west] {\lowery}
-- (\highpoint)
node [pos=0.5,black,anchor=west] {\pgfmathparse{int(\maxy-\lowery)}\pgfmathresult}
node [black,anchor=west] {\maxy};
\end{axis}
\end{tikzpicture}
\end{document}
Best Answer
In addition to the solutions provided in the comments, because of the repetitive pattern being found, the code can be simplified by using
foreach
loop and a counter calledsub
for subscript of labels. Many line codings are, therefore, reduced significantly by this skill.Code