Permalink
212 lines (172 sloc) 6.63 KB

Visualization

It can be very helpful to visualize your state machine as a directed graph. This is possible with the open source GraphViz library if we convert from our state machine configuration to the .dot language expected by GraphViz using the visualize method:

  var visualize = require('javascript-state-machine/lib/visualize');

  var fsm = new StateMachine({
    init: 'open',
    transitions: [
      { name: 'close', from: 'open',   to: 'closed' },
      { name: 'open',  from: 'closed', to: 'open'   }
    ]
  });

  visualize(fsm)

Generates the following .dot syntax:

  digraph "fsm" {
    "closed";
    "open";
    "closed" -> "open" [ label=" open " ];
    "open" -> "closed" [ label=" close " ];
  }

Which GraphViz displays as:

door

Enhanced Visualization

You can customize the generated .dot output - and hence the graphviz visualization - by attaching dot attributes to your transitions and (optionally) declaring an orientation:

  var fsm = new StateMachine({
    init: 'closed',
    transitions: [
      { name: 'open',  from: 'closed', to: 'open',   dot: { color: 'blue', headport: 'n', tailport: 'n' } },
      { name: 'close', from: 'open',   to: 'closed', dot: { color: 'red',  headport: 's', tailport: 's' } }
    ]
  });
  visualize(fsm, { name: 'door', orientation: 'horizontal' });

Generates the following (enhanced) .dot syntax:

  digraph "door" {
    rankdir=LR;
    "closed";
    "open";
    "closed" -> "open" [ color="blue" ; headport="n" ; label=" open " ; tailport="n" ];
    "open" -> "closed" [ color="red" ; headport="s" ; label=" close " ; tailport="s" ];
  }

Which GraphViz displays as:

door

Visualizing State Machine Factories

You can use the same visualize method to generate .dot output for a state machine factory:

  var Matter = StateMachine.factory({
    init: 'solid',
    transitions: [
      { name: 'melt',     from: 'solid',  to: 'liquid', dot: { headport: 'nw' } },
      { name: 'freeze',   from: 'liquid', to: 'solid',  dot: { headport: 'se' } },
      { name: 'vaporize', from: 'liquid', to: 'gas',    dot: { headport: 'nw' } },
      { name: 'condense', from: 'gas',    to: 'liquid', dot: { headport: 'se' } }
    ]
  });

  visualize(Matter, { name: 'matter', orientation: 'horizontal' })

Generates the following .dot syntax:

  digraph "matter" {
    rankdir=LR;
    "solid";
    "liquid";
    "gas";
    "solid" -> "liquid" [ headport="nw" ; label=" melt " ];
    "liquid" -> "solid" [ headport="se" ; label=" freeze " ];
    "liquid" -> "gas" [ headport="nw" ; label=" vaporize " ];
    "gas" -> "liquid" [ headport="se" ; label=" condense " ];
  }

Which GraphViz displays as:

matter

Other Examples

  var Wizard = StateMachine.factory({
    init: 'A',
    transitions: [
      { name: 'step',  from: 'A',               to: 'B', dot: { headport: 'w',  tailport: 'ne' } },
      { name: 'step',  from: 'B',               to: 'C', dot: { headport: 'w',  tailport: 'e' } },
      { name: 'step',  from: 'C',               to: 'D', dot: { headport: 'w',  tailport: 'e' } },
      { name: 'reset', from: [ 'B', 'C', 'D' ], to: 'A', dot: { headport: 'se', tailport: 's' } }
    ]
  });

  visualize(Wizard, { orientation: 'horizontal' })

Generates:

  digraph "wizard" {
    rankdir=LR;
    "A";
    "B";
    "C";
    "D";
    "A" -> "B" [ headport="w" ; label=" step " ; tailport="ne" ];
    "B" -> "C" [ headport="w" ; label=" step " ; tailport="e" ];
    "C" -> "D" [ headport="w" ; label=" step " ; tailport="e" ];
    "B" -> "A" [ headport="se" ; label=" reset " ; tailport="s" ];
    "C" -> "A" [ headport="se" ; label=" reset " ; tailport="s" ];
    "D" -> "A" [ headport="se" ; label=" reset " ; tailport="s" ];
  }

Displays:

wizard

  var ATM = StateMachine.factory({
    init: 'ready',
    transitions: [
      { name: 'insert-card', from: 'ready',              to: 'pin'                },
      { name: 'confirm',     from: 'pin',                to: 'action'             },
      { name: 'reject',      from: 'pin',                to: 'return-card'        },
      { name: 'withdraw',    from: 'return-card',        to: 'ready'              },

      { name: 'deposit',     from: 'action',             to: 'deposit-account'    },
      { name: 'provide',     from: 'deposit-account',    to: 'deposit-amount'     },
      { name: 'provide',     from: 'deposit-amount',     to: 'confirm-deposit'    },
      { name: 'confirm',     from: 'confirm-deposit',    to: 'collect-envelope'   },
      { name: 'provide',     from: 'collect-envelope',   to: 'continue'           },

      { name: 'withdraw',    from: 'action',             to: 'withdrawal-account' },
      { name: 'provide',     from: 'withdrawal-account', to: 'withdrawal-amount'  },
      { name: 'provide',     from: 'withdrawal-amount',  to: 'confirm-withdrawal' },
      { name: 'confirm',     from: 'confirm-withdrawal', to: 'dispense-cash'      },
      { name: 'withdraw',    from: 'dispense-cash',      to: 'continue'           },

      { name: 'continue',    from: 'continue',           to: 'action'             },
      { name: 'finish',      from: 'continue',           to: 'return-card'        }
    ]
  })

  visualize(ATM)

Generates:

  digraph "ATM" {
    "ready";
    "pin";
    "action";
    "return-card";
    "deposit-account";
    "deposit-amount";
    "confirm-deposit";
    "collect-envelope";
    "continue";
    "withdrawal-account";
    "withdrawal-amount";
    "confirm-withdrawal";
    "dispense-cash";
    "ready" -> "pin" [ label=" insert-card " ];
    "pin" -> "action" [ label=" confirm " ];
    "pin" -> "return-card" [ label=" reject " ];
    "return-card" -> "ready" [ label=" withdraw " ];
    "action" -> "deposit-account" [ label=" deposit " ];
    "deposit-account" -> "deposit-amount" [ label=" provide " ];
    "deposit-amount" -> "confirm-deposit" [ label=" provide " ];
    "confirm-deposit" -> "collect-envelope" [ label=" confirm " ];
    "collect-envelope" -> "continue" [ label=" provide " ];
    "action" -> "withdrawal-account" [ label=" withdraw " ];
    "withdrawal-account" -> "withdrawal-amount" [ label=" provide " ];
    "withdrawal-amount" -> "confirm-withdrawal" [ label=" provide " ];
    "confirm-withdrawal" -> "dispense-cash" [ label=" confirm " ];
    "dispense-cash" -> "continue" [ label=" withdraw " ];
    "continue" -> "action" [ label=" continue " ];
    "continue" -> "return-card" [ label=" finish " ];
  }

Displays:

atm