Code Monkey home page Code Monkey logo

finity's Introduction

finity

npm version Build Status Coverage Status GitHub license Greenkeeper badge

A finite state machine library for Node.js and the browser with a friendly configuration DSL.

Features

  • Event-based, time-based, and Promise-based triggers
  • Entry, exit, and transition actions
  • Guard conditions
  • Self-transitions and internal transitions
  • Hierarchical state machines
  • State machine event hooks
  • Fluent configuration API
  • No external dependencies
  • 3.7 kB minified and gzipped
  • TypeScript typings

Installation

Install finity using npm:

npm install --save finity

Then you can import it using ES2015 or CommonJS modules:

// ES2015
import Finity from 'finity';

// CommonJS
const Finity = require('finity');

The UMD build is available on unpkg:

<script src="https://unpkg.com/finity/umd/Finity.min.js"></script>

Example

const worker = Finity
  .configure()
    .initialState('ready')
      .on('task_submitted').transitionTo('running')
    .state('running')
      .do((state, context) => processTaskAsync(context.eventPayload))
        .onSuccess().transitionTo('succeeded')
        .onFailure().transitionTo('failed')
      .onTimeout(1000)
        .transitionTo('timed_out')
    .global()
      .onStateEnter(state => console.log(`Entering state '${state}'`))
  .start();

worker.handle('task_submitted', task);

More examples

Usage

Configuration

Before you can create and start a state machine, you need to create a state machine configuration using Finity configuration DSL. The entry point to the DSL is the Finity.configure method.

States

To define the initial state, use the initialState method. To add other states, use the state method. Both methods take the state name as a parameter.

Finity
  .configure()
    .initialState('state1')
    .state('state2')

A state machine must have one and only one initial state.

Transitions

A transition from one state to another can be triggered by an event-based, time-based, or Promise-based trigger.

Event-based triggers

To add a transition that is triggered by a specific event, first call the on method, passing in the name of the event. Then call the transitionTo method, passing in the name of the target state.

Finity
  .configure()
    .initialState('state1')
      .on('eventA').transitionTo('state2')
      .on('eventB').transitionTo('state3')
    .state('state2')
      .on('eventC').transitionTo('state1')

To add a catch-all transition that is triggered by any event, use the onAny method. (Event-specific transitions take precedence over catch-all transitions.)

Finity
  .configure()
    .initialState('state1')
        // Perform a transition to state2 when eventA occurs
      .on('eventA').transitionTo('state2')
      // Perform a transition to state3 when any other event occurs
      .onAny().transitionTo('state3')  
Time-based triggers

To add a transition that is triggered when a specific amount of time has passed since entering the state, use the onTimeout method which accepts the amount of time in milliseconds as a parameter.

// Perform a transition from state1 to state2 when 100 milliseconds have passed
// since entering state1
Finity
  .configure()
    .initialState('state1')
      .onTimeout(100).transitionTo('state2')

// If eventA occurs before 100 milliseconds have passed, perform a transition to
// state2; otherwise, perform a transition to state3
Finity
  .configure()
    .initialState('state1')
      .on('eventA').transitionTo('state2')
      .onTimeout(100).transitionTo('state3')
Promise-based triggers

A transition can be triggered by the completion of an async operation.

Finity
  .configure()
    .initialState('state1')
      .do(asyncOperation) // asyncOperation is a function that returns a Promise
        .onSuccess().transitionTo('state2')
        .onFailure().transitionTo('state3')

The result or error can be accessed through the context object.

Finity
  .configure()
    .initialState('state1')
      // httpClient.get makes an HTTP request and returns a Promise that will
      // resolve with the response if the request succeeds or reject if the
      // request fails
      .do(() => httpClient.get('https://api.github.com/users/nickuraltsev'))
        .onSuccess().transitionTo('state2').withAction((from, to, context) =>
          console.log('Response: ', context.result)
        )
        .onFailure().transitionTo('state3').withAction((from, to, context) =>
          console.log('Error: ', context.error)
        )

Entry and exit actions

A state can have entry and exit actions associated with it, which are functions that are called when the state is about to be entered or exited, respectively.

Entry and exit actions receive two parameters:

  • state - The name of the state to be entered or exited.
  • context - The current context.

Use the onEnter method to add an entry action to a state, and the onExit method to add an exit action.

Finity
  .configure()
    .initialState('ready')
      .on('task_submitted').transitionTo('running')
    .state('running')
      .onEnter(() => console.log('Processing task...'))
      .onExit(() => console.log('All done!'))

You can add multiple entry actions to a state. They will be executed in the same order as they have been added. The same is true for exit actions.

Transition actions

Transitions can have actions associated with them. Transition actions are functions that are called when the transition is executed.

A transition action receives three parameters:

  • fromState - The name of the transition's source state.
  • toState - The name of the transition's target state.
  • context - The current context.

To add an action to a transition, use the withAction method.

Finity
  .configure()
    .initialState('state1')
      .on('eventA')
        .transitionTo('state2')
          .withAction(() => console.log('Transitioning to next state'))

You can add multiple actions to a transition. They will be executed in the same order as they have been added.

Guard conditions

A transition can have a guard condition attached, which is a function that is used to determine if the transition is allowed. A transition with a guard condition can be executed only if the guard condition returns a truthy value.

A guard condition function receives the current context as a parameter.

To set a guard condition for a transition, use the withCondition method.

Finity
  .configure()
    .initialState('state1')
      .on('eventA')
        // Perform a transition to state2 if `fn` returns a truthy value
        .transitionTo('state2').withCondition(fn)
        // Otherwise, perform a transition to state3
        .transitionTo('state3')

A transition cannot have more than one guard condition.

Self-transitions and internal transitions

Self-transitions and internal transitions are transitions from a state to itself.

When a self-transition is executed, the state is exited and re-entered, and thus the entry and exit actions are executed. In contrast, an internal transition does not cause exit and reentry to the state.

To add a self-transition to a state, use the selfTransition method. To add an internal transition, call the internalTransition method.

Finity
  .configure()
    .initialState('state1')
      .on('eventA').selfTransition()
      .on('eventB').internalTransition()

Self-transitions and internal transitions can have actions and guard conditions attached.

Ignoring events

To ignore an event, use the ignore method.

Finity
  .configure()
    .initialState('state1')
      .on('eventA').ignore()

Events can be conditionally ignored using the withCondition method.

Finity
  .configure()
    .initialState('state1')
      .on('eventA')
        // If `fn1` returns a truthy value, perform a transition to state2
        .transitionTo('state2').withCondition(fn1)
        // Else if `fn2` returns a truthy value, ignore the event
        .ignore().withCondition(fn2)
        // Otherwise, perform a transition to state3
        .transitionTo('state3')

Global hooks

Global hooks are functions that are called on certain state machine events, such as state entry, state exit, and state transition.

Global hooks are similar to entry, exit, and transition actions. The main difference is that global hooks are called for any state or transition, while actions are called only for the state or transition that they are attached to.

onStateEnter

Called when the state machine is about to enter a state.

Parameters
  • state - The name of the state to be entered.
  • context - The current context.
Finity
  .configure()
    .global()
      .onStateEnter(state => console.log(`Entering state '${state}'`))
onStateExit

Called when the state machine is about to exit a state.

Parameters
  • state - The name of the state to be exited.
  • context - The current context.
Finity
  .configure()
    .global()
      .onStateExit(state => console.log(`Exiting state '${state}'`))
onTransition

Called when the state machine is executing a transition.

Parameters
  • fromState - The name of the transition's source state.
  • toState - The name of the transition's target state.
  • context - The current context.
Finity
  .configure()
    .global()
      .onTransition((fromState, toState) =>
        console.log(`Transitioning from '${fromState}' to '${toState}'`)
      )
onStateChange

Called when the state of the state machine is about to change.

In contrast to onTransition hooks, onStateChange hooks are not called when executing a self-transition or internal transition as these types of transitions do not cause a state change.

Parameters
  • oldState - The name of the old state.
  • newState - The name of the new state.
  • context - The current context.
Finity
  .configure()
    .global()
      .onStateChange((oldState, newState) =>
        console.log(`Changing state from '${oldState}' to '${newState}'`)
      )

You can register multiple global hooks of the same type. They will be called in the same order as they have been registered.

Finity
  .configure()
    .initialState('state1')
    .global()
      .onStateEnter(state => console.log(`Entering state '${state}'`))
      .onStateEnter(state => console.log('We are almost there!'))

Unhandled events

By default, if a state machine receives an event that it cannot handle, it will throw an error. You can override this behavior by registering an onUnhandledEvent hook.

An onUnhandledEvent hook receives three parameters:

  • event - The name of the event.
  • state - The name of the state.
  • context - The current context.
Finity
  .configure()
    .initialState('state1')
    .global()
      .onUnhandledEvent((event, state) =>
        console.log(`Unhandled event '${event}' in state '${state}'.`)
      )

You can register multiple onUnhandledEvent hooks. They will be executed in the same order as they have been registered.

Creating and starting a state machine

Once you have created a configuration, you can create and start a state machine. There are two ways to do this. The easiest way is to call the start method of the configuration API.

const stateMachine = Finity
  .configure()
    .initialState('state1')
  .start();

If you don't want to start a state machine right away or you need to create multiple instances of a state machine with the same configuration, you can call the getConfig method to get the configuration first. Then you can create and start new state machine instances by passing the configuration to the Finity.start method.

const config = Finity
  .configure()
    .initialState('state1')
  .getConfig();

const firstInstance = Finity.start(config);
const secondInstance = Finity.start(config);

When a state machine is started, it enters the initial state and all the onStateEnter hooks and the initial state's entry actions are executed. However, the onTransition and onStateChange hooks are not executed.

Sending an event to a state machine

To send an event to a state machine, pass the event name as the first parameter to the handle method of the state machine object.

const stateMachine = Finity
  .configure()
    .initialState('state1')
      .on('eventA').transitionTo('state2')
  .start();

// This will trigger a transition from state1 to state2.
stateMachine.handle('eventA');

You can send an event with a payload by passing the payload as the optional second parameter. The event payload can be accessed in entry, exit, and transition actions, guard conditions, async operations, and global hooks through the context object.

const stateMachine = Finity
  .configure()
    .initialState('state1')
      .on('eventA').transitionTo('state2').withAction((fromState, toState, context) => {
        console.log('Payload:', context.eventPayload);
      })
  .start();

stateMachine.handle('eventA', { foo: 'bar' }); // Note: A payload can be of any type.

If a state machine cannot handle the specified event, it will throw an error or execute the onUnhandledEvent hooks if any are registered (see Unhandled events).

Checking if a state machine can handle an event

You can check if a state machine, in its current state, can handle a given event via the canHandle method. Like the handle method, it takes an event name and an optional payload. If the specified event can be handled, the canHandle method returns true; otherwise, it returns false.

const stateMachine = Finity
  .configure()
    .initialState('state1')
      .on('eventA').transitionTo('state2')
  .start();

console.log(stateMachine.canHandle('eventA')); // true
console.log(stateMachine.canHandle('eventB')); // false

Getting the current state of a state machine

To get the current state of a state machine, call the getCurrentState method on the state machine object.

const stateMachine = Finity
  .configure()
    .initialState('state1')
  .start();

console.log(stateMachine.getCurrentState()); // state1

Context

A context object is passed to all entry, exit, and transition actions, guard conditions, async operations, and global hooks.

Properties

  • stateMachine - The current state machine instance.
  • event - The name of the event. This property is only present when the state machine is handling an event.
  • eventPayload - The payload of the event. This property is only present when the state machine is handling an event that has a payload.
  • result - The async operation result.
  • error - The async operation error.

Hierarchical state machines

const submachineConfig = Finity
  .configure()
    .initialState('s21')
      .on('eventB').transitionTo('s22')
    .global()
      .onStateEnter(substate => console.log(`  - Entering substate '${substate}'`))
      .onStateExit(substate => console.log(`  - Exiting substate '${substate}'`))
  .getConfig();

const stateMachine = Finity
  .configure()
    .initialState('s1')
      .on('eventA').transitionTo('s2')
    .state('s2')
      .submachine(submachineConfig) // s2 is a submachine state
      .on('eventC').transitionTo('s3')
    .global()
      .onStateEnter(state => console.log(`- Entering state '${state}'`))
      .onStateExit(state => console.log(`- Exiting state '${state}'`))
  .start();

stateMachine.handle('eventA');

stateMachine.handle('eventB');

stateMachine.handle('eventC');

The above code will generate the following output:

- Entering state 's1'
- Exiting state 's1'
- Entering state 's2'
  - Entering substate 's21'
  - Exiting substate 's21'
  - Entering substate 's22'
  - Exiting substate 's22'
- Exiting state 's2'
- Entering state 's3'

TypeScript Support

Finity includes TypeScript typings.

Contributing

Bug reports and pull requests are welcome!

By participating in this project you agree to abide by the code of conduct.

License

MIT

finity's People

Contributors

aoberoi avatar greenkeeper[bot] avatar nickuraltsev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

finity's Issues

Getting config doesn't work

I'm trying to execute code from the example:

const config = Finity
  .configure()
    .initialState('state1')
  .getConfig();

const firstInstance = Finity.start(config);
const secondInstance = Finity.start(config);

The result is an error:

Finity.min.js:1 Uncaught Error: Initial state must be specified.

Handling events that are not configured

When i try to handle events that are not configured, that is canHandle('e) returns false, It would be great if we could specify an "else" event which gets fired.
It could be used something like this:

var fsm = finity.configure()
.initialState('start')
.on('a').transitionTo('Pass')
.on('_').transitionTo('Fail');

I think this would be really good feature to have.

Something goes wrong in Node 0.12.14

Not sure what's going on, but with node 0.12.14 this blows up:

My configuration:

        this._queueStates = StateMachine

            .configure()

                .initialState('idle')

                    .onEnter(() => {
                        if (this._queue.length > 0) this._queueStates.handle('enqueue');
                    })

                    .on('enqueue').transitionTo('process')

                .state('process')

                    .onEnter(() => {
                        this._processNextQueuedJob()
                    })

                    .on('idle').transitionTo('idle')

                .global()
                    .onStateEnter((state: string) => this._log.info(`QUEUE: Entering state ${state}`))
                    .onUnhandledEvent((event: string, state: string) => this._log.info(`QUEUE: Unhandled event '${event}' in state '${state}.'`))

            .start();

Then I enqueue something and tell the SM to "enqueue":

        this._queue.push(qj);
        this._queueStates.handle('enqueue');

and BOOM!

TypeError: undefined is not a function
    at StateMachine.selectTransition (XYZ\node_modules\finity\lib\core\StateMachine.js:304:26)
    at StateMachine.getTransitionForEvent (XYZ\node_modules\finity\lib\core\StateMachine.js:149:61)
    at StateMachine.processEvent (XYZ\node_modules\finity\lib\core\StateMachine.js:121:35)
    at XYZ\node_modules\finity\lib\core\StateMachine.js:83:25
    at StateMachine.execute (XYZ\node_modules\finity\lib\core\StateMachine.js:98:9)
    at StateMachine.handle (XYZ\node_modules\finity\lib\core\StateMachine.js:82:14)
    at AdobeMediaEncoder.enqueueJob (XYZ\src\ame.ts:708:27)
    at XYZ\testing\test-ame.js:54:26
    at _fulfilled (XYZ\node_modules\q\q.js:834:54)
    at self.promiseDispatch.done (XYZ\node_modules\q\q.js:863:30)

Works fine in Node 4.4.5

(Just switching versions using nodist)

Apprently this is within selectTransition() where the "transitions" object is JSON.stringify(transitions) -> "[{"targetState":"process","isInternal":false,"actions":[],"condition":null}]" which does not have any find() method so this blows up trying to call "undefined";

return transitions.find(function (t) {
        return !t.condition || t.condition(context);
      });

(And why am I using 0.12.14? Well.. long story)

Expose stop() method

I'm using finity with react and I need to stop the FSM when a component will be unmounted.

I figured a few ways to call the internal stop() method but they seem a bit hacky. Could we expose the stop() method, the same way start() is available?

Or do I need a custom "stop" event and handle it that way?

Feature Request: add HierarchicalStateMachine type to TypeScript definitions

hello there 👋

first of all, i just wanted to express my gratitude for this project. i think the API is great, the code is readable/understandable, and the functionality is complete and robust.

i'm using this package in a TypeScript project. I need to do something like the following, but TypeScript is giving me the error shown:

if (stateMachine.getCurrentState() === 'some_state' &&
    stateMachine.getStateHeirarchy !== undefined && stateMachine.getStateHeirarchy()[1] === 'some_substate') {
  // do stuff
}

TypeScript error:

[ts] Property 'getStateHierarchy' does not exist on type 'StateMachine<string, string>'.

the simplest fix i could think of would just be to export the interface for HierarchicalStateMachine so that after the stateMachine.getStateHeirarchy !== undefined condition, i could cast my stateMachine to that type specifically. i don't know if there's a more advanced technique for expressing true polymorphism in TypeScript.

edit: on closer inspection, it looks like the StateMachineConfigurator.start() method always returns a HierarchicalStateMachine, so maybe its just a matter of replacing the StateMachine type with the former in the definitions.

thanks!

return the state machine in graphviz dot language

Hello,
I desperately needed a graphical representation of the state machine I have configured. So I wrote this function to return a string representing the configured state machine in graphviz dot language. Feel free to extend and include in your code.
Ognian

An in-range update of webpack is breaking the build 🚨

The devDependency webpack was updated from 4.26.1 to 4.27.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

webpack is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

Release Notes for v4.27.0

Features

  • When using functions as plugins they are now also called with the compiler as parameter
    • This make it possible to use arrow functions as plugins
  • splitChunks.maxSize now emits a warning when minSize > maxSize
  • Loaders have now access to a getResolve method to create their own resolver function with custom options

Bugfixes

  • splitChunks.cacheGroups.xxx.enforce now behaves as documented and enforce chunk creation
  • splitChunks.cacheGroups.xxx.enforce now no longer deletes minSize for maxSize
  • fixes a bug where splitChunks cause cacheGroups to be incorrectly merged when using the same name
    • now conditions are considered per cacheGroup
    • the correct cache group comment is displayed in stats
  • fixes a bug which causes providedExports not to be updated on rebuilds when using export * from
Commits

The new version differs by 12 commits.

  • f47bf8b 4.27.0
  • a67ffcd Merge pull request #8452 from webpack/feature/resolveWithOptions
  • 96f625c Merge pull request #8457 from webpack/bugfix/rebuild-provided-exports
  • 56feccc convert test case to normal function for node.js 6 support
  • 2f4296e fix a bug which causes incorrect providedExports for cached modules
  • f944002 Merge pull request #8451 from webpack/bugfix/split-chunks
  • 162da1c add getResolve method to loader context
  • 3b46b48 enforce doesn't affect minSize for maxSize
  • 72a8a1f Merge pull request #8440 from Connormiha/oprimize-chunk-can-be-integrated
  • 537d3e4 Cache hasRunstime in chunk
  • e3e8a68 Merge pull request #8405 from xiaoxiaojx/fix-function-plugin-apply
  • 70b9a1b fix parameter missing when plugin type is a funtion

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of cross-env is breaking the build 🚨

Version 3.2.0 of cross-env just got published.

Branch Build failing 🚨
Dependency cross-env
Current Version 3.1.4
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

As cross-env is “only” a devDependency of this project it might not break production or downstream projects, but “only” your build or test tools – preventing new deploys or publishes.

I recommend you give this issue a high priority. I’m sure you can resolve this 💪


Status Details
  • continuous-integration/travis-ci/push The Travis CI build could not complete due to an error Details
Release Notes v3.2.0

<a name"3.2.0">

3.2.0 (2017-03-04)

Features

  • revamp: revamp the entire lib (backward compatible) (#63) (dad00c46)
Commits

The new version differs by 4 commits .

  • dad00c4 feat(revamp): revamp the entire lib (backward compatible) (#63)
  • e33a85c docs(README): Add doc for cross-var. (#58)
  • 5e590ec docs(README): added how to use cross-env to run npm sub-scripts (#53)
  • afdb2de docs(README): mention Bash on Windows (#49)

See the full diff.

Not sure how things should work exactly?

There is a collection of frequently asked questions and of course you may always ask my humans.


Your Greenkeeper Bot 🌴

Can I pass data to leave througth out all states?

const worker = Finity
  .configure()
    .initialState('ready')
      .on('task_submitted').transitionTo('running')
    .state('running')
    // here I have the context.eventPayload
    // but if I want to inside the worker gerate a data to use in another state?
      .do((state, context) => processTaskAsync(context.eventPayload))
        .onSuccess().transitionTo('succeeded')
        .onFailure().transitionTo('failed')
      .onTimeout(1000)
        .transitionTo('timed_out')
    .global()
      .onStateEnter(state => console.log(`Entering state '${state}'`))
  .start();

An in-range update of babel-eslint is breaking the build 🚨

Version 8.2.0 of babel-eslint was just published.

Branch Build failing 🚨
Dependency babel-eslint
Current Version 8.1.2
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

babel-eslint is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push The Travis CI build failed Details

Commits

The new version differs by 4 commits.

  • ef27670 8.2.0
  • eba5920 Add other parser plugins, update yarn.lock (#569)
  • e201fb4 Make 2018 the default ecmaVersion for rules relying on parserOptions (#556)
  • 1dedd1b update babel packages (#565)

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Determine which instance is handling the event

Hi, thanks for the great module.

I have a lot of workers and when an event happens I would like to know which worker trigger this event.

let config = Finity.configure()
  .initialState('stop')
  .global()
  .onStateEnter((state, context) =>
    console.log(`Entering state '${state}'`)
  // which worker is handling this event?
  )
  .getConfig()

let worker0 = Finity.start(config)
let worker1 = Finity.start(config)


Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.