Git Product home page Git Product logo

bpmn's Introduction

bpmn

This module executes BPMN 2.0 processes.

BPMN execution is deemed to be a good way to describe process oriented business logic. This is especially true if we have to describe the orchestration and collaboration of service- and UI-interactions. Many of these interactions are asynchronous and event driven making Node.js an ideal candidate for implementing a BPMN engine. To draw the BPMN file to be executed each BPMN 2.0 compliant tool can be used.

The e2e-transaction-logger package can be used optionally. The generated transaction log files enable the E2E Dashboards to provide graphical views of your processes. You may learn more about our efforts and other Node.js packages on http://e2ebridge.com.

Installation

The easiest way to install it is via NPM:

npm install bpmn

If you don't have a compiler on your platform the restify dtrace dependency won't be installed. Just ignore it.

Assumptions

  • This package assumes each BPMN 2.0 file is accompanied by an equal named JS file. For example, the directory containing myprocess.bpmn must contain also myprocess.js holding the BPMN event handlers.
  • Each BPMN element name is unique per process. This simplifies live considerably because we can use names instead of IDs simplifying the world for users and developers alike. If this is not the case, an error is thrown while creating the process.

Remarks

Process can be managed or unmanaged. Unmanaged processes are not stored in any way, the developer is responsible of storing the returned process objects to be able to use them later. Process manager allow to create multiple processes and store them during their execution. The managers have functions to retrieve existing processes by id, filter by property or state. Managers will also persist the managed processes if persistency options are set.

This is a very rough work in progress and we're not providing any official support.

Basic Example

These following samples assume that you installed bpmn via npm.

Assume myProcess.bpmn describes the following process

then this process can be created by

var bpmn = require("bpmn");
// We assume there is a myProcess.js besides myProcess.bpmn that contains the handlers
bpmn.createUnmanagedProcess("path/to/myProcess.bpmn", function(err, myProcess){

    // we start the process
    myProcess.triggerEvent("MyStart");

});

The handler file looks like:

exports.MyStart = function(data, done) {
	// called after the start event arrived at MyStart
	done(data);
};

exports.MyTask = function(data, done) {
	// called at the beginning of MyTask
	done(data);
};

exports.MyTaskDone = function(data, done) {
	// Called after the process has been notified that the task has been finished
	// by invoking myProcess.taskDone("MyTask").
	// Note: <task name> + "Done" handler are only called for 
	// user tasks, manual task, and unspecified tasks
	done(data);
};

exports.MyEnd = function(data, done) {
	// Called after MyEnd has been reached
	done(data);
};

Processes can also be created from an xml string instead of file. In this case the handler can be an object or a javascript string that would be parsed.

bpmn.createUnmanagedProcessFromXML("<definitions ... </definitions>", "exports.MyStart = ...", function(err, myProcess){

    // we start the process
    myProcess.triggerEvent("MyStart");

});

If no handler is defined, the default handler is being called. This handler can also be specified in the handler file by:

/**
 * @param {String} eventType Possible types are: "activityFinishedEvent", "callHandler"
 * @param {String?} currentFlowObjectName The current activity or event
 * @param {String} handlerName
 * @param {String} reason Possible reasons:
 * 							- no handler given
 *							- process is not in a state to handle the incoming event
 *							- the event is not defined in the process
 *							- the current state cannot be left because there are no outgoing flows
 */	
exports.defaultEventHandler = function(eventType, currentFlowObjectName, handlerName, reason, done) {
	// Called, if no handler could be invoked. 
	done(data);
};

If the default handler is not specified the default default event handler is being called which just logs a message to stdout.

Besides the default event handler, it is also possible to specify a default error handler:

exports.defaultErrorHandler = function(error, done) {
	// Called if errors are thrown in the event handlers
	done();
};

Sometimes it is useful to call handlers before or after each activity, task, or catch event. To do this specify

exports.onBeginHandler = function(currentFlowObjectName, data, done) {
    // do something
	done(data);
};
exports.onEndHandler = function(currentFlowObjectName, data, done) {
    // do something
	done(data);
};

Handler Context (this)

Each handler is called in the context of the current process. More formally: this is bound to BPMNProcessClient. This object offers the following interface to the current process instance:

  • taskDone(taskName, data): notify the process that a task has been done. This triggers calling the event handler: taskName + "Done"
  • triggerEvent(eventName, data): send an event to the process
  • getState(): get the state of the current process. The state object is BPMNProcessState.
  • getHistory(): get the history of the current process. Basically a list of all visited activities and events encapsulated in BPMNProcessHistory
  • setProperty(name, value): set a process property. This property is also persisted together with the process. The value is a valid JS data object. That is, we do not persist functions.
  • getProperty(name): get property.
  • getParentProcess(): if this process has been called by a callActivity activity, this call returns a BPMNProcessClient instance of the calling process. Otherwise it returns null.
  • getParticipantByName(participantName): if this process collaborates with other processes (see section Collaboration Processes), this call returns a BPMNProcessClient instance of a participating process instance having the name participantName. This allows to send for example an event to a participating process by this.getParticipantName("Another process").triggerEvent("my event");

Handler Names

The handler names are derived by replacing all not allowed JS characters by '_'. For example, "My decision?" becomes My_decision_. The bpmn module exports mapName2HandlerName(bpmnName) that can be invoked to get the handler name for a given BPMN name.

Exclusive Gateways (Decisions)

If the following process has to be implemented, we have to provide three handlers for the exclusive gateway:

exports.Is_it_ok_ = function(data, done) {
	// called after arriving at "Is it ok?"
	done(data);
};

exports.Is_it_ok_$ok = function(data) {
	// has to return true or false
	// the name of the sequence flow follows after "$".
	// if there is no name, an error is thrown 
	return true;
};

exports.Is_it_ok_$nok = function(data) {
	// has to return true or false
	// the name of the sequence flow follows after "$".
	// if there is no name, an error is thrown 
	return false;
};

Note: For each outgoing transition we have a condition handler that hast to evaluate synchronously. So if backend data are required, fetch them in the gateway callback. Furthermore, BPMN does not specify the order of evaluating the flow conditions, so the implementer has to make sure, that only one operation returns true. Additionally, we ignore the condition expression. We consider this as part of the implementation.

Timer Events

Boundary Timer Events

Boundary timer events are timeouts on the activity they are attached to. To implement timeouts use two handlers:

exports.MyTimeout$getTimeout = function(data, done) {
	// called when arriving on "MyTask"
	// should return timeout in ms.
	return 1000;
};

exports.MyTimeout = function(data, done) {
	// called if the timeout triggers
	done(data);
};

Intermediate Timer Events

Intermediate catch timer events are used to stop the process for a given time. If the timer event occurs, the process proceeds. The implementation is very similar to boundary timer events:

exports.MyTimeout$getTimeout = function(data, done) {
	// called when arriving on "Intermediate Catch Timer Event"
	// should return wait time in ms.
	return 10000;
};

exports.Intermediate_Catch_Timer_Event = function(data, done) {
	// called if the timeout triggers
	done(data);
};

Collaborations

BPMN also supports collaborating processes as depicted below.

These processes must be created together:

// create collaborating processes
bpmn.createUnmanagedCollaboratingProcesses("my/collaboration/example.bpmn", function(err, collaboratingProcesses){

    // start the second process
    var secondProcess = collaboratingProcesses[1];
    secondProcess.triggerEvent("Start Event 2");

});

The collaboration of the processes is then implemented in the handlers. For example, it is possible to get a partner process by name and then send an event to this process. This is frequently done to start the partner process:

exports.Task_2 = function(data, done) {
	// after arriving ot "Task 2" we start process 1
	var partnerProcess = this.getParticipantByName("My First Process");
	partnerProcess.triggerEvent("Start Event 1");
	done(data);
};

However, another option is to get all outgoing message flows and send a message along these flows. In the current example we have exactly one flow, so sending the message is done by:

exports.End_Event_1 = function(data, done) {
	// after reaching the end of process 1, we send a message
	var messageFlows = this.getOutgoingMessageFlows("End Event 1");
	this.sendMessage(messageFlows[0], {gugus: "blah"});
	done(data);
};

Collaborating processes can also be created from strings using createUnmanagedCollaboratingProcessesFromXML(bpmnXML, handler, callback).

Note: all task and event names must be unique

Logging

By default, only errors are logged. However, it is easy to change the log level:

var logLevels = require('bpmn').logLevels;

myProcess.setLogLevel(logLevels.debug);

It is also possible to use log level strings instead of the log level enumeration:

myProcess.setLogLevel("debug");

Or within a handler:

this.setLogLevel("trace");

By default, logs are written to the console and ./process.log. Of course, this can be changed. For details see the section Log Transports.

The supported log levels are:

  • none: switches logging off
  • error (default): Errors, error handler calls, and default event handler calls are logged
  • trace: process actions are logged: sendMessage, triggerEvent, callHandler, callHandlerDone, taskDone, catchBoundaryEvent
  • debug: internal process actions are logged, such as putTokenAt, tokenArrivedAt, doneSaving, etc.
  • silly, verbose, info, warn: these levels are reserved for further use but not yet implemented:

Log Transports

We use winston as log library. This allows as to define different ways of storing our logs by defining so called winston transports (for details see here). The default transports used by this library are

transports: [
        new (winston.transports.Console)({
            colorize: true
        }),
        new (winston.transports.File)({
            level: 'verbose',
            filename: './process.log',
            maxsize: 64 * 1024 * 1024,
            maxFiles: 100,
            timestamp: function() {
                return Date.now();
            }
        })
    ]

However, these transports can be overridden or completely new transports can be added. For example, the following code snippet adds a file transport used for errors, max size of one megabyte, and not writing timestamps:

var winston = require('winston'); 
myProcess.addLogTransport(winston.transports.File,
    {
        level: 'error',
        filename: "my/log/file.log",
        maxsize: 1024 * 1024,
        timestamp: false
    }
);

Note: the directory containing the log file must exist, otherwise an error is thrown.

Of course, transports can be removed as well, e.g.:

bpmnProcess.removeLogTransport(winston.transports.File);

Managing processes

Process managers are used to create multiple processes using the same definitions and find them back later.

var manager = new bpmn.ProcessManager();

manager.addBpmnFilePath("path/to/myProcess.bpmn");

manager.createProcess("myId", function(err, myProcess){

    // we start the process
    myProcess.triggerEvent("MyStart");

});

If the process id is already used an error is returned.

If the manager have multiple bpmn definitions a descriptor object must be passed to the create function.

manager.createProcess({id: "myId", name: "MyProcess"}, function(err, myProcess){

    // we start the process
    myProcess.triggerEvent("MyStart");

});

To create collaborating processes and array of descriptors must be passed to the create function.

var processDescriptors = [
    {name: "My First Process", id: "myFirstProcessId_1"},
    {name: "My Second Process", id: "mySecondProcessId_1"}
];
manager.createProcess(processDescriptors, function(err, collaboratingProcesses){

    var secondProcess = collaboratingProcesses[1];
    secondProcess.triggerEvent("Start Event 2");

});

Process definitions and handlers can be add in the manager creator or using add* functions.

Creator options:

  • handlerFilePath: Object with name and filePath. Can be an array of theses object to define multiple handlers.

    • name: The name of the process definition for which the handler will be used.
    • filePath: Path to the javascript file defining the handler module.
  • handlerString: Object with name and string. Can be an array of theses object to define multiple handlers.

    • name: The name of the process definition for which the handler will be used.
    • string: The javascript string defining the handler module.
  • handler: Object with name and module. Can be an array of theses object to define multiple handlers.

    • name: The name of the process definition for which the handler will be used.
    • module: The javascript object defining the handler module.
  • bpmnFilePath: Path to the bpmn file. Can be an array to define multiple definitions. will try to load the handler from the corresponding javascript file.

  • bpmnXML: Object with name and xml. Can be an array of theses object to define multiple definitions.

    • name: The name of the process definition.
    • xml: The xml string definition.

Note: If no handler is found for a process definition an error is thrown.

var manager = new bpmn.ProcessManager({
    bpmnFilePath: "path/to/myProcess.bpmn"
});

ProcessManager.addHandlerFilePath(name, handlerFilePath)

  • name: The name of the process definition for which the handler will be used.
  • handlerFilePath: Path to the javascript file defining the handler module.

ProcessManager.addHandlerString = function(name, handlerString)

  • name: The name of the process definition for which the handler will be used.
  • handlerString: The javascript string defining the handler module.

ProcessManager.addHandler = function(name, handler)

  • name: The name of the process definition for which the handler will be used.
  • handlerString: The javascript object defining the handler module.

ProcessManager.addBpmnFilePath = function(bpmnFilePath, processHandler)

  • bpmnFilePath: Path to the bpmn file.
  • processHandler: Optional. The javascript object defining the handler module or the path to the javascript file defining the handler module.

If no processHandler is passed we try to load the corresponding javascript file to the bpmn file. If no handler is found or defined before for this definition an error is thrown.

ProcessManager.addBpmnXML = function(bpmnXml, processName, processHandler)

  • bpmnXml: The xml string definition.
  • processName: The name of the process definition.
  • processHandler: Optional. The javascript object defining the handler module or the javascript string defining the handler module.

If no processHandler is passed and no handler was defined before for this definition an error is thrown.

Finding processes

Existing processes in a manager can be retrived using these functions:

// returns the process with the corresponding id
bpmn.get(processId, function(err, process){
    ...
});

// returns all processes
bpmn.getAllProcesses(function(err, processes){
    ...
});

// returns all processes having the property names
bpmn.findByProperty({propName1: propValue1, propName2: propValue2, ...}, function(err, processes){
    ...
});

// returns all processes in this state (callActivity, tasks, event, ...)
bpmn.findByState(stateName, function(err, processes){
    ...
});

// returns all processes using this definition
bpmn.findByName(definitionName, function(err, processes){
    ...
});

Persistency

The manager constructor also takes persistency options. The engine will save the state while waiting for a task being done. After a manager is created with persistency options all stored processes can be retrived using the functions above. The process can be persisted to the file system or to a MongoDB. We recommend the latter approach for productive use cases.

// using files
var manager = new bpmn.ProcessManager({
    persistencyOptions: {
        uri: "path/to/folder"
    }
});


// using mongodb
var manager = new bpmn.ProcessManager({
    persistencyOptions: {
        uri: "mongodb://host:port/db_name"
    }
});

Main module is an instance of ProcessManager

The main bpmn module is an instance of ProcessManager without options meaning you can use manager functions directly from it.

var bpmn = require('bpmn');

bpmn.addBpmnFilePath("path/to/myProcess.bpmn");

bpmn.createProcess("myId", function(err, myProcess){

    // we start the process
    myProcess.triggerEvent("MyStart");

});

REST

Server

The above API can also be called by REST HTTP calls. To do this, you have first to instantiate a server from ta manager. For example:

// Returns a restify server.
var server = manager.createServer();
server.listen(9009, function() {
	console.log('%s listening at %s', server.name, server.url);
});

The server is a node restify server. So all features of this package can be used. The full signature of createProcess is

var server = manager.createServer(options, restifyOptions);

The parameters are:

  • options: optional object having the following optional properties
    • createProcessId: Function that returns a UUID. Default: node-uuid.v1()
    • logLevel: used log level. Default: Error. Use logger.logLevels to set.
  • restifyOptions: these options are given to the restify.createServer call. If not given, the log property is set to the internal winston logger and the name property is set to 'bpmnRESTServer'.

Client

The following sections describe how a client would use the REST API provided by the server above. The API calls are illustrated using the restify client library.

Creating a process

To create a process send a POST request:

// This example used the node-restify client
var client = restify.createJsonClient({url: "http://localhost:9009"});

client.post('/TaskExampleProcess', function(err, req, res, obj) { ... });

When receiving this request the server will use the urlMap to find the BPMN file associated with the process name in the URL, instantiate this process and return the process state in the response body as a JSON object:

{
	"id": "3c5e28f0-cec1-11e2-b076-31b0fecf7b6f",
	"name": "TaskExampleProcess",
	"link": {
	    "rel": "self",
	    "href": "/TaskExampleProcess/3c5e28f0-cec1-11e2-b076-31b0fecf7b6f"
	},
	"state": [],
	"history": [],
	"properties": {}
}

The process has now been created but not yet started! Thus, state, history, and properties are empty. To do this, you have either to send a start event using a PUT request (see below) or you can create and start the process in one go by appending the start event to the process URI:

   var message = {
		"gugus": "blah", // a process property ...
		"sugus": "foo", // and another one.
    };

    client.post('/TaskExampleProcess/MyStart', message, function(err, req, res, obj) { ... });

If the MyStart event handler sets a process property such as

exports.MyStart = function(data, done) {
	this.setProperty("myFirstProperty", data);
	done(data);
};

The result of above POST request may look like:

{
    "id": "3c5e28f0-cec1-11e2-b076-31b0fecf7b6f",
    "name": "TaskExampleProcess",
    "link": {
        "rel": "self",
        "href": "/TaskExampleProcess/3c5e28f0-cec1-11e2-b076-31b0fecf7b6f"
    },
    "state": [
        {
            "position": "MyTask",
            "owningProcessId": "3c5e28f0-cec1-11e2-b076-31b0fecf7b6f"
        }
    ],
    "history": [
        {
            "name": "MyStart"
        },
        {
            "name": "MyTask"
        }
    ],
    "properties": {
        "myFirstProperty": {
            "gugus": "blah",
			"sugus": "foo"
        }
    }
}

Note: all REST request return either the process state or an array of process states.

Getting the process state, history, and properties

To the current state, history, and properties of process use

client.get('/TaskExampleProcess/3c5e28f0-cec1-11e2-b076-31b0fecf7b6f', function(err, req, res, obj) {...});

The returned object is the same as in the last POST request. Following REST convetions, the operation giving all processes of a given type is

client.get('/TaskExampleProcess', function(err, req, res, obj) {...});

Or if is also possible using query strings. For example, the following query returns all processes having property x containing the attribute y having the value uvw

client.get('/TaskExampleProcess?x.y=uvw', function(err, req, res, obj) {...});

It is also possible to query processes executing a task, an activity, or waiting for an event to happen by sending the following request:

client.get('/TaskExampleProcess?state=MyTask', function(err, req, res, obj) {...});

Of course, all queries can be combined in one request.

Sending messages and triggering events

Both is done by send a PUT request containing the send message or triggered event data as body:

var data = {
    "gugus": "blah"
};
client.put('/TaskExampleProcess/myprocessid/MyStart/myeventid', data, function(err, req, res, obj) {...});

or

var message = {
    "gugus": "blah"
};
client.put('/TaskExampleProcess/myprocessid/MyStart/mymessageid', data, function(err, req, res, obj) {...});

BPMN

Supported Elements

  • Start events: all kind of start events are mapped to the none start event. Any further specialization is then done in the implementation of the handler.
  • End events: all kind of end events are mapped to the none end event. Any further specialization is then done in the implementation of the handler.
  • Gateways: Parallel- and exclusive gateways are supported.
  • Task, User Task, Manual Task, Receive Task: These tasks call an event handler when the task starts and then wait until taskDone(taskName, data) is invoked on the process.
  • Service Task, Script Task, Business Rule Task, Send Task (Wait Tasks): These tasks call an event handler when the task starts and proceed immediately after the the handler finishes.
  • Throw Intermediate Events: the handler is triggered when the intermediate event is reached. All types of intermediate events are treated the same.
  • Catch Intermediate Events: the handler is triggered if the event is catched and not when it is reached. For example, if we have an intermediate catch message event, the handler is triggered when the message arrives. If we have an intermediate catch timer event, the handler is triggered when the timout occurs. However, in both cases, no handler is triggered when the intermediate event is reached.
  • Call Activity: an external sub-process is called. The sub-process must not be a collaboration and must have exactly one start event.
  • Boundary Events: message and timeout boundary elements are supported for all wait tasks (Task, User Task, Manual Task, Receive Task). The handler is triggered if the events occur.

Limitations

  • Start events: all kind of start events are mapped to the none start event. Any further specialization is then done in the implementation of the handler.
  • End events: all kind of end events are mapped to the none end event. Any further specialization is then done in the implementation of the handler.
  • Gateways: only parallel- and exclusive gateways are supported yet.
  • Data objects: are ignored by the engine

Licensing

(The MIT License)

Copyright (c) 2014-2017 Scheer E2E AG

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

by Scheer E2E AG

bpmn's People

Contributors

aledbf avatar cyrilschmitt avatar eemece2 avatar jzakrzewski avatar kgebert avatar mrassinger 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  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

bpmn's Issues

Some handlers are not called

I am using the task.bpmn sample.

task.bpmn | uploaded via ZenHub

A basic manager:

var bpmn = require("bpmn");
var manager = new bpmn.ProcessManager();

bpmn.createUnmanagedProcess("/.../task.bpmn", function(err, task){
    task.triggerEvent("MyStart");
});

and the following handlers:

/*global module exports console */

exports.MyStart = function( data , done ){
    // called after the start event arrived at MyStart
    console.log("Running MyStart");
    done();
};

exports.MyTask = function( data , done ){
    // called at the beginning of MyTask
    console.log("Running MyTask");
    setTimeout(function(){
        done();
    }, 2000);

};

exports.MyTaskDone = function( data , done ){
    // Called after the process has been notified that the task has been finished
    // by invoking myProcess.taskDone("MyTask").
    // Note: <task name> + "Done" handler are only called for
    // user tasks, manual task, and unspecified tasks
    console.log("MyTask is now Done", data);
    done();
};


exports.MyEnd = function( data , done ){
    // Called after MyEnd has been reached
    console.log("Running MyEnd");
    done();
};

exports.MyEndDone = function( data , done ){
    // Called after MyEnd has been reached
    console.log("MyEnd is done");
    done();
};

I get the following output:

$ node manager.js 
Running MyStart
Running MyTask
$ 

so some handlers seem not to be called:

  • MyTaskDone
  • MyEnd
  • MyEndDone (ok not sure whether this one makes sense at all ๐Ÿ˜„ )

Sample project

Hi all and happy holidays,

I am new to node, I can't not understand how to start a new bpmn project in node, and how to start a new process and how to Handel in the handlers, can anyone help me with sample bpmn project in node or point me to documentation explaining the above.

Thanks and appreciate in advance

Data Object elements support?

Hi Team, first off CONGRATULATIONS for such a great project.
I would like to know if is there any plan to support Data Object elements? It would be really awesome for the engine since it allow to describe many technical topics on the model itself.

Looking forward to your feedback,
Best Regards, Rolando

Reading example .bpmn files in modeler/viewer

Are the provided example bpmn files supposed to be viewable in any BPMN2.0 compatible modeler/viewer? I'm having trouble opening them.

bpmn.io gives no errors but draws no diagram.
camundaModeler gives a bunch of errors and draws no diagram.
Errors examples (on opening collaboration.bpmn):
feature 'label' not found
feature 'mxCell' not found
BPMNPlane not associated with collaboration
Element *N*: Diagram element not found, element will not be shown

Then there's yaoqiang, which does display a diagram, but warns that the xml is malformed and should be fixed.

What viewer/modeler can I use to open the example files?

Should I expect issues when creating new bpmn files with one of these modelers? (I'm planning on using bpmn-js/bpmn.io)

Errors opening .bpmn files

I tried to open the bpmn files in the examples directory in Eclipse BPMN Modeler 1.2.4 and Camunda Modeler 1.5.1. Both show errors when opening the files. Below is the error log for task.bpmn in Eclipse. Camuda won't even open the diagram. Eclipse opens task.bpmn but collaboration.bpmn does not display properly, which may be related to the error messages.

!ENTRY org.eclipse.bpmn2.modeler.core 4 0 2016-12-08 17:40:53.546
!MESSAGE DocumentRoot id="unknown": Feature 'label' not found. (file:/usr/apps/das/bpmn/examples/processes/task.bpmn, 6, 60)

!ENTRY org.eclipse.bpmn2.modeler.core 4 0 2016-12-08 17:40:53.546
!MESSAGE DocumentRoot id="unknown": Feature 'mxCell' not found. (file:/usr/apps/das/bpmn/examples/processes/task.bpmn, 7, 129)

!ENTRY org.eclipse.bpmn2.modeler.core 4 0 2016-12-08 17:40:53.546
!MESSAGE DocumentRoot id="unknown": Feature 'label' not found. (file:/usr/apps/das/bpmn/examples/processes/task.bpmn, 11, 52)

!ENTRY org.eclipse.bpmn2.modeler.core 4 0 2016-12-08 17:40:53.547
!MESSAGE DocumentRoot id="unknown": Feature 'mxCell' not found. (file:/usr/apps/das/bpmn/examples/processes/task.bpmn, 12, 70)

!ENTRY org.eclipse.bpmn2.modeler.core 4 0 2016-12-08 17:40:53.552
!MESSAGE DocumentRoot id="unknown": Feature 'mxCell' not found. (file:/usr/apps/das/bpmn/examples/processes/task.bpmn, 17, 130)

!ENTRY org.eclipse.bpmn2.modeler.core 4 0 2016-12-08 17:40:53.552
!MESSAGE DocumentRoot id="unknown": Feature 'label' not found. (file:/usr/apps/das/bpmn/examples/processes/task.bpmn, 24, 54)

!ENTRY org.eclipse.bpmn2.modeler.core 4 0 2016-12-08 17:40:53.552
!MESSAGE DocumentRoot id="unknown": Feature 'mxCell' not found. (file:/usr/apps/das/bpmn/examples/processes/task.bpmn, 25, 127)

!ENTRY org.eclipse.bpmn2.modeler.core 4 0 2016-12-08 17:40:53.552
!MESSAGE DocumentRoot id="unknown": Feature 'mxCell' not found. (file:/usr/apps/das/bpmn/examples/processes/task.bpmn, 30, 130)

QUESTION: About Boundary Timer Events

I assume that the implementation of boundary events are interrupt which means the activity which was being performed will immediately be canceled,
That's right?

So, there is a way to implement non-interrupt boundary events?

Best Regards.

clustering

I am having trouble clustering the bpmn using pm2. I can only find process that were created by that node of the cluster. is it possible to refresh the process cache from what has been persisted to the data store?

How to use "Listener" properties in BPMN

I have add Execution Listener and generate XML.

<bpmn:task id="Task1">
    <bpmn:extensionElements>
      <camunda:executionListener event="start">
        <camunda:script scriptFormat="javascript">function(){alert(1)}</camunda:script>
      </camunda:executionListener>
    </bpmn:extensionElements>
   <bpmn:incoming>SequenceFlow_1hvxda3</bpmn:incoming>
</bpmn:task>

How to use "camunda:executionListener" properties?

How to start sub-process

When flow reaches the subprocess - handler is being called and then flow ends. No handler for start event in subprocess is called and I haven't found any functionality to start sub process. Any ideas on how to get this done?

QUESTION: There is a way to persist the bpmnXml schema, to parse just once

Hi,

I saw that to use
bpmn.createUnmanagedProcess("path/to/myProcess.bpmn", ...});
or
bpmn.createUnmanagedProcessFromXML("<definitions ... ", ...);

the first parameter is always the bpmnXml source,.. I know that XML Parsing is a little hardest work,
and it consume resources (processor and memory),..

I think that if we are always passing the bpmnXML source as first param, it always is parsing the schema to json. The question is, is there a way to persist the first parse of the schema,. (or cache), to do not parse the xml each time that we need to use an instance of a given process.

By example:

------------------------------------------File A ---------------------------------------------

    var processManager = new bpmn.ProcessManager({
        persistencyOptions: {
            uri: util.format('mongodb://%s:%s/%s', config.db.host, config.db.port, config.db.name)
        },
        bpmnXML: {name: 'TaskExampleProcess', xml: bpmnXML},
        handlerString: {name: 'Process1', string: handlerString}
    });

    processManager.createProcess("instance2", function(err, myProcess){
        if (err) throw err;
        // we start the process
        myProcess.triggerEvent("StartEvent_1");
        console.log('process started');
    });

------------------------------------------File B ---------------------------------------------

   var processManager = new bpmn.ProcessManager({
        persistencyOptions: {
            uri: util.format('mongodb://%s:%s/%s', config.db.host, config.db.port, config.db.name)
        },
        bpmnXML: {name: 'TaskExampleProcess', xml: bpmnXML},
        handlerString: {name: 'Process1', string: handlerString}
    });
    //// returns all processes
    processManager.getAllProcesses(function(err, processes) {
        console.log(processes)
    });

So, on both files I need to pass bpmnXML string, and I assume that the engine is parsing twice the xml.
The best way would be parse the xml at first time, give me an ID,... and on the second file B, I create the processManager instance with the given ID, and do not pass again the XML.

BEST REGARDS.

Persisting of modified properties

Possibly related to #16 and #17, we've been using your BPMN engine successfully now for a number of months with no glitches, but we recently had a requirement to modify rather than add one of the properties on a process instance. When we do this using setProperty, it shows as modified when using getProperties, but is left unchanged in the database. If there is a simple fix for this, we'd very much appreciate a patch.

Get bpmnId of a task

Is it possible to get the bpmnId of a task inside the handler ?
Something link that ?

exports.mytask = function(data, done) {
  console.log(this.bpmnId);
  done(data);
};

Is it possible to force the engine to persist?

Hi,

I'm trying to use e2ebridge/bpmn in a meteorjs app to replace a BPM system previously written in Java (using Activiti).

I'm having some trouble as I want to use Meteor's reactivity to show a task list from the MongoDB. All was going well but it seems that when I call taskDone it doesn't flush this to the database. Is there a way to force a persistency flush or to make the persistency more eager? If not, can you guide me as to modify the code to do it as this is becoming something for a deal-breaker and I'm under pressure on this project! Thanks,

Chris

closed mongodb connections on first completed process

db connections are closed when a process is done and has no parent. This holds water when a process was launched via a bpmn 'call activity' which is where the notion of 'parent' exists. AFAIK collaborating processes don't seem to have any parent processes but they do have participants who may, and more often than not, are, still in a 'processing' state. Currently the connection to mongo is terminated when a processes ends if it doesn't have any parents. But since collaborating processes don't necessarily have to have parents the first one to complete will kill the connection to mongo. I may be doing something wrong here so if so please let me know. Otherwise, if this is a legitimate issue I'll fix and do a pull request.
Just let me know,
thanks.

Qusestion

Why there should be a 'JS' file specific to a process to contain handlers? If I design a new process do I need to create handlers for that process too?

Question: bpmnParser.getBPMNDefinitions('./expenses.bpmn');

Hi,
I have the following code:

var bpmnParser = require('bpmn/lib/parsing/definitions.js');
var processDefinition = bpmnParser.getBPMNDefinitions('./expenses_payment.bpmn');

and it returns a result like:

[ { bpmnId: '_Collaboration_3',
participants: [ [Object], [Object] ],
messageFlows: [],
isCollaborationDefinition: true } ]

The question is why this function always returns an array? I seen that in most of cases it always is
just one object.
There is a case when this fn. will return more than one object?

Regards.

False detection of several processes

Hello,

Running a trivial sample, I get the following message:

$ node myprocess.js
[Error: The BPMN file '/.../myprocess.bpmn'. contains more than one process definition. Use 'createCollaboratingProcesses' instead of 'createProcess']
/..../myprocess.js:6
    myprocess.triggerEvent("MyStart");

However, the process definition contains a single process.
myprocess.bpmn | uploaded via ZenHub

TaskDone in REST

Probably this is an stupid question, but how do I tell the engine that a task is done using the REST server?

Mongodb driver version not compatible with 3.0+

In the package.json, "mongodb": ">= 1.3.10", I think it is not better way to dependency,
generally, major version changes means API not compatible with previous version, now, we need to upgrade mongodb to 3.x, which is causing bpmn is not working.

Wrong error message if no definition is found

We get [Error: The BPMN file '...'. contains more than one process definition. Use 'createCollaboratingProcesses' instead of 'createProcess'] if no executable definition is found which is wrong

Lanes in BPMN

Can we Use lanes in BPMN ?? How to use in this git example

RethinkDB persistency support?

Hi,

Would there be any interest in pulling this if I implement it? I think RethinkDB + BPMN are very cool, complimentary technologies.

In server mode, process creation is not persistent

when creating a manager with persistence:

var manager = new bpmn.ProcessManager({
    persistencyOptions: {
        uri: "mongodb://127.0.0.1:27017/bpmn"
    }
});

then creating a new task

var client = restify.createJsonClient({url: "http://localhost:9009"});
client.post('/Task');

The new task is not stored in mongodb. It is stored only if you create AND start it:

var client = restify.createJsonClient({url: "http://localhost:9009"});
client.post('/Task/MyStart');

QUESTION : how to implement collaboration process in rest way .

Hi ,
I have tried the collaboration process in rest way , I use the example : test/resources/projects/collaboration/collaboration.bpmn , howerver , I got the the following result

error:  process=My_Second_Process, id=1fc883b0-6593-11e4-9b73-eb590f76728b, description=Error in handler 'Task_2': TypeError: Cannot read property 'processClient' of undefined

I changed the name of every node with underscore for rest .

client.post('/My_Second_Process/Start_Event_2',function(error, req, res, obj) {
});

this.getParticipantByName("My_First_Process") does not work.

exports.Task_2 = function(data, done) {
    log("Task_2");
    this.getParticipantByName("My_First_Process", function(err, partnerProcess){
        partnerProcess.triggerEvent("Start_Event_1");
        done(data);
    });
};

Thanks a lot .

Processes getting stuck in converging parallel gateway

Hi,

Not sure why, but I'm having processes getting stuck at converging parallel gateways. Is this a known problem? Is there anything can be done about it? I've attached the process diagram. The orders seem to get stuck occasionally in "Order Built" and "Doors Built". I've tried a few things to work around it thinking it might be to do with the combination of exclusive gateways and parallel ones (ideally I want an inclusive gateway - is that anywhere near available?) e.g. I inserted a "Doors Start" activity which just calls done and "Track Cut" etc. to join the exclusive gateways (I couldn't get exclusive gateways to join any other way).

Please help! I was SO pleased when I found a Javascript BPMN engine but I'm now getting ready to give up and just go back to using Activiti via REST!

bpmnorder

createUnmanagedProcess tries to load the .js from folder releative from handler.js in Node_Modules

When I try to load a process wiht

var path = require("path");
bpmn.createUnmanagedProcess("./plan/myDiag.bpmn", function (err, myProcess) {
    // we start the process
    myProcess.triggerEvent("MyStart");
});

the handler.js file tries to load the associated .js file (myDiag.js) from the location the handler.js file is located. in my example the handler.js file is ...../node_modules/bpmn/lib/handler.js, which is not the same folder in which my myDiag.bpmn file is located.

Reading other bpmn 2 formats

I designed a workflow using Camunda's bpmn-io, which is a great Javascript/HTML designer/viewer. However the generated BPMN looks completely different than what you have in your samples. Here is an excerpt from both...

bpmn-io...

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="sid-38422fae-e03e-43a3-bef4-bd33b32041b2" targetNamespace="http://bpmn.io/bpmn" exporter="http://bpmn.io" exporterVersion="0.10.1">
  <process id="Process_1" isExecutable="false">
    <startEvent id="StartEvent_1" name="MyStart">
      <outgoing>SequenceFlow_1</outgoing>
    </startEvent>
    <task id="Task_1" name="MyTask">
      <incoming>SequenceFlow_1</incoming>
      <outgoing>SequenceFlow_03hcm0h</outgoing>
    </task>
    <sequenceFlow id="SequenceFlow_1" name="" sourceRef="StartEvent_1" targetRef="Task_1" />
    <endEvent id="EndEvent_06zsuum" name="MyEnd">
      <incoming>SequenceFlow_03hcm0h</incoming>
    </endEvent>
    <sequenceFlow id="SequenceFlow_03hcm0h" sourceRef="Task_1" targetRef="EndEvent_06zsuum" />
  </process>
  <bpmndi:BPMNDiagram id="BpmnDiagram_1">
    <bpmndi:BPMNPlane id="BpmnPlane_1" bpmnElement="Process_1">
      <bpmndi:BPMNShape id="StartEvent_1_gui" bpmnElement="StartEvent_1">
        <omgdc:Bounds x="242" y="187" width="30" height="30" />
        <bpmndi:BPMNLabel>
          <omgdc:Bounds x="212" y="219" width="90" height="22" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Task_1_gui" bpmnElement="Task_1">
        <omgdc:Bounds x="340" y="162" width="100" height="80" />
        <bpmndi:BPMNLabel>
          <omgdc:Bounds x="118.85714721679688" y="47" width="82.28570556640625" height="12" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="SequenceFlow_1_gui" bpmnElement="SequenceFlow_1">
        <omgdi:waypoint xsi:type="omgdc:Point" x="272" y="202" />
        <omgdi:waypoint xsi:type="omgdc:Point" x="340" y="202" />
        <bpmndi:BPMNLabel>
          <omgdc:Bounds x="225" y="140" width="90" height="20" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="EndEvent_06zsuum_di" bpmnElement="EndEvent_06zsuum">
        <omgdc:Bounds x="518" y="184" width="36" height="36" />
        <bpmndi:BPMNLabel>
          <omgdc:Bounds x="491" y="220" width="90" height="20" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="SequenceFlow_03hcm0h_di" bpmnElement="SequenceFlow_03hcm0h">
        <omgdi:waypoint xsi:type="omgdc:Point" x="440" y="202" />
        <omgdi:waypoint xsi:type="omgdc:Point" x="475" y="202" />
        <omgdi:waypoint xsi:type="omgdc:Point" x="475" y="202" />
        <omgdi:waypoint xsi:type="omgdc:Point" x="518" y="202" />
        <bpmndi:BPMNLabel>
          <omgdc:Bounds x="430" y="235" width="90" height="20" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

and comparatively the same sample with e2e's bpmn...

<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:debugger="http://e2e.ch/bpmneditor/debugger">
  <bpmn:extensionElements>
    <debugger:position href="http://localhost:7261/grapheditor/debugger/position"/>
  </bpmn:extensionElements>
  <bpmn:process id="PROCESS_1">
    <bpmn:startEvent name="MyStart" label="MyStart" id="2">
      <mxCell style="shape=mxgraph.bpmn.none_start_event;verticalLabelPosition=bottom;verticalAlign=top;" vertex="1" parent="1">
        <mxGeometry x="91" y="91" width="40" height="40" as="geometry"/>
      </mxCell>
    </bpmn:startEvent>
    <bpmn:task name="MyTask" label="MyTask" id="3">
      <mxCell style="shape=mxgraph.bpmn.task;" vertex="1" parent="1">
        <mxGeometry x="240" y="81" width="140" height="60" as="geometry"/>
      </mxCell>
    </bpmn:task>
    <bpmn:sequenceFlow name="" sourceRef="2" targetRef="3" id="4">
      <mxCell style="endArrow=block;endFill=1;endSize=6;edgeStyle=orthogonalEdgeStyle" edge="1" parent="1" source="2" target="3">
        <mxGeometry x="1" y="1" as="geometry">
          <mxPoint as="sourcePoint"/>
          <mxPoint x="30" y="30" as="targetPoint"/>
        </mxGeometry>
      </mxCell>
    </bpmn:sequenceFlow>
    <bpmn:endEvent name="MyEnd" label="MyEnd" id="5">
      <mxCell style="shape=mxgraph.bpmn.none_end_event;verticalLabelPosition=bottom;verticalAlign=top;" vertex="1" parent="1">
        <mxGeometry x="470" y="91" width="40" height="40" as="geometry"/>
      </mxCell>
    </bpmn:endEvent>
    <bpmn:sequenceFlow name="" sourceRef="3" targetRef="5" id="6">
      <mxCell style="endArrow=block;endFill=1;endSize=6;edgeStyle=orthogonalEdgeStyle" edge="1" parent="1" source="3" target="5">
        <mxGeometry x="1" y="1" as="geometry">
          <mxPoint as="sourcePoint"/>
          <mxPoint x="30" y="30" as="targetPoint"/>
        </mxGeometry>
      </mxCell>
    </bpmn:sequenceFlow>
  </bpmn:process>
</bpmn:definitions>

I am new to BPMN, but shouldn't these two be compatible? Shouldn't I be able to design a workflow in any bpmn designer and execute in any execution engine?

Getting the instance

Hello Team,
I have created a bpmn process and it executes well. Is it possible to get the specific running instance of process and validate any of the task (Human Task) from the process using the instance.

Thanks

rest api

howa can i define frontend route with restify!

bpmn-Query

I like to implement BPM engine in Node to execute business process we are using your bpmn package and it works fine. I do have some queries

Can we introduce organisation and lanes in your module. If yes then how can we implement ?

Properties are not set in Gateways in Default events

screenshot from 2015-12-10 16 38 46

I am trying to set properties in EXCLUSIVE GATEWAY events caught by below handler.

exports.defaultEventHandler = function(eventType, currentFlowObjectName, handlerName, reason, done) {
//Called, if no handler could be invoked.
console.log('defaultEventHandler ' + eventType + currentFlowObjectName);
this.setProperty('Date()','Date()');
done();
};

console says
defaultEventHandler callHandlerStateChange1

but the properties is not set the mongo database.
can you help me out? Please.

_BELOW CODE WORKS FINE_

screenshot from 2015-12-10 17 18 02

Problem with bpmn:import and bpmn:extensionElements is not being interpreted, and an error is thrown ...

Bpmn imports is not working...

Example

 <?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions id="ShipmentDefinitions"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://schema.omg.org/spec/BPMN/2.0 BPMN20.xsd"
  xmlns:bpmn="http://schema.omg.org/spec/BPMN/2.0"
  xmlns:camunda="http://sample.bpmn.camunda.com/"
  xmlns:xbpmn="http://bpmn.camunda.com/"
  xmlns:tns="http://sample.bpmn.camunda.com/"
  typeLanguage="http://www.w3.org/2001/XMLSchema"
  expressionLanguage="http://www.w3.org/1999/XPath"
  targetNamespace="http://sample.bpmn.camunda.com/">

  <!-- imports -->
  <bpmn:import namespace="http://sample.bpmn.camunda.com/"
               location="SampleService.xsd"
               importType="http://www.w3.org/2001/XMLSchema"/>
   ...

   <extensionElements>
    <camunda:formData>
        <camunda:formField
            id="firstname" label="Firstname" type="string">
            <camunda:validation>
               <camunda:constraint name="maxlength" config="25" />
               <camunda:constraint name="required" />
            </camunda:validation>
        </camunda:formField>
        <camunda:formField
            id="lastname" label="Lastname" type="string">
            <camunda:validation>
               <camunda:constraint name="maxlength" config="25" />
               <camunda:constraint name="required" />
            </camunda:validation>
        </camunda:formField>
        <camunda:formField
            id="dateOfBirth" label="Date of Birth" type="date" />
    </camunda:formData>
  </extensionElements>

  ...

parser is returning an error like:

The BPMN file contains 4 error(s).

Unbound namespace prefix: "bpmn:import"
Line: 15\nColumn: 147 Char: >
Unbound namespace prefix: "bpmn:extensionElements"
Line: 19
Column: 28
Char: >Unbound namespace prefix: "bpmn:extensionElements"
Line: 37 
Column: 30

I'm doing something wrong? or they are not supported actually?

raising error events in your script

I see that you can specify a global unhandled exception handler callback per process in your js file. If I model script exceptions as an intermediate error event in my bpmn file, is there a way to raise those events when the script error occurs? Would I use a try/catch and trigger the event manually? If so, how?

I am new to BPMN so hopefully I missed something in the docs.

"Cannot find module " -- javascript file not found

The engine cannot find the Javascript file:

My process looks like this:

var bpmn = require("bpmn");
console.log("Start process")
bpmn.createUnmanagedProcess("bpmn/simpleRegistration.bpmn", function(err, myProcess) {
    // we start the process
    myProcess.triggerEvent("Start_registration");
});
$ ls -la bpmn
total 24
drwxr-xr-x 1 ieugen ieugen  184 aug 10 20:40 .
drwxr-xr-x 1 ieugen ieugen  186 aug 10 20:41 ..
-rw-r--r-- 1 ieugen ieugen 6511 aug 10 20:21 simpleRegistration.bpmn
-rw-r--r-- 1 ieugen ieugen  757 aug 10 20:22 simpleRegistration.js
$ node -v 
v2.2.1
$ npm -v
2.11.0
module.js:334
    throw err;
    ^
Error: Cannot find module 'bpmn/simpleRegistration.js'
    at Function.Module._resolveFilename (module.js:332:15)
    at Function.Module._load (module.js:282:25)
    at Module.require (module.js:361:17)
    at require (module.js:380:17)
    at Object.exports.getHandlerFromFile (/home/ieugen/contracte/gpec-event/gpec-event/node_modules/bpmn/lib/handler.js:72:12)
    at exports.createUnmanagedProcess (/home/ieugen/contracte/gpec-event/gpec-event/node_modules/bpmn/lib/public.js:36:28)
    at Object.<anonymous> (/home/ieugen/contracte/gpec-event/gpec-event/bpmn-sample.js:5:6)
    at Module._compile (module.js:426:26)
    at Object.Module._extensions..js (module.js:444:10)
    at Module.load (module.js:351:32)

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.