What is railroad-diagrams?
The railroad-diagrams npm package is a library that allows users to generate SVG diagrams that visually represent context-free grammars, typically used in documentation for programming languages, command line tools, or web APIs. These diagrams are known as 'railroad diagrams' or 'syntax diagrams' and provide a visual way to describe the grammar in a more readable format than plain text or regular expressions.
What are railroad-diagrams's main functionalities?
Creating simple diagrams
This code sample demonstrates how to create a simple railroad diagram with two nodes, 'A' and 'B', connected by an arrow. The resulting SVG is then appended to the body of the document.
var Diagram = require('railroad-diagrams');
var diagram = Diagram('A', '->', 'B');
document.body.appendChild(diagram.toSVG());
Complex diagrams with choices and sequences
This code sample shows how to create a more complex railroad diagram with a sequence of nodes, including a choice between 'B' and 'C'. The resulting SVG is appended to the body of the document.
var Diagram = require('railroad-diagrams');
var diagram = Diagram(
Diagram.Sequence('A', Diagram.Choice(0, 'B', 'C'), 'D')
);
document.body.appendChild(diagram.toSVG());
Optional and repeated elements
This code sample illustrates how to create a railroad diagram with optional and repeated elements. 'B' is marked as optional, and 'C' can be repeated zero or more times.
var Diagram = require('railroad-diagrams');
var diagram = Diagram(
'A',
Diagram.Optional('B'),
Diagram.ZeroOrMore('C'),
'D'
);
document.body.appendChild(diagram.toSVG());
Comments and styling
This code sample demonstrates how to add comments to the diagram and apply custom styling to the SVG elements.
var Diagram = require('railroad-diagrams');
var diagram = Diagram(
Diagram.Comment('Start'),
'A',
Diagram.Comment('End')
);
diagram.addTo(document.body, {stroke: '#000', 'stroke-width': 3});
Other packages similar to railroad-diagrams
js-sequence-diagrams
The js-sequence-diagrams package allows users to turn text into UML sequence diagrams. It is similar to railroad-diagrams in that it also generates diagrams from textual descriptions, but it focuses on sequence diagrams rather than syntax diagrams.
mermaid
Mermaid is a popular package that lets you create diagrams and visualizations using text and code. It supports a variety of diagram types including flowcharts, sequence diagrams, class diagrams, and more. While it offers broader functionality compared to railroad-diagrams, it also includes the ability to create syntax diagrams similar to railroad diagrams.
viz.js
Viz.js is a package that provides a way to render Graphviz graphs in the browser. It can be used to create various types of diagrams and visualizations, including those similar to railroad diagrams. It is more general-purpose compared to railroad-diagrams, which is specialized for creating railroad syntax diagrams.
Railroad-diagram Generator
This is a small js library for generating railroad diagrams
(like what JSON.org uses)
using SVG.
Railroad diagrams are a way of visually representing a grammar
in a form that is more readable than using regular expressions or BNF.
I think (though I haven't given it a lot of thought yet) that if it's easy to write a context-free grammar for the language,
the corresponding railroad diagram will be easy as well.
There are several railroad-diagram generators out there, but none of them had the visual appeal I wanted.
Here's an example of how they look!
And here's an online generator for you to play with and get SVG code from!
The library now exists in a Python port as well! See the information further down.
Details
To use the library, just include the js and css files, and then call the Diagram() function.
Its arguments are the components of the diagram (Diagram is a special form of Sequence).
An alternative to Diagram() is ComplexDiagram() which is used to describe a complex type diagram.
Components are either leaves or containers.
The leaves:
- Terminal(text) or a bare string - represents literal text
- NonTerminal(text) - represents an instruction or another production
- Comment(text) - a comment
- Skip() - an empty line
The containers:
- Sequence(children) - like simple concatenation in a regex
- Choice(index, children) - like | in a regex. The index argument specifies which child is the "normal" choice and should go in the middle
- Optional(child, skip) - like ? in a regex. A shorthand for
Choice(1, [Skip(), child])
. If the optional skip
parameter has the value "skip"
, it instead puts the Skip() in the straight-line path, for when the "normal" behavior is to omit the item. - OneOrMore(child, repeat) - like + in a regex. The 'repeat' argument is optional, and specifies something that must go between the repetitions.
- ZeroOrMore(child, repeat, skip) - like * in a regex. A shorthand for
Optional(OneOrMore(child, repeat))
. The optional skip
parameter is identical to Optional().
For convenience, each component can be called with or without new
.
If called without new
,
the container components become n-ary;
that is, you can say either new Sequence([A, B])
or just Sequence(A,B)
.
After constructing a Diagram, call .format(...padding)
on it, specifying 0-4 padding values (just like CSS) for some additional "breathing space" around the diagram (the paddings default to 20px).
The result can either be .toString()
'd for the markup, or .toSVG()
'd for an <svg>
element, which can then be immediately inserted to the document. As a convenience, Diagram also has an .addTo(element)
method, which immediately converts it to SVG and appends it to the referenced element with default paddings. element
defaults to document.body
.
Options
There are a few options you can tweak, at the bottom of the file. Just tweak either until the diagram looks like what you want.
You can also change the CSS file - feel free to tweak to your heart's content.
Note, though, that if you change the text sizes in the CSS,
you'll have to go adjust the metrics for the leaf nodes as well.
- VERTICAL_SEPARATION - sets the minimum amount of vertical separation between two items. Note that the stroke width isn't counted when computing the separation; this shouldn't be relevant unless you have a very small separation or very large stroke width.
- ARC_RADIUS - the radius of the arcs used in the branching containers like Choice. This has a relatively large effect on the size of non-trivial diagrams. Both tight and loose values look good, depending on what you're going for.
- DIAGRAM_CLASS - the class set on the root
<svg>
element of each diagram, for use in the CSS stylesheet. - STROKE_ODD_PIXEL_LENGTH - the default stylesheet uses odd pixel lengths for 'stroke'. Due to rasterization artifacts, they look best when the item has been translated half a pixel in both directions. If you change the styling to use a stroke with even pixel lengths, you'll want to set this variable to
false
. - INTERNAL_ALIGNMENT - when some branches of a container are narrower than others, this determines how they're aligned in the extra space. Defaults to "center", but can be set to "left" or "right".
Caveats
At this early stage, the generator is feature-complete and works as intended, but still has several TODOs:
- The font-sizes are hard-coded right now, and the font handling in general is very dumb - I'm just guessing at some metrics that are probably "good enough" rather than measuring things properly.
Python Port
In addition to the canonical JS version, the library now exists as a Python library as well.
Using it is basically identical. The config variables are globals in the file, and so may be adjusted either manually or via tweaking from inside your program.
The main difference from the JS port is how you extract the string from the Diagram. You'll find a writeSvg(writerFunc)
method on Diagram
, which takes a callback of one argument and passes it the string form of the diagram. For example, it can be used like Diagram(...).writeSvg(sys.stdout.write)
to write to stdout. Note: the callback will be called multiple times as it builds up the string, not just once with the whole thing. If you need it all at once, consider something like a StringIO
as an easy way to collect it into a single string.
License
This document and all associated files in the github project are licensed under CC0 .
This means you can reuse, remix, or otherwise appropriate this project for your own use without restriction.
(The actual legal meaning can be found at the above link.)
Don't ask me for permission to use any part of this project, just use it.
I would appreciate attribution, but that is not required by the license.