Draw a solid with circular base & square cross-sections in TikZ

I want to draw a solid with these specifications in TikZ. The base of the solid is the disk x^2+y^2\leq 4. The cross-sections by planes perpendicular to the y-axis between y=-2 and y=2 are squares with one leg in the disk. The code and image of the question are below.

\documentclass[12pt, border=5mm]{standalone}
\usepackage{tikz, tikz-3dplot}
\usetikzlibrary{arrows.meta}
\begin{document}
\tdplotsetmaincoords{60}{35}
\begin{tikzpicture}[tdplot_main_coords, scale=0.9]
\pgfmathsetmacro{\y}{-1}
\pgfmathsetmacro{\x}{sqrt(4-pow(\y,2))}
\draw [thick, -Stealth] (-3,0,0) -- (3,0,0) node [right] {$x$};
\draw [thick, -Stealth] (0,-3,0) -- (0,4,0) node [left=4pt] {$y$};
\filldraw [fill=lime, very thick, draw=green!33!black, canvas is xy plane at z=0, opacity=0.75] (0,0) circle [radius=2cm];
\fill [magenta, canvas is xz plane at y=\y, opacity=0.67] (-\x,0) rectangle (\x,2*\x);
\end{tikzpicture}
\end{document}


Of course, I was able to draw almost the figure using \foreach. But it is not continuous and can be seen in pieces. How can this problem be fixed? Here is my effort so far.

\documentclass[12pt, border=5mm]{standalone}
\usepackage{tikz, tikz-3dplot}
\usetikzlibrary{arrows.meta}
\begin{document}
\tdplotsetmaincoords{60}{35}
\begin{tikzpicture}[tdplot_main_coords, scale=0.9]
\pgfmathsetmacro{\dy}{0.05}
\draw [thick, -Stealth] (-4,0,0) -- (5,0,0) node [right] {$x$};
\draw [thick, -Stealth] (0,-4,0) -- (0,6,0) node [left=4pt] {$y$};
\filldraw [fill=lime, very thick, draw=green!50!black, canvas is xy plane at z=0, opacity=0.5] (0,0) circle [radius=2cm];
\begin{scope}[opacity=0.5]
\foreach \y in {2,1.95,...,-2}{
\pgfmathsetmacro{\x}{sqrt(4-pow(\y,2))}
\filldraw [fill=red!50, semithick, draw=red!50, canvas is yz plane at x=\x] (\y,0) rectangle ++(\dy,2*\x);
\filldraw [fill=cyan!50, semithick, draw=cyan!50, canvas is xy plane at z=2*\x] (-\x,\y) rectangle ++(2*\x,\dy);
\filldraw [fill=orange!50, semithick, draw=orange, canvas is xz plane at y=\y] (-\x,0) rectangle (\x,2*\x);
}
\end{scope}
\end{tikzpicture}
\end{document}


2 Answers

Here is another solution made with plain tikz. We only have to draw lines and ellipses, but we need to do the maths. I use isometric axes and the maths are nearly the same as in the OP, but we need a couple of additional calculations for the tangent points. It's easy to obtain Q (see the drawing) but not so easy to obtain P, so I'm drawing it "by hand". Alternatively, we could compute the tangents to the semiellipses parallel to x but the maths won't be simple.

I show in my drawing the points P, Q and S only for reference, they can be deleted or commented.

This is my proposal:

\documentclass[border=2mm]{standalone}
\usepackage    {tikz}
\usetikzlibrary{3d}

% isometric axes (don't change them)
\pgfmathsetmacro\xx{1/sqrt(2)}
\pgfmathsetmacro\xy{1/sqrt(6)}
\pgfmathsetmacro\zy{sqrt(2/3)}

\begin{document}
\begin{tikzpicture}[line cap=round,line join=round,%
x={({\xx cm,-\xy cm})},y={(\xx cm,\xy cm)},z={(0 cm,\zy cm)}]
% dimensions
\def\r{2}                           % base radius
\pgfmathsetmacro\a  {\r*sqrt(5)}    % semimajor axis, semiellipses
\pgfmathsetmacro\phi{atan(2)}       % inclination angle, semiellipses
\pgfmathsetmacro\rho{28}            % approximate angle, tangent point P (can be calculated,
\pgfmathsetmacro\px {\r*cos(\rho)}  % x, tangent point P                  but the maths... you know)
\pgfmathsetmacro\py {\r*sin(\rho)}  % y,
\pgfmathsetmacro\pz {2*\r*cos(\rho)}% z
\pgfmathsetmacro\qx {\r*cos(45)}    % x, tangent point Q
\pgfmathsetmacro\qy {\qx}           % y,
\pgfmathsetmacro\qz {2*\qx}         % z
\pgfmathsetmacro\sx {0.975*\r}            % x, S point, square vertex
\pgfmathsetmacro\sy {sqrt(\r*\r-\sx*\sx)} % y,
% axes and base
\begin{scope}[canvas is xy plane at z=0]
\draw[latex-latex] (\r+1.5,0,0) node [right] {$x$} -| (0,\r+1.5,0) node [right] {$y$};
\draw (0,0) circle (\r);
\end{scope}
% semiellipses
\foreach\i in {-\phi,\phi-180}
{%
\begin{scope}[rotate around y=\i,canvas is xy plane at z=0]
\draw (0,-\r) arc (-90:90:\a cm and \r cm);
\end{scope}
}
% square
\draw[canvas is xz plane at y=-\sy,green,fill=green,fill opacity=0.6] (-\sx,0) rectangle (\sx,2*\sx);
% left surface
\draw[left color=orange,right color=orange!20,fill opacity=0.6] (0,-2) arc (-90:-135:\r) --++ (0,0,\qz)
{[rotate around y=\phi-180,canvas is xy plane at z=0] arc (315:270:\a cm and \r cm)} -- cycle;
% right surface
\draw[left color=orange!20,right color=orange,fill opacity=0.6] (0,-2) arc (-90:45:\r) --++ (0,0,\qz)
{[rotate around y=-\phi,canvas is xy plane at z=0] arc (45:-90:\a cm and \r cm)} -- cycle;
% top surface
\draw[top color=blue,fill opacity=0.6] (0,-\r)
{[rotate around y=-\phi,canvas is xy plane at z=0] arc (-90:\rho:\a cm and \r cm)} --
(\px,\py,\pz) -- (-\px,\py,\pz)
{[rotate around y=\phi-180,canvas is xy plane at z=0] arc (\rho:-90:\a cm and \r cm)};
% only for reference, comment or remove the following:
\draw[dashed] (\px,\py,\pz) -- (\px,\py,0);
\fill (\px, \py,0) circle (1pt) node [right] {$P$};
\fill (\qx, \qy,0) circle (1pt) node [right] {$Q$};
\fill (\sx,-\sy,0) circle (1pt) node [below] {$S$};
\end{tikzpicture}
\end{document}


• this is awesome! – AlexG May 6 at 11:41
• @Juan What does line cap=round, line join=round do? – Mohammadi May 6 at 12:11
• @Mohammadi, I put these options almost always. They make the end points of lines (line cap) and the joining of two lines (line join) rounded. By default they are rectangular. Try, for example, \draw[line width=2pt] (0,0) -- (0,1) -- (1,0) -- (0.5,0);with and without these options and you'll see what happens. – Juan Castaño May 6 at 12:45
• +1 for this very nice input. – SebGlav May 6 at 14:52

You could use pgfplots.

\documentclass[tikz,border=3mm]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.17}
\begin{document}
\begin{tikzpicture}
\begin{axis}[hide axis,
%xlabel={$x$},ylabel={$y$},
view={30}{35}]
\addplot3[surf,domain=0:180,z buffer=sort,domain y=-2:2]
({sign(y)*2*min(abs(y),1)*sin(x)},{2*cos(x)},{(abs(y)>1?0:2)*abs(sin(x))});
\end{axis}
\end{tikzpicture}
\end{document}


• Thank you very much. I have a few question. 1. What does z buffer=sort do? 2. Why not 0:360 domain? 3. I use colormap={custom}{color(0)=(cyan!25) color(1)=(cyan)}, is it possible to use different colormap for the cylindrical wall? And most importantly, 4. Please explain about the addplot3 function rule. It is difficult for me to understand. – Mohammadi May 6 at 10:09
• @Mohammadi z buffer=sort is the reason why I recommended pgfplots, it draws the plot in the correct order, i.e. you do not have to worry that objects in the back appear in the front. That is, you neither have to fine tune some parameters nor are you restricted in the choice of the view angles, pgfplots will figure it out for you. And yes, you can change the color for the cylindrical wall, e.g. by dialing some appropriate point meta. – user241266 May 6 at 15:21