5

I cannot figure out how to get a node shape similar to this in pgf/tikz, I don't need the colours, just the black and white shape.

Rectangle with curved corners and both long edges dipping towards the center.

Sorry I don't have any code as nothing I've tried gets me remotely close. Any ideas are appreciated, thanks.

  • Do you really need a node (i.e. with text inside, and anchors on the border that you can use for connecting lines), or do you just want to draw the shape as a path? – Jake Feb 19 '16 at 20:01
  • 1
    @Jake I need it as a node I can use in a goal graph, so yes the anchor points are important. – Adriala Feb 19 '16 at 20:03
  • Tikz nodes (as a rule) get bigger when you add text. If you don't need that, I have an example here: elfsoft2000.com/projects/multipole.pdf – John Kormylo Feb 19 '16 at 23:51
  • I think, that if you really want a proper node you need to look at section 101.5.3 Command for Declaring New Shapes and \pgfdeclareshape. The remainder of that section explains the macros available for setting anchors etc. and ends with an example which declares a shape and then uses it with \node in a tikzpicture environment. – cfr Feb 19 '16 at 23:56
  • You may define a newcommand that takes the coordinates and the text as mandatory arguments and draws a standard node. Once the node is defined you can then access to the anchor points and draw a spline using the TikZ commands. I can try to do this as soon as I can. – mirkom Feb 20 '16 at 10:22
8

Here's what I came up with. I basically used the example from the PGF manual and tried to emulate the rectangle shape as much as possible.

The shape is defined by the macro \myshapepath. All anchor points are calculated using the intersection library, so they should be exact. Defining the shape so it looks good at different aspect ratios is kind of difficult, though.

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

\makeatletter
\def\myslant{0.2} % defines the skew of the right and left side
\def\myxsep{0.5}  % extra distance in x direction; relative to node height
\def\myvhandlelen{.85} % length of the vertical bezier handles; relative to node height
\def\myhhandlelen{.65} % length of the horizontal bezier handles; relative to node width
\def\mypoint#1#2#3{
    % #1 = x coordinate, in multiples of the width
    % #2 = y coordinate, in multiples of the height, slanted
    % #3 = x coordinate, relative to height and slant factor
    \pgfpointdiff{\southwest}{\northeast}
    \pgf@xc=\pgf@x          % xc = width of the node
    \pgf@yc=\pgf@y          % yc = height of the node
    \pgf@xb=\myslant\pgf@yc % xb = width of the node scaled by \myslant
    \southwest
    \advance\pgf@x by .5\pgf@xc
    \advance\pgf@y by .5\pgf@yc
    \advance\pgf@x by  #1\pgf@xc
    \advance\pgf@y by  #2\pgf@yc
    \advance\pgf@x by  #2\pgf@xb
    \advance\pgf@x by  #3\pgf@xb
}
% this defines the shape of the node; the macro is used for drawing the shape as well as for calculating intersection points
\def\myshapepath{
    \pgfpathmoveto{\mypoint{-.5}{0}{-\myxsep}}
    \pgfpathcurveto{\mypoint{-.5}{\myvhandlelen}{-\myxsep}}{\mypoint{-\myhhandlelen}{.5}{0}}{\mypoint{0}{.5}{0}}
    \pgfpathcurveto{\mypoint{\myhhandlelen}{.5}{0}}{\mypoint{.5}{\myvhandlelen}{\myxsep}}{\mypoint{.5}{0}{\myxsep}}
    \pgfpathcurveto{\mypoint{.5}{-\myvhandlelen}{\myxsep}}{\mypoint{\myhhandlelen}{-.5}{0}}{\mypoint{0}{-.5}{0}}
    \pgfpathcurveto{\mypoint{-\myhhandlelen}{-.5}{0}}{\mypoint{-.5}{-\myvhandlelen}{-\myxsep}}{\mypoint{-.5}{0}{-\myxsep}}
    \pgfpathclose
}
% compute an intersection point between a line and \myshapepath
\def\myshapeanchorborder#1#2{
    % #1 = point inside the shape
    % #2 = direction
    \pgftransformreset % without this, the intersection commands yield strange results
    \pgf@relevantforpicturesizefalse % don't include drawings in bounding box
    \pgfintersectionofpaths{
        \myshapepath
        %\pgfgetpath\temppath\pgfusepath{stroke}\pgfsetpath\temppath % draw path for debugging
    }{
        \pgfpathmoveto{
            \pgfpointadd{
                \pgfpointdiff{\southwest}{\northeast}\pgf@xc=\pgf@x \advance\pgf@xc by \pgf@y % calculate a distance that is guaranteed to be outside the shape
                \pgfpointscale{
                    \pgf@xc
                }{
                    \pgfpointnormalised{
                        #2
                    }
                }
            } {
                #1
            }
        }
        \pgfpathlineto{#1}
        %\pgfgetpath\temppath\pgfusepath{stroke}\pgfsetpath\temppath % draw path for debugging
    }
    \pgfpointintersectionsolution{1}
}
\def\myshapeanchorcenter{
    \pgfpointscale{.5}{\pgfpointadd{\southwest}{\northeast}}
}
% we could probably re-use some existing \dimen, but better be careful
\newdimen\myshapedimenx
\newdimen\myshapedimeny

\pgfdeclareshape{myshape}{
    % some stuff, we can inherit from the rectangle shape
    \inheritsavedanchors[from=rectangle]
    \inheritanchor[from=rectangle]{center}
    \inheritanchor[from=rectangle]{mid}
    \inheritanchor[from=rectangle]{base}

    % calculate these anchors so they lie on a coorinate line with .center
    \anchor{west}{\myshapeanchorborder{\myshapeanchorcenter}{\pgfpoint{-1cm}{0cm}}}
    \anchor{east}{\myshapeanchorborder{\myshapeanchorcenter}{\pgfpoint{1cm}{0cm}}}
    \anchor{north}{\myshapeanchorborder{\myshapeanchorcenter}{\pgfpoint{0cm}{1cm}}}
    \anchor{south}{\myshapeanchorborder{\myshapeanchorcenter}{\pgfpoint{0cm}{-1cm}}}

    % calculate these anchors so they lie on a line through .center and the corresponding anchor of the underlying rectangle
    \anchor{south west}{\myshapeanchorborder{\myshapeanchorcenter}{\pgfpointdiff{\myshapeanchorcenter}{\southwest}}}
    \anchor{north east}{\myshapeanchorborder{\myshapeanchorcenter}{\pgfpointdiff{\myshapeanchorcenter}{\northeast}}}
    \anchor{south east}{\myshapeanchorborder{\myshapeanchorcenter}{\pgfpointdiff{\myshapeanchorcenter}{\northeast\pgf@xa=\pgf@x\southwest\pgf@x=\pgf@xa}}}
    \anchor{north west}{\myshapeanchorborder{\myshapeanchorcenter}{\pgfpointdiff{\myshapeanchorcenter}{\southwest\pgf@xa=\pgf@x\northeast\pgf@x=\pgf@xa}}}

    % somewhat more special anchors. The coordinate calculations were taken from the rectangle node
    \anchor{mid west}{\myshapeanchorborder{\myshapeanchorcenter\pgfmathsetlength\pgf@y{.5ex}}{\pgfpoint{-1cm}{0cm}}}
    \anchor{mid east}{\myshapeanchorborder{\myshapeanchorcenter\pgfmathsetlength\pgf@y{.5ex}}{\pgfpoint{1cm}{0cm}}}
    \anchor{base west}{\myshapeanchorborder{\myshapeanchorcenter\pgf@y=0pt}{\pgfpoint{-1cm}{0cm}}}
    \anchor{base east}{\myshapeanchorborder{\myshapeanchorcenter\pgf@y=0pt}{\pgfpoint{1cm}{0cm}}}

    \backgroundpath{
        % uncomment to draw underlying rectangle node
        %\southwest\pgf@xa=\pgf@x \pgf@ya=\pgf@y
        %\northeast\pgf@xb=\pgf@x \pgf@yb=\pgf@y
        %\pgfpointdiff{\southwest}{\northeast}\pgf@xc=\pgf@x \pgf@yc=\pgf@y
        %\pgfpathmoveto{\pgfpoint{\pgf@xa}{\pgf@ya}}
        %\pgfpathlineto{\pgfpoint{\pgf@xa}{\pgf@yb}}
        %\pgfpathlineto{\pgfpoint{\pgf@xb}{\pgf@yb}}
        %\pgfpathlineto{\pgfpoint{\pgf@xb}{\pgf@ya}}
        %\pgfpathclose

        \myshapepath
    }
    \anchorborder{
        \myshapedimenx=\pgf@x
        \myshapedimeny=\pgf@y
        \myshapeanchorborder{\myshapeanchorcenter}{\pgfpoint{\myshapedimenx}{\myshapedimeny}}
    }
}

\makeatother

\tikzset{shape example/.style={color=black!30,draw,fill=yellow!30,line width=.5cm,inner xsep=2.5cm,inner ysep=0.5cm}}

\begin{document}
    {\Huge\begin{tikzpicture}
        \node[name=s,shape=myshape,shape example] {myshape\vrule width 1pt height 2cm};

        \foreach \anchor/\placement in {
            north west/above left,
            north/above,
            north east/above right,
            west/left,
            center/above,
            east/right,
            mid west/right,
            mid/above,
            mid east/left,
            base west/left,
            base/below,
            base east/right,
            south west/below left,
            south/below,
            south east/below right,
            text/left,
            10/right,
            130/above%
        } {
            \draw[shift=(s.\anchor)] plot[mark=x] coordinates{(0,0)}
            node[\placement] {\scriptsize\texttt{(s.\anchor)}};
        }
    \end{tikzpicture}}

    \begin{tikzpicture}
        \draw (-2.0, 0.0) node[draw,myshape] (a) {normal};
        \draw (-0.5, 2.0) node[draw,myshape] (b) {very long node indeed};
        \draw ( 2.0, 0.0) node[draw,myshape,align=left] (c) {h\\i\\g\\h\\\\n\\o\\d\\e};
        \draw ( 0.0,-1.5) node[draw,myshape,align=left] (d) {almost\\square\\node};

        \draw[->] (a) -> (b);
        \draw[->] (b) -> (c);
        \draw[->] (c) -> (d);
        \draw[->] (d) -> (a);
    \end{tikzpicture}
\end{document}

node shape, annotated like in the manual

several nodes of the new shape, with arrows in between

The big advantage is that you have full control over the node shape, however it has to be specified in PGF with none of the fancy TikZ syntax. It's quite a lot of code, though, maybe there is an easier way...

8

This is a "dirty" solution but may work in most cases. I define a command \mynode[options to node]{node_name}{(coordinate)}{text}[options to curve]. At first the command simply draws a node such as \node [options to node](node_name) at (coordinate) {text};. I then access to the anchor points to draw a curve-to path.

\documentclass[tikz,convert]{standalone}
\usepackage{xparse}
\NewDocumentCommand{\mynode}{%
O{}
m
m
m
O{}
}{
{
\pgfmathsetmacro{\angSE}{30}
\pgfmathsetmacro{\angNE}{-45}
\pgfmathsetmacro{\angNW}{30}
\pgfmathsetmacro{\angSW}{-45}
\node [#1] (#2)  at #3 {#4};
\draw [#5] (#2.south west)to [in=180,out=\angSW] (#2.south) 
                          to [in=180+\angSE,out=0] (#2.south east);
\draw [#5] (#2.south east)to [in=\angNE,out=\angSE] (#2.north east);
\draw [#5] (#2.north east)to [out=180+\angNE,in=0] (#2.north)
                          to [in=\angNW,out=180](#2.north west);
\draw [#5] (#2.north west)to [in=180+\angSW,out=180+\angNW](#2.south west);


\draw [draw=none,in=\angNE,out=\angSE]  (#2.south east) to coordinate [midway] (#2-east) (#2.north east);
\draw [draw=none,in=180+\angSW,out=180+\angNW]  (#2.north west) to coordinate [midway] (#2-west)  (#2.south west);
}
}
\begin{document}
\begin{tikzpicture}
\draw (0,0)--(4,4);
\mynode[below,align=left,fill=white,draw=white]{pt1}{(2,2)}{hello\\ to \\everybody}[red,fill=white]
\draw (3,0)--++(4,4);
\node [draw=red,fill=white,below,align=left] at (5,2) {hello\\ to \\everybody};
\end{tikzpicture}
\end{document}

filled options

It means that the shape of the node is still a rectangle. To access the coordinate actual coordinates corresponding to the west and east side on the curved path the command defines node_name-west and node_name-east (i.e. with a dash rather than a dot): Anchoring points

\begin{tikzpicture}
\coordinate (pt0) at (4,4);
\mynode[below,align=left,fill=white,draw=white]{pt1}{(2,2)}{hello\\ to \\everybody}[red,fill=white]
\draw[<-] (pt1.south)--(pt0);
\draw[<-] (pt1.north)--(pt0);
\draw[<-] (pt1-east)--(pt0);
\draw[<-] (pt1-west)--(pt0);
\begin{scope}[xshift=4cm]
\coordinate (pt00) at (4,4);
\mynode[below,align=left,fill=white,draw]{pt11}{(2,2)}{hello\\ to \\everybody}[red,fill=white]
\draw[<-] (pt11.south)--(pt00);
\draw[<-] (pt11.north)--(pt00);
\draw[<-] (pt11.east)--(pt00);
\draw[<-] (pt11.west)--(pt00);
\end{scope}
\end{tikzpicture}

However, the keys left and right still refers to the rectangular shape. To avoid this problem one solution could be to force the curve to pass through the point .east and .west, for example:

new shape

\documentclass[tikz]{standalone}
\usepackage{xparse}
\NewDocumentCommand{\mynode}{%
O{}
m
m
m
O{}
}{
{
\pgfmathsetmacro{\angSE}{30}
\pgfmathsetmacro{\angNE}{-45}
\pgfmathsetmacro{\angNW}{30}
\pgfmathsetmacro{\angSW}{-45}
\node [#1] (#2)  at #3 {#4};
\draw [#5] (#2.south west)to [in=180,out=\angSW] (#2.south) 
                          to [in=180+\angSE,out=0] (#2.south east);
\draw [#5] (#2.south east)       to [out=\angSE,in=270] (#2.east)
                          to [in=\angNE,out=90] (#2.north east);
\draw [#5] (#2.north east)      to [out=180+\angNE,in=0] (#2.north)
                          to [in=\angNW,out=180](#2.north west);
\draw [#5] (#2.north west)     to [out=\angNW+180,in=90] (#2.west)
                          to [in=180+\angSW,out=270](#2.south west);
}
}


\begin{document}
\begin{tikzpicture}
\draw (0,0)--(4,4);
\mynode[left,align=left,fill=green,draw=green]{pt1}{(2,2)}{hello\\ to \\everybody}[blue,fill=red]
\draw (3,0)--++(4,4);
\node [draw,left,align=left,fill=white] at (5,2) {hello\\ to \\everybody};
\end{tikzpicture}
\end{document}

Another example: Usage of \mynode

\begin{tikzpicture}[>=stealth]
\mynode{node1}{(0,0)}{one node}
\mynode{node2}{(5,0)}{second node}
\mynode[align=center]{node3}{(2.5,2.5)}{central\\ bigger node}
\mynode{node4}{(0,5)}{node!}
\mynode{node5}{(5,5)}{upper right and wide node}
%%%%
\draw [<->] (node1.north east) to [bend right] (node3.south);
\draw [<->] (node1.east) -- (node2.west);
\draw [<->] (node1.south east) to [out=-45, in=-90] (node5.south);
\draw [->] (node4.south east) to [bend right] (node3.west);
\draw [->] (node4.south east) to [bend right] (node3.south west);
\draw [->] (node4.south east) to [bend right] (node3.north west);
\draw [->] (node4.south east) to [bend left] (node3.north);
\draw [->] (node4.south east) to [bend left] (node3.north east);
\end{tikzpicture}
  • Sorry I took so long to reply. I like the last one. Just curious, If I want to use it in a matrix of nodes how could I modify the code to move the co-ordinates? – Adriala Feb 26 '16 at 6:42
  • Which coordinate do you want to move? When calling \mynode the second argument passes to the command the coordinate of the node so that you don't need to modify the code. If you want to make a matrix of nodes you can use the \foreach statement. Feel free to upload a sketch of what you want to achieve and I can look into it. – mirkom Feb 26 '16 at 7:16
  • I think I followed part of that. I'm using it as a node in something called a goal graph, so for any one graph I need to be able to specify any number of nodes (some with this shape and some with others) and then link them with various relationships. I think I worked out a solution that while not elegant gives me the flexibility I need. – Adriala Feb 26 '16 at 7:23
  • I've edited my answer adding another example, hope it clarifies how the command works. The shape of the node is defined such that the splines go through the predefined anchoring points. – mirkom Feb 26 '16 at 7:55
1

So after a big of playing I think this seems fairly simply, and it maintains the curves through size changes, so I can keep the node the same height as the others in my graph.

The shape is not perfect but the anchor points being correct is really important because of how I want to use the shape. (Yes I know I should make a style, I haven't quite figured them out yet though)

\documentclass[tikz]{standalone}
\usepackage{tikz}
\usetikzlibrary{shapes.arrows}
\usepackage{varwidth}

\begin{document}
    \begin{figure}
        \begin{tikzpicture} 
            \node[double arrow, double arrow head indent=-0.8cm, 
                  double arrow head extend=0.13cm, double arrow tip angle=160, 
                  rounded corners = 4pt, text centered, thick, draw, 
                   minimum width=2cm, minimum height=1.5cm, scale=0.5]
                  {\begin{varwidth}{5cm}
                      \centering
                      {Squishy rectangle}
                   \end{varwidth}};
        \end{tikzpicture}
    \end{figure}
\end{document}

possible match scaled shape

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.