Skip to content

Commit

Permalink
much improved visualizations
Browse files Browse the repository at this point in the history
  • Loading branch information
StoneCypher committed May 10, 2017
1 parent be73cbb commit 88bd317
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 17 deletions.
12 changes: 9 additions & 3 deletions docs/do want.md
Expand Up @@ -3,10 +3,10 @@
- [x] action names (edge names are unique, action names are unique-to-source,)
- [ ] the probability of an edge,
- [ ] being the most probable edge,
- [ ] which states are "complete" (that is, that an input sequence can be considered satisfactorily terminal),
- [ ] whether a machine is
- [x] which states are "complete" (that is, that an input sequence can be considered satisfactorily terminal),
- [x] whether a machine is
- [x] complete,
- [ ] final (not is_changing, complete, and terminal),
- [x] final (not is_changing, complete, and terminal),
- [ ] edges as force-only (eg to "off" in the traffic light)
- [ ] actions as force-only (also eg to "off")
- [ ] the ability to list
Expand Down Expand Up @@ -64,6 +64,12 @@
- [ ] data change,
- [ ] init,
- [ ] non-matching event
- [ ] timers (todo needs fleshing out)
- [ ] language support?
- [ ] promise support?
- [ ] generator support?
- [ ] observable support?
- [ ] async await support?
- [x] the ability to generate
- [x] flowchart representations
- [x] as DOT strings
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "jssm",
"version": "0.15.0",
"version": "0.16.0",
"description": "A Javascript state machine with a simple API. Well tested, and typed with Flowtype. MIT License.",
"main": "dist/jssm.es5.browserified.js",
"scripts": {
Expand Down
62 changes: 62 additions & 0 deletions src/js/jssm-tests.js
Expand Up @@ -44,6 +44,68 @@ describe('Simple stop light', async it => {



describe('Stochastic weather', async it => {

const weather = new jssm.machine({

initial_state: 'breezy',

transitions:[

{ from: 'breezy', to: 'breezy', probability: 0.4 },
{ from: 'breezy', to: 'sunny', probability: 0.3 },
{ from: 'breezy', to: 'cloudy', probability: 0.15 },
{ from: 'breezy', to: 'windy', probability: 0.1 },
{ from: 'breezy', to: 'rain', probability: 0.05 },

{ from: 'sunny', to: 'sunny', probability: 0.5 },
{ from: 'sunny', to: 'hot', probability: 0.15 },
{ from: 'sunny', to: 'breezy', probability: 0.15 },
{ from: 'sunny', to: 'cloudy', probability: 0.15 },
{ from: 'sunny', to: 'rain', probability: 0.05 },

{ from: 'hot', to: 'hot', probability: 0.75 },
{ from: 'hot', to: 'breezy', probability: 0.05 },
{ from: 'hot', to: 'sunny', probability: 0.2 },

{ from: 'cloudy', to: 'cloudy', probability: 0.6 },
{ from: 'cloudy', to: 'sunny', probability: 0.2 },
{ from: 'cloudy', to: 'rain', probability: 0.15 },
{ from: 'cloudy', to: 'breezy', probability: 0.05 },

{ from: 'windy', to: 'windy', probability: 0.3 },
{ from: 'windy', to: 'gale', probability: 0.1 },
{ from: 'windy', to: 'breezy', probability: 0.4 },
{ from: 'windy', to: 'rain', probability: 0.15 },
{ from: 'windy', to: 'sunny', probability: 0.05 },

{ from: 'gale', to: 'gale', probability: 0.65 },
{ from: 'gale', to: 'windy', probability: 0.25 },
{ from: 'gale', to: 'torrent', probability: 0.05 },
{ from: 'gale', to: 'hot', probability: 0.05 },

{ from: 'rain', to: 'rain', probability: 0.3 },
{ from: 'rain', to: 'torrent', probability: 0.05 },
{ from: 'rain', to: 'windy', probability: 0.1 },
{ from: 'rain', to: 'breezy', probability: 0.15 },
{ from: 'rain', to: 'sunny', probability: 0.1 },
{ from: 'rain', to: 'cloudy', probability: 0.3 },

{ from: 'torrent', to: 'torrent', probability: 0.65 },
{ from: 'torrent', to: 'rain', probability: 0.25 },
{ from: 'torrent', to: 'cloudy', probability: 0.05 },
{ from: 'torrent', to: 'gale', probability: 0.05 }

]

});

});





describe('Complex stop light', async it => {

const light2 = new jssm.machine({
Expand Down
16 changes: 9 additions & 7 deletions src/js/jssm-types.js
Expand Up @@ -77,13 +77,13 @@ type JssmGenericMachine<NT, DT> = {


type JssmTransition<NT, DT> = {
from : NT,
to : NT,
name? : string,
action? : string,
check? : JssmTransitionPermitterMaybeArray<NT, DT>, // validate this edge's transition; usually about data
likelihood? : number, // for stoch modelling, would like to constrain to [0..1], dunno how
usual? : '' // most common exit, for graphing; likelihood overrides
from : NT,
to : NT,
name? : string,
action? : string,
check? : JssmTransitionPermitterMaybeArray<NT, DT>, // validate this edge's transition; usually about data
probability? : number, // for stoch modelling, would like to constrain to [0..1], dunno how
usual? : '' // most common exit, for graphing; likelihood overrides
};

type JssmTransitions<NT, DT> = Array< JssmTransition<NT, DT> >;
Expand Down Expand Up @@ -114,6 +114,8 @@ type JssmGenericConfig<NT, DT> = {
allow_force? : false,
actions? : JssmPermittedOpt,

simplify_bidi? : boolean,

auto_api? : boolean | string; // boolean false means don't; boolean true means do; string means do-with-this-prefix

};
Expand Down
52 changes: 46 additions & 6 deletions src/js/jssm.js
Expand Up @@ -151,10 +151,14 @@ todo comeback
return this._state;
}

in_flux() : boolean {
is_changing() : boolean {
return true; // todo whargarbl
}

is_final() : boolean {
return ( (!this.is_changing()) && (this.is_terminal()) && (this.is_complete()) );
}



machine_state() : JssmMachineInternalState<mNT, mDT> {
Expand Down Expand Up @@ -308,6 +312,8 @@ todo comeback

// can leave machine in inconsistent state. generally do not use
force_transition(newState : mNT, newData? : mDT) : boolean {
// todo whargarbl implement hooks
// todo whargarbl implement data stuff
if (this.valid_force_transition(newState, newData)) {
this._state = newState;
return true;
Expand Down Expand Up @@ -340,17 +346,51 @@ todo comeback

const nodes = l_states.map( (s:any) => `${node_of(s)} [label="${s}"];`).join(' ');

const edges = this.states().map( (s:any) =>
const strike = [];
const edges = this.states().map( (s:any) =>

this.exits_for(s).map( (ex:any) => {
const edge = this.edge(s, ex),
label = edge? (edge.name || undefined) : undefined;
return `${node_of(s)}->${node_of(ex)} [${label? `label="${(label:any)}"`:''} len=2];`;

if ( strike.find(row => (row[0] === s) && (row[1] == ex) ) ) {
return ''; // already did the pair
}

const edge = this.edge(s, ex),
pair = this.edge(ex, s),
double = pair && (s !== ex),

// label = edge ? ([edge.name?`${(edge.name:any)}`:undefined,`${(edge.probability:any)}`]
// .filter(not_undef => !!not_undef)
// .join('\n') || undefined
// ) : undefined,

if_obj_field = (obj, field) => obj? obj[field] : undefined,

label = edge ? (`label="${ (edge.name : any)}";` || '') : '',
headlabel = pair ? (`headlabel="${(pair.probability : any)}";` || '') : '',
taillabel = edge ? (`taillabel="${(edge.probability : any)}";` || '') : '',

labelInline = [
[edge, 'name', 'label'],
[pair, 'probability', 'headlabel'],
[edge, 'probability', 'taillabel']
]
.map( r => ({ which: r[2], whether: if_obj_field(r[0], r[1]) }) )
.filter( present => present.whether )
.map( r => `${r.which}="${(r.whether : any)}"`)
.join(' '),

edgeInline = edge ? (double? 'dir=both;color="#777777:#555555"' : 'color="#555555"') : '';

if (pair) { strike.push([ex, s]); }

return `${node_of(s)}->${node_of(ex)} [${labelInline}${edgeInline}];`;

}).join(' ')

).join(' ');

return `digraph G {\n fontname="helvetica neue";\n style=filled;\n bgcolor=lightgrey;\n node [shape=box; style=filled; fillcolor=white; fontname="helvetica neue"];\n edge [len=2; fontname="helvetica neue"];\n\n ${nodes}\n\n ${edges}\n}`;
return `digraph G {\n fontname="helvetica neue";\n style=filled;\n bgcolor=lightgrey;\n node [shape=box; style=filled; fillcolor=white; fontname="helvetica neue"];\n edge [fontsize=9;fontname="helvetica neue"];\n\n ${nodes}\n\n ${edges}\n}`;

}

Expand Down

0 comments on commit 88bd317

Please sign in to comment.