How to create custom shapes

Graphviz custom shapes

As alluded to in the dot user's guide, there are several ways to incorporate custom shapes. At this point, they either must be in PostScript or image files, or you'll need to modify the source code. A serious problem is that you can't make custom shapes that work across all the drivers and the interactive front ends such as dotty or Grappa. At least SVG has interactive renderers, and PostScript can be translated to PDF which also has some interactive features. Improving the architecture of the graphics drivers is an ongoing project of ours.

External image files

If using SVG (-Tsvg), PostScript (-Tps,-Tps2) or one of the raster formats (-Tgif, -Tpng, or -Tjpg), you can load certain images (e.g., pictures) by file name into nodes. For example:

   yournode [image="yourface.gif"];

indicates the contents of the node are given in the GIF file yourface.gif. The image attribute specifies which file to use. There is also the deprecated shapefile attribute. This is similar to image but the node shape will always be a box.

NOTE: In versions before 11 March 2006, in particular, 1.12 graphviz and earlier, it is necessary to also set the attribute shape=custom.

With -Tsvg, image must give the name of a file containing a GIF, PNG or JPEG bitmap file. Note that the file's contents are not copied into the SVG output, only the files name. Thus, for Graphviz SVG output to display correctly, the image file must be available to the SVG viewer.

With PostScript, image must give the name of a file containing encapsulated PostScript or a bitmap. The contents are copied into the output file. Note that encapsulated PostScript will only be copied in once. The restrictions on the contents of the image are the same as those specified below under External PostScript files.

For bitmap output, image is a file name containing a bitmap image. The file is opened and copied (and possibly scaled) into the output drawing.

This code is still preliminary, and we have noted some problems with color quantization in indexed color map management that we are trying to understand and correct. (You can use -Gtruecolor=1 to try a 32 bit internal canvas as an alternative, but we have observed fuzziness (lossiness?) in the images.)

When the software is used as a web server, access to image files is more restrictive. See GV_FILE_PATH and SERVER_NAME.

External PostScript files

If using the PostScript driver (-Tps) you can import node shapes as external PostScript files such as EPS (Encapsulated PostScript). At a minimum, the external file must have a valid BoundingBox header and not do drastic things to the graphics state since we don't install a wrapper for example to inhibit showpage.

To import an external PostScript file, set the shape and shapefile attributes as shown here:

 
somenode [shape=epsf, shapefile="yourfile.ps" ];

An EPSF shape is always clipped to its bounding box.

The use of [shape=epsf, shapefile="yourfile.ps" ] is largely superceded by the mechanism described in the previous section, using [image="yourfile.ps" ].

External PostScript procedures

If using the PostScript driver (dot -Tps), you can define a PostScript procedure for shape drawing. The procedure must be able to draw variable-sized shapes. A file containing the definition can be loaded as a command line argument using the -l flag:


dot -Tps -l yourPS.ps file.dot -o file.ps

In the graph file, invoke the shape like this:


somenode [shape=yourshape]

In file.ps, for non-filled nodes, the procedure for yourshape will be called like this:


[ 54 36 0 36 0 0 54 0 54 36 ] 4 false yourshape

where the current color is the node's pencolor. The array contains the shape's bounding polygon, with the first point repeated at the end, followed by the number of points. At present, the shape is always a rectangle. From left to right, the points in the array always go counterclockwise, starting with the upper-right vertex. The boolean value after the number of vertices, here false, is the value of the node's "fill" attribute. The coordinates are absolute canvas coordinates.

For nodes with fill=true, the above invocation of yourshape will be preceded by


[ 54 36 0 36 0 0 54 0 54 36 ] 4 true yourshape

where the current color is the node's fillcolor.

NOTE: In versions before 23 September 2005, yourshape is only invoked once, with the node's fill value and with the color set to the node's pencolor.

For example, here are the contents of a plausible shape file, DFD.ps, which can be invoked by [shape=DFDbox]

	/xdef {exch def} bind def
	/DFDbox {
		10 dict begin
			/fflag xdef
			/sides xdef
			fflag   % if shape is filled
			{
				aload pop
				newpath
				moveto
				1 1 sides { pop lineto } for
				closepath fill
			}
			{
				aload pop
				% draw the sides
				newpath
				moveto
				1 1 sides {
					2 mod 0 ne
					{moveto} % even sides
					{lineto currentpoint stroke moveto} % odd sides
					ifelse
				} for
            }
			ifelse
		end
	} bind def

A custom shape of this kind is always clipped to its bounding box. It would not be hard to create a hook in the function user_shape() in shapes.c to determine clipping polygons other than rectangles (maybe) in case someone wants to try this and contribute the code for this.

Note that, by default, the bounding box is drawn around the contents and the node label is drawn. If you wish to eliminate these, set label="" and peripheries=0 in the node.

Driver-independent custom shapes

If not using PostScript, you'll need to roll up your sleeves and modify the source. None of the other code generators support custom node shapes directly, yet. If the custom shape is to be high-level and driver-independent, then you can add shape-specific functions (methods) to shapes.c with a corresponding entry in the array Shapes[] that maps shape names to methods. The method interfaces are described in the comment header in this file. Methods must be defined to initialize a shape (typically to size it big enough to fit its text label), bind a port name to a coordinate, test if a point is inside an instance of the shape (for edge clipping), generate code for the shape via the functions provided in the gvrender_engine_t structure, and return a box path to reach ports on the interior of a node (if they can exist).

More information on the functions available via gvrender_engine_t and the Graphviz graphics model can be found in Section 5 of the Graphviz library manual.

Shapes that behave more or less like polygons can be bootstrapped from the basic polygon methods; see for example the "invtri" or "tab" shape. Such shapes use a polygon descriptor whose fields are listed below.

Polygon descriptor fields
regul if a regular polygon FALSE
perip number of border peripheries 1
sides number of sides (1 for curves) 4
orien angular rotation in degrees 0
disto trapezoidal distortion 0
skew parallelogram distortion 0
flags fancy options: ROUNDED, DIAGONALS, AUXLABELS 0

For example shapes not derived from general polygons, see the record or epsf shape.

Driver-dependent custom shapes

To implement a driver-specific shape (such as GIF or PNG icons) you'll need to write a body for the driver function that implements user-defined shapes. This involves providing the library_shape function for the specific driver, if it does not already exist, and generating the graphics operations needed to display your shape using the driver's graphics functions. (The drivers supplied with Graphviz can be found in the plugins directory.)

A user shape function basically receives four arguments:
- the custom shape name string
- the absolute canvas coords of the shape bounding polygon
- number of coords (currently, always 4)
- fill flag

The rest is up to you, but contact us first just in case.

 

SVG imagepath

Hi,
I have a problem with custom shapes (for a node) and svg. I know what should be the link to the img in the svg output (image xlink:href="../_static/cloud.png" ...) but if the image is not found at generation time, the tag is not generated.
I don't see how I can come up with a (relative) path that would work both at generation time and online and I don't want to rely on a full URL. I am actually using graphviz with Sphinx (rst).
I have tried multiple times with different settings but with no luck so far.
Any help appreciated ?
Thanks

Recent comments