View on GitHub

GraphFellow

GraphFellow: directed graphs in JavaScript

Download this project as a .zip file Download this project as a tar.gz file

beta: GraphFellow is still in development!

GraphFellow example: Galton Board

The Galton Board is used to show how normal distribution emerges from a combination of choices (Pascal’s triangle is in here too, if you start counting the number of paths). Each marble starts at the top and makes a fifty-fifty left-right decision at every vertex. Vertices count the number of marbles that have passed through. Marbles appear every tick (about 2 seconds) or when you click on that top node.

Just show the graph →


Behind the scenes

The Galton Board example drops a new marble on every tick event (which is a little slower than every 2 seconds). Extra marbles appear if you click on the vertex at the top of the graph. Each vertex counts the number of marbles that have rolled through it.

HTML setup

First things first… like all the examples, this page starts by loading the libraries GraphFellow needs:

<script src="../../vendor/pixi.min.js"></script>
<script src="../../vendor/greensock-js/TweenMax.min.js"></script>

The container for the graph links to the JSON file that contains the graph’s config (see below):

<div class="graphfellow" data-graph-src="galton.json" ></div>

Before defining custom functions, the GraphFellow library itself is loaded:

<script src="../../graphfellow.js"></script>

Config in the JSON

As the data-graph-src attribute shows, the graph is defined in galton.json. You can click on that and inspect the whole file, but the rest of this page describes the JSON piece-by-piece.

There are 28 vertices: six rows, with one vertex in the top row, and six in the bottom row. Each one needs a unique id (so the edges between them can be defined). The system used is nm where n is the row number and m is the count: so 00 is the id of the top node, and 66 the last (seventh) node in the bottom (seventh) row.

The top vertex (00) is special: it has an on_click event that causes a new marble (traveller) to appear, and has a different colour (pale blue). The bottom row of vertices also have a different colour (pale pink).

"vertices": [
  {"id": "00", "x": 500, "y":  50, "payload": 0,
     "on_click": "drop_new_marble", "fill_color": "0xeeeeff"},
  {"id": "10", "x": 440, "y": 110, "payload": 0},
  {"id": "11", "x": 560, "y": 110, "payload": 0},
  {"id": "20", "x": 380, "y": 170, "payload": 0},
  ...
  ...
  {"id": "66", "x": 860, "y": 410, "payload": 0, "fill_color": "0xffeeee"}
],

The edges (there are 42) are systematic once those vertices have been created — here’s just some of them:

  "edges": [
    {"from": "00", "to": "10"},
    {"from": "00", "to": "11"},
    {"from": "10", "to": "20"},
    {"from": "10", "to": "21"},
    {"from": "11", "to": "21"},
    {"from": "11", "to": "22"},
    {"from": "20", "to": "30"},
    {"from": "20", "to": "31"},
    {"from": "21", "to": "31"},
    {"from": "21", "to": "32"},
    ...
    ...
    {"from": "55", "to": "66"}
],

There are no travellers (or marbles) defined, because they are created by the on_click or on_tick events.

The grid_height for this graph is 500 — the grid_width is 1000 by default, so this describes a graph that is twice as wide as it is tall. See the documentation about the dimensions of graphs for more about how this works.

The config for the graph also defines the on_tick behaviour: that’s an event that happens, like the tick of a clock, at regular intervals. The same function that’s the on_click event for the top node (dropping a marble) is used. The tick_period is a little over 2 seconds. The default journey along an edge is 1 second, so making the period between new marbles being dropped slightly out of synch with that makes the behaviour a little more aesthetic.

  "config": {
    "grid_height": 500,
    "on_tick": "drop_new_marble",
    "tick_period": 2.2,

The config for the vertices sets a pale yellow (which is overridden in the definitions, above, for individual vertices: blue at the top, and red at the bottom), and makes the font small enough to comfortably show 3 or even 4 digits in the node (the number of marbles dropped will get bigger and bigger as the animation runs).

    "vertices": {
      "fill_color": "0xffffee",
      "is_pulse_blur": false,
      "pulse_scale": 1.3,
      "is_displaying_payload": true,
      "text_font_size": 16
    },

By default, edges have arrowheads, but this graph is drawn more simply without them so the config suppresses them:

    "edges": {
      "is_arrow": false
    },

Finally, each traveller (which by default is a “spot” or circle) has an on_arrival event that will increment the count at the vertex at which it’s just arrived. The is_above_vertices setting ensures that the marbles are always visible as they roll over the graph.

    "travellers": {
      "radius": 14,
      "on_arrival": "marble_arrives",
      "is_above_vertices": true
    }
  } 

Defining the custom functions

All the custom functions are all added with GraphFellow.add_function(). It’s essential that the names in the config (JSON) matches the name with which the functions are added.

The travellers in this graph could have been given a journey_lifespan of 6, which would automatically destroy each marble when it got to the bottom of the graph (an inevitably 6 journeys from the top). But the config doesn’t specify that, so it defaults to 0 (immortal). Instead the on_arrival function explicitly tests for the marble’s sixth journey (see marble_arrives below).

For clarity, some constants, including six colours for the marbles:

const max_cascade_depth = 6;
const start_vertex_id = "00";
const rainbow_colors = [0xee4035, 0xf37736, 0xfdf498, 0x7bc043, 0x0392cf, 0x4b0082];

The first function creates a new traveller (a marble). Marbles always start at the top vertex, which has the id 00. The vertex pulses when the marble is created — blue if this is a result of the regular tick, but green if the user has actively dropped a marble by clicking on the vertex. Each time a marble is dropped on the top node, the vertex’s payload (counting the number of marbles) is incremented using payload.set(), which updates the value and its display.

The marbles make their left-right decision using the GraphVertex.get_random_edge_out() method. On this graph, every vertex has exactly two edges out (except the bottom row, which has none), so get_random_edge_out() is always choosing either left of right. The settings passed into create_traveller override the defaults set in the graph’s JSON config (for example, the fill_color). The payload value of the top node increments every time you drop a new marble, so it’s being used to cycle through the colour each marble is created with:

  GraphFellow.add_function("drop_new_marble", function(event, graph){
    let v = graph.get_vertex_by_id(start_vertex_id);
    v.pulse(event.type === 'tick'? 0x0000ff:0x00ff00); // green for tap/click
    graph.create_traveller({
       "at_vertex": v, 
       "fill_color": rainbow_colors[ v.payload.value % rainbow_colors.length]
    }).travel(v.get_random_edge_out());
    v.payload.set(v.payload.value+1);
  });

Each marble’s on_arrival method increments the payload of the vertex it is passing through. If it’s reached the last row, the vertex is pulsed, and the marble self-destructs by calling its own destroy() method.

GraphFellow.add_function("marble_arrives", function(event, graph){
  this.at_vertex.payload.set(this.at_vertex.payload.value+1);
  if (this.qty_journeys < max_cascade_depth) {
    this.travel(this.at_vertex.get_random_edge_out());
  } else {
    this.at_vertex.pulse();
    this.destroy(); // alternatively, config could set journey_lifespan: 6
  }
});

All done: create the graph

Finally, the graph is initialised by an explicit call to init():

GraphFellow.init();

This finds the container with class graphfellow, reads the JSON linked by the data-graph-src attribute it find there, and initialises the graph. There’s no on_init function to run, but — because the tick_period is not zero — the on_tick function will be called a little over 2 seconds later, and marbles will start to drop.