A superellipse is a kind of closed curve which can be used as a "intermediate" shape between ellipse and rectangle. A parameter can control its "roundness". I find it a pleasant alternative to the typical "rounded corners" rectangle.
A convenient approximation of the curve can be achieved using bezier patches. In "Metafont book", Knuth defines the following approximation (p. 267), indistinguishable of a real superellipse for practical purposes:
The five parameters to ‘superellipse’ are the right, the top, the left, the bottom, and the superness.
def superellipse(expr r,t,l,b,s)= r{up}...(s[xpart t,xpart r],s[ypart r,ypart t]){t-r}... t{left}...(s[xpart t,xpart l],s[ypart l,ypart t]){l-t}... l{down}...(s[xpart b,xpart l],s[ypart l,ypart b]){b-l}... b{right}...(s[xpart b,xpart r],s[ypart r,ypart b]){r-b}...cycle enddef;
The following metapost
code provides a more convenient way to specify the superellipse by the four corners of the rectangle which contains it:
def my_superellipse(expr sw, nw, ne, se, factor) =
superellipse(1/2[se,ne], 1/2[nw,ne], 1/2[nw,sw], 1/2[sw,se], factor)
enddef;
An example of use (metapost code):
beginfig(1)
z1=(0,0); % z1, z2, z3 and z4 are four corners of a rectangle
z3=(40,20);
x2=x1; x4=x3; y2=y3; y4=y1;
% Draw a red circle at those points
for i = 1 upto 4:
draw z[i] withpen pencircle scaled 2 withcolor red;
endfor
% Draw the rectangle through those points
draw z1 -- z2 -- z3 -- z4 -- cycle withpen pencircle scaled .1;
% Draw the superellipse, with 0.9 "superness" (the closer to 1, the more squarish)
draw my_superellipse(z1,z2,z3,z4, .9);
% Some text inside
label(btex \LaTeX etex, 1/2[z1,z3]);
endfig;
This is the result:
I thought that this would be a nice addition to tikz
node shapes, but I don't know how to translate the metapost syntax to the required control points of the superellipse curve, and I don't have experience in defining new node shapes for tikz
.
Also note that the resulting node has obvious north
, south
, east
and west
anchors, but it would be trickier to find the coordinates of north west
, etc. not to mention the more general anchors node.angle
.
Update
Some clarifications about the question:
The formula of the superellipse is well known (see Wikipedia article linked from the first line of the question), however I would prefer do not plot that formula, since it would require to decide the sampling frequency, and do all the computations. I'm interested instead in drawing an approximation using
tikz
syntax such as..
orto
, orpgf
primitives such as\pgfpathcurveto
. This would require to compute only the control points.I'm aware of the
hobby
package by Andrew Stacey (I was indeed the author of a python script to convert a subset of the metapost syntax totikz
, which started it all). However I think this solution is a bit overkill, because it contains a general parser for the metapost syntax, while the superellipse curve has a strong symmetry which could simplify a lot the calculations (although I don't see still how).The ultimate goal is to define a new node shape. This require the use of low-level
pgf
primitives and I don't know howhobby
package relates to that level ofpgf/tikz
.
Comparison of proposed solutions
In order to compare the proposed solutions with the reference superellipse drawn by
metapost
code above, I have obtained the explicit control points of the curve.
The following code defines a macro \metapostsuperellipse
containing this path in tikz
syntax, and defines a rectangular node of appropiate dimensions (40x20 pt), which can be decorated with the proposed solutions.
\documentclass{article}
\usepackage{tikz}
\begin{document}
\usetikzlibrary{calc}
% These numbers were computed by metapost, and obtained using tracingchoices option
\newcommand{\metapostsuperellipse}{
(40,10) .. controls (40,13.43742) and (39.99951,18.00012)
.. (37.99988,18.99994) .. controls (36.00024,19.99976) and (26.11214,20)
.. (20,20)..controls (13.88786,20) and (3.99976,19.99976)
.. (2.00012,18.99994) .. controls (0.00049,18.00012) and (0,13.43742)
.. (0,10)..controls (0,6.56258) and (0.00049,1.99988)
.. (2.00012,1.00006) .. controls (3.99976,0.00024) and (13.88786,0)
.. (20,0)..controls (26.11214,0) and (36.00024,0.00024)
.. (37.99988,1.00006) .. controls (39.99951,1.99988) and (40,6.56258)
.. (40,10);
}
% Solutions proposed by Qrrbrbirlbel
\newcommand*{\superellipse}[3][draw]{% #1 = styles
% #2 = node
% #3 = superness
\pgfmathsetmacro\looseness{#3}
\path[#1]
(#2.east) .. controls ($(#2.east)!\looseness!(#2.north east)$) and ($(#2.north)!\looseness!(#2.north east)$).. (#2.north)
.. controls ($(#2.north)!\looseness!(#2.north west)$) and ($(#2.west)!\looseness!(#2.north west)$).. (#2.west)
.. controls ($(#2.west)!\looseness!(#2.south west)$) and ($(#2.south)!\looseness!(#2.south west)$).. (#2.south)
.. controls ($(#2.south)!\looseness!(#2.south east)$) and ($(#2.east)!\looseness!(#2.south east)$).. (#2.east)
;
}
\newcommand*{\superellipseA}[3][draw]{% #1 = styles
% #2 = node
% #3 = superness
\pgfmathsetmacro\looseness{2*#3}
\path[curve to, looseness=\looseness, #1]
let \p1 = (#2.east),
\p2 = (#2.north),
\n1 = {\x1-\x2}, % distance in x direction
\n2 = {\y2-\y1} % distance in y direction
in
(#2.east) to[out=90, in=0, out max distance=\n2, in max distance=\n1]
(#2.north) to[out=180, in=90, out max distance=\n1, in max distance=\n2]
(#2.west) to[out=270, in=180, out max distance=\n2, in max distance=\n1]
(#2.south) to[out=0, in=270, out max distance=\n1, in max distance=\n2] (#2.east);
}
\newcommand*{\superellipseB}[3][draw]{% #1 = styles
% #2 = node
% #3 = superness
\pgfmathsetmacro\looseness{2*#3}
\path[curve to, looseness=\looseness, #1]
let \p1 = (#2.east),
\p2 = (#2.north),
\n1 = {min({\x1-\x2},{\y2-\y1})} % minimum of distances
in
(#2.east) to[out=90, in=0, max distance=\n1]
(#2.north) to[out=180, in=90, max distance=\n1]
(#2.west) to[out=270, in=180, max distance=\n1]
(#2.south) to[out=0, in=270, max distance=\n1] (#2.east);
}
\tikzset{node box/.style={rectangle, minimum height=20pt, minimum width=40pt, draw, ultra thin, anchor=south west, green}
}
% Pictures to compare proposed solutions with reference (in red)
\begin{tikzpicture}[x=1pt, y=1pt]
\node[node box] (sample) {};
\draw[red, opacity=.5] \metapostsuperellipse;
\superellipse[draw, opacity=0.6, very thin]{sample}{.9}
\end{tikzpicture}
\begin{tikzpicture}[x=1pt, y=1pt]
\node[node box] (sample) {};
\draw[red, opacity=.5] \metapostsuperellipse;
\superellipseA[draw, opacity=0.6, very thin]{sample}{.9}
\end{tikzpicture}
\begin{tikzpicture}[x=1pt, y=1pt]
\node[node box] (sample) {};
\draw[red, opacity=.5] \metapostsuperellipse;
\superellipseB[draw, opacity=0.6, very thin]{sample}{.9}
\end{tikzpicture}
\end{document}
Which gives:
r{up}
and(<expr1>,<expr2>){t-r}
means? I’m guessing: From pointr
, direction up and from point(<expr1>,<expr2>)
to the pointt-r
( =(r1 - t1,r2 - r1)
,p1
being the x-coordinate andp2
being the y-coordinate of pointp
.1/2[<point1>,<point2>]
obviously (?) is the point between<point1>
and<point2>
but what is<factor>[<value1>,<value2>]
?up
is a predefined vector equivalent to{0,1}
.r
,l
,t
,d
are variables containing coordinates of points (right, left, top and down). Also, expressiona[b,c]
is equivalent to tikz's($(b)!a!(c)$)
whenb
andc
are points, or tob + a*(c-b)
when they are numbers.