railroad-diagrams

Railroad-diagram Generator

NPM version Build Status Dependency Status devDependency Status

<img src=”https://github.com/prantlf/railroad-diagrams/raw/gh-pages/images/rr-title.svg?sanitize=true” alt=”Diagram(Stack(‘Generate’, ‘some’), OneOrMore(NonTerminal(‘railroad diagrams’), Comment(‘and more’)))” title=”Diagram(Stack(‘Generate’, ‘some’), OneOrMore(NonTerminal(‘railroad diagrams’), Comment(‘and more’)))” width=400>

This is a small JavaScript 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. They can easily represent any context-free grammar, and some more powerful grammars. There are several railroad-diagram generators out there, but none of them had the visual appeal I wanted, so I wrote my own.

There’s an online dingus for JavaScript, JSON or YAML input for you to play with and get SVG code from!

This is a fork of the original project with the following ipmrovements:

Diagrams

To use the library, include railroad-diagrams.css in your page, and import the ./railroad-diagrams/lib/index.mjs module in your script, then call the Diagram() function. Its arguments are the components of the diagram (Diagram is a special form of Sequence).

The constructors for each node are named exports in the module; the default export is an object of same-named functions that just call the constructors, so you can construct diagrams without having to spam new all over the place:

// Use the constructors
import {Diagram, Choice} from "./railroad-diagrams/lib/index.mjs";
const d = new Diagram("foo", new Choice(0, "bar", "baz"));

// Or use the functions that call the constructors for you
import rr from "./railroad-diagrams/lib/index.mjs";
const d = rr.Diagram("foo", rr.Choice(0, "bar", "baz"));

// Or use the JSON serialization of the diagram
import {Diagram} from "./railroad-diagrams/lib/index.mjs";
const d = Diagram.fromJSON([
  { type: 'Terminal', text: 'foo' }.
  { type: 'Choice', normalIndex: 0, options: [
      { type: 'Terminal', text: 'bar' }, { type: 'Terminal', text: 'baz' }
    ] }
]);

Alternately, you can call ComplexDiagram(); it’s identical to Diagram(), but has slightly different start/end shapes, same as what JSON.org does to distinguish between “leaf” types like number (ordinary Diagram()) and “container” types like Array (ComplexDiagram()).

The Diagram class also has a few methods:

CDN

You can include a specific version of this library on a plain web page using the service provided by unpkg:

<link rel="stylesheet"
      href="https://unpkg.com/@prantlf/railroad-diagrams@1.0.0/railroad-diagrams.css">
<script src="https://unpkg.com/@prantlf/railroad-diagrams@1.0.0/lib/index.umd.min.js"></script>

Node.js

You can install the library using the Node.js 6 or newer. For example, with npm or yarn:

npm i @prantlf/railroad-diagrams
yarn add @prantlf/railroad-diagrams

Exports of the library can be consumed in ESM modules similarly as it is documented for the web pages above:

// Use the constructors os the static Diagram.fromJSON
import {Diagram, Choice} from "@prantlf/railroad-diagrams";

// Or use the functions that call the constructors for you
import rr from "@prantlf/railroad-diagrams";

Exports of the library can be consumed in CJS modules too:

// Use the constructors os the static Diagram.fromJSON
const {Diagram, Choice} = require("@prantlf/railroad-diagrams");

// Or use the functions that call the constructors for you
const rr = require("@prantlf/railroad-diagrams").default;

Make sure, that you do not call methods addTo and toSVG, which work inly in the web browser. You can generate an SVG by toString or toStandalone.

Command-line Tools

If you install the library using the Node.js 6 or newer globally:

npm i -g railroad-diagrams

You will be able to execute the following command line tools:

$ rrdlint -h
Usage: rrdlint [option...] [pattern...]

Options:
  -i|--input <type>  read input from json, yaml or javascript. defaults to json
  -v|--verbose       print checked file names and error stacktrace
  -V|--version       print version number
  -h|--help          print usage instructions

Examples:
  cat foo.yaml | rrdlint -i yaml
  rrdlint diagrams/*
$ rrd2svg -h
Usage: rrd2svg [option...] [file]

Options:
  --[no]-standalone  add stylesheet to the SVG element. defaults to true
  --[no]-debug       add sizing data into the SVG element. defaults to false
  -i|--input <type>  read input from json, yaml or javascript. defaults to json
  -v|--verbose       print error stacktrace
  -V|--version       print version number
  -h|--help          print usage instructions

Examples:
  cat foo.yaml | rrd2svg -i yaml
  rrd2svg foo.json

If no file name or file name pattern is provided, standard input will be read. If no input type is provided, it will be inferred from the file extension: “.json” -> json, “.yaml” or “.yml” -> yaml, “.js” -> javascript.

Components

Components are either leaves or containers.

The leaves:

The containers:

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, in an Options object exported from the module. 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 options specifying the text metrics as well.

JSON

Diagrams can be created from a JSON serialization using Diagram.fromJSON(input) or ComplexDiagram.fromJSON(input). (If the JSON input starts with a Diagram or ComplexDigram node, it will be honoured and the parent class of fromJSON will not apply.)

The JSON serialization can be a single object or an array of objects in the format { "type": "...", ...parameters }, where type is a class name of a node and parameters are constructor arguments following more-or-less closely the naming conventions from the documentation above.

{ "type": "Diagram", "items" }
{ "type": "ComplexDiagram", "items" }

{ "type": "Terminal, "text", "href", "title" }
{ "type": "NonTerminal", "text", "href", "title" }
{ "type": "Comment", "text", "href", "title" }
{ "type": "Skip" }
{ "type": "Start", "startType", "label" }
{ "type": "End", "endType" }

{ "type": "Sequence", "items" }
{ "type": "Stack", "items" }
{ "type": "OptionalSequence", "items" }
{ "type": "Sequence", "items" }
{ "type": "Choice", "normalIndex", "options" }
{ "type": "MultipleChoice", "normalIndex", "choiceType", "options" }
{ "type": "HorizontalChoice", "options" }
{ "type": "Optional", "item", "skip" }
{ "type": "OneOrMore", "item", "repeat" }
{ "type": "AlternatingSequence", "option1", "option2" }
{ "type": "ZeroOrMore", "item", "repeat", "skip" }
{ "type": "Group", "item", "label" }

If the diagram input should be edited manually, using YAML instead of JSON will make maintenance easier. YAML can be converted to JSON before calling fromJSON.

Performance

The difference between using constructors or functions to create diagram nodes is negligible. Parsing the JSON serialization is only a little slower. Results from generating a all example diagrams using Node.js 12 on Macbook Pro 2018 with i7 2,6 GHz:

Creating 17 diagrams...
  using functions x 29,989 ops/sec ±0.56% (95 runs sampled)
  using constructors x 30,651 ops/sec ±0.28% (97 runs sampled)
  using fromJSON x 23,038 ops/sec ±0.50% (95 runs sampled)
The fastest one was using constructors.

Caveats

SVG can’t actually respond to the sizes of content; in particular, there’s no way to make SVG adjust sizing/positioning based on the length of some text. Instead, I guess at some font metrics, which mostly work as long as you’re using a fairly standard monospace font. This works pretty well, but long text inside of a construct might eventually overflow the construct.

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.)