Giter Club home page Giter Club logo

ergometerjs's Introduction

Introduction

Java script ergometer driver for concept 2 performance monitor with BLE. (The PM5) Works on all major platforms using cordova and node/electron",

I hope this project will become a shared effort to make it easier to write next generation ergometer software.

Tijmen Tijmen@vangulik

Project features

  • The project is open source and and it is based on open source project. (appache 2 license)

  • Uses low power bluetooth (BLE) connection (only PM5).

  • Usb (hid) support for PM3/4/5

  • Written in typescript which is compiled to javascript. You can use the driver without typescript.

  • Platform independent (mobile / desktop / web ) I have not yet been able to test all platforms but it should work on:

    • Mobile using cordova: iOS,android, Windows
    • Mobile react native: iOS,android
    • Desktop using Electron MacOS X, Windows, Linux
    • Server using Node MacOS X, Windows, Linux (inc Raspberry PI)
    • Web: chrome canary (still beta)

Basically ErgometerJS needs javascript and a blue tooth driver which can be (noble,cordova-plugin-ble or web bluetooth)

Change Log

  • 1.4.8 New values for usb,:
    • strokeDistance;
    • driveTime;
    • strokeRecoveryTime;
    • strokeCount;
  • 1.4.6
    • Fixed PM5 usb communication problem, On some hardware/firmware combinations the buffer size was higher than expected. This gives problems with newer PM5 devices.
  • 1.4.5
    • Made a work around for ble central bug for ios (1.3.1) You have run start scan twice to get the scan to work on ios13
  • 1.4.4
    • Cordova ios has very late library initialization (even after device ready). Recheck if the driver needs initialization just before connecting
    • Improved error handling and stop scan when driver could not be found
  • 1.4.3
    • Upgrade android to 9.0.0 (api level 29 needed for the android store)
    • Removed obsolete simple cordova demo
    • fix connection issue for usb demo when connecting for the first time
  • 1.4.2
    • When multi plexing is enabled on android devices it did not receive any events because a disable was send as last. After this fix connecting with multi plex should also be faster.
  • 1.4.1
    • after c2 firmware is updated , the power curve contains often two curve. made a workaround to detect the correct end.
  • 1.4.0
    • Separate blue tooth Heart rate monitor class which makes use of the existing driver infra structure. This is use full for devices like the PM3 which does not support heart rate.
  • 1.3.7
    • BLE: fix strokes value
    • BLE: fix power value
    • USB: Fixed time workout EndDuration/time is now correctly set
  • 1.3.6
    • fixed breaking change in web hid api
  • 1.3.5
    • New Ble central driver for cordova + demo. Currently bleat + evo things ble was used on cordova. This driver is not supported any more and there where a lot of errors in the log. So I replaced it by the popular ble central.
    • For ble the notification enable/disable was called too many times. This fix can prevent initial connection problems on some ble drivers.
    • Expose the driver property so it can be set.
  • 1.3.4
    • added minified version of ergometer.js
    • Merge long config commands when they are directly after each other
    • Added sortCommands property (by default switched off) This sorts the commands so they can be merged for efficiency.
    • Refactored sending commands (added an extra buffer and removed wait state)
    • Reduced calls when not yet rowing
  • 1.3.3
    • Increased accuracy of usb
  • 1.3.2
    • Add missing stuffing
  • 1.3.1
    • Added csafe state to the receive buffer and access from the command to the receive buffer
    • more async refactorings for better stabliity
    • Small bug fixes
  • 1.3.0
    • Refactored internals for better stability of the usb csafe commands (csafe command processing could stop after some time.)
    • Breaking change: the csafebuffer is not a property of the monitor any more. It is replaced by a function newCsafeBuffer() which creates a new buffer on every call. This prevents potential async problems.
    • the clear function of the csafebuffer is removed. (it is not needed any more because newCsafeBuffer creates every time an empty buffer )
  • 1.2.0
    • Cordova android usb support
  • 1.1.0
    • Added WebHid support
    • The promise of the send function is now resolved after receiving all the data. (for both usb and ble)
    • Error handling and connection stability enhancements
  • 1.0.1
    • Fixed bugs in Usb part
  • 1.0.0
    • Added USB support for PM3, PM4, PM5 for electron and node I am anticipating on the next WebHid standard which should add browser support for Usb devices. I will also check if it is possible to support cordova.
    • Make it possible to set the driver
    • Breaking change: the PerformanceMonitor is now named PerformanceMonitorBle
    • Fix: Web blue tooth driver dit not notice when device is disconnected
    • Upgraded Electron demo. The demo does not use web bluetooth instead of noble.
  • 0.0.12
    • Web bluetooth Fix: web blue tooth messages stop after some time.
  • 0.0.11
    • added web bluetooth support
  • 0.0.10
    • ionic 2 example
    • moved readme's
  • 0.0.9
    • Upgraded bleat 0.1.0
      • The bleat bug fixes are not needed any more.
      • Still use the bleat classic interface
      • The ergometer api is not changed but you will need to include other bleat javascript libraries in your html/javascript. See the demo's for the details
    • Upgraded to typescript 1.8.2
    • Made a start with implementing Web-bluetooth. In the future this allows you to run the app from a normal browser. This is still work in progress
  • 0.0.8
    • Record and replay events. This is use full for:
      • Writing code without the constant need of an ergometer
      • You can test code in an phone emulator (emulators do not have access to bluetooth hardware)
      • Writing unit tests
      • Record issues and send them to some one else to fix.
    • added a demo project for record and replay
  • 0.0.7
    • More commands
    • Skipp some strange return values which look like corrupted or undocumented return values
    • Short hand notation for some simple get and set commands
    • Program command , the value now a correct type and the property program is now named value
  • 0.0.6
    • Refactored all internal error handling to make use of Promises
    • Made internal driver layer based on Promises which gives some more protection and will make recording easier in the future.
  • 0.0.5
    • Improved npm build script and typescript config files
    • Send now returns a Promise. Changed the demo for this. I plan to make more use of promises to clean up some internal error handling code.
  • 0.0.4
    • Electron demo for desktop apps
    • separated the demo code from the platform code
    • Refactored error handling
    • Renamed project from MobileErgometer to ErgometerJS
  • 0.0.3
  • easy ble is replaced by bleat (still uses the evothings ble drivers) Easy ble is replaced by bleat. With some small changes in bleat api my ergometer api is still backwards compatible.

You only need to change the javascript included file

<script src="libs/evothings/easyble/easyble.js"></script>

to

<script src="libs/bleat.js"></script>
  • 0.0.2 New features:
    • csafe framework
    • power curve event and csafe command
    • some simple csafe commands
  • 0.0.1 First version

Licenses

Components

  • The project : Apache license 2.
  • Bleat : Mit license
  • Electron: Mit license

platforms

pm3-5 usb Blue tooth
Web beta yes
Cordova android yes
Electron yes yes
React native *
  • the demo contains an limeted proof of concept, there are other libraries which have better suport. it is not difficult to support other usb/ble drivers ( react-native-ble-plx may be an better option)

Todo

  • usb ios support
  • Add more commands

Known problems

  • There are problems in the PM5 BLE firmware. Some csafe commands give back invalid responses. I hope they fix it soon. See

http://www.c2forum.com/viewtopic.php?f=15&t=93321

  • ES6-Promises The library uses ES6-Promises. I assume that the library is uses in modern browsers. If this is not the case you need to include a poly fill javascript library:

https://github.com/lahmatiy/es6-promise-polyfill

  • React native: the used blue tooth library does not (yet?) support direct reading and writing to characteristics. due to this the csafe commands and the power curve do not work.

  • Web bluetooth is not yet supported by all browsers

Installation

To make it work you need:

  • An concept2 ergometer with PM5
  • An PC or Mac, android or iphone with BLE capability.
  • npm which can be downloaded from https://www.npmjs.com (for the electron usb demo you can use an older PM3 device)

Do a download or checkout from github (https://github.com/tijmenvangulik/ErgometerJS)

open a command prompt on the downloaded directory and type

npm install

to build the driver and the demo from the original typescript source. (not required they are already build)

npm run build

after the build the driver will be located in

driver\lib\ergometer.js

the description of the interface can be viewed as type script definition file. (this is generated from the source)

driver\lib\ergometer.d.ts

To use the library you need all the files in the lib directory and include it in your cordova phone gab app

<script src="libs/ergometer.js"></script>
<script src="libs/jquery/jquery.js"></script>

Usage for Ble

Create this class to acCess the performance data

var performanceMonitor= new ergometer.PerformanceMonitorBle();                                                       

After this connect to the events to get data

performanceMonitor.rowingGeneralStatusEvent.sub(this,this.onRowingGeneralStatus);                                 

On some android phones you can connect to a limited number of events. Use the multiplex property to overcome
this problem. When the multi plex mode is switched on the data send to the device can be a a bit different, see
the documentation in the properties You must set the multiplex property before connecting

performanceMonitor.multiplex=true;                                                                                

to start the connection first start scanning for a device,
you should call when the cordova deviceready event is called (or later)

performanceMonitor.startScan((device : ergometer.DeviceInfo) : boolean => {                                       
  //return true when you want to connect to the device                                                           
   return device.name=='My device name';                                                                         
});  

to connect at at a later time

performanceMonitor.connectToDevice('my device name'); 

the devices which where found during the scan are collected in

performanceMonitor.devices   

when you connect to a device the scan is stopped, when you want to stop the scan earlier you need to call

performanceMonitor.stopScan 

More information can be found in the typescript definitions:

https://github.com/tijmenvangulik/MobileErgometer/blob/master/api/lib/ergometer.d.ts

CSafe

CSafe is used to send and receive commands. I have implemented an jquery like api which is:

  • chainable (not required)
  • Extensible (you add your own commands to the buffer object. A command can consist out of multiple commands)
  • type safe
  • multiple commands can be send in one requests to reduce the load

An example of a

when the connection state is ready for communcation you can start with csafe commands

protected onConnectionStateChanged(oldState : ergometer.MonitorConnectionState, newState : ergometer.MonitorConnectionState) {  
        if (newState==ergometer.MonitorConnectionState.readyForCommunication) {

The csafeBuffer property is used to prepare one or multiple commands. Before adding commands you have to clear the buffer. At then end call send to call the buffer. The next command can only be send after that the first command is send. Use the optional success and error parameters of the send function to start with the next command. You can also send the next command when data is received.

this.performanceMonitor.newCsafeBuffer()
            .getStrokeState({
                received: (strokeState : ergometer.StrokeState) =>{
                    this.showData(`stroke state: ${strokeState}`);
                }
            })
            .getVersion({
                received: (version : ergometer.csafe.IVersion)=> {
                    this.showData(`Version hardware ${version.HardwareVersion} software:${version.FirmwareVersion}`);
                }
            })
            .setProgram({program:2})
            .send();

It is not required to chain the commands. You can also write code the classic way:

var buffer=this.performanceMonitor.newCsafeBuffer();
buffer.setProgram({program:2}); 
buffer.send();

It is possible to add new commands to the command buffer. for this you have to call commandManager.register to register your new command. You have to pass on a function because the actual declaration is deferred to a later state.

There is one required command property where you define the main command. Some long commands like the configuration command have a second detail command. You can specify this in the detailCommand property. You do not have to set the start,stop,crc check,length bytes in the cdafe commands these values are automaticly calculated. (except when there is an additional length in the data of a command, like the power curve)

export interface ICommandStrokeState  {
    received : (state : StrokeState )=>void;
    onError? : ErrorHandler;
}
export interface IBuffer {
    getStrokeState(params : ICommandStrokeState) : IBuffer;
}

commandManager.register( (buffer : IBuffer,monitor : PerformanceMonitor) =>{
    buffer.getStrokeState= function (params : ICommandStrokeState) : IBuffer {
        buffer.addRawCommand({
            waitForResponse:true,
            command : csafe.defs.LONG_CFG_CMDS.SETUSERCFG1_CMD,
            detailCommand: csafe.defs.PM_SHORT_PULL_DATA_CMDS.PM_GET_STROKESTATE,
            onDataReceived : (data : DataView)=>{
                if (params.received) params.received(data.getUint8(0))
            },
            onError:params.onError
        });
        return buffer;
    }
})

There are many commands, I have not yet found time to add all the commands. If you added new ones please commit them to github. When you not care about writing a user friendly command wrapper you can allways send raw commands. For example

this.performanceMonitor.newCsafeBuffer()
    .addRawCommand({
                    waitForResponse:true,
                    command : csafe.defs.LONG_CFG_CMDS.SETUSERCFG1_CMD,
                    detailCommand: csafe.defs.PM_SHORT_PULL_DATA_CMDS.PM_GET_STROKESTATE,
                    onDataReceived : (data : DataView)=>{
                        alert(data.getUint8(0));
                    }
                })
    .send(); 
    .then(()=>{  //send returns a promise
       console.log("send done, you can send th next")
     }); 

Command merging Long config commands can be merged into one command for efficency when they are directly after each other in the buffer.

When you set sortCommands to true the commands are sorted so they can be merged without caring about the the order in which you add the commands.

Usage for Usb

An usb device has a quicker way of finding devices but does not have all the concept2 BLE events. So the api is a bit different. The csafe part is exactly the same as for the ble device.

To create an Usb monitor:

var performanceMonitor= new ergometer.PerformanceMonitorUsb();

//to find out which concept2 devices are connected var foundDevice; this.performanceMonitor.requestDevics().then(devices=>{ //here a list of concept 2 devices are returned //you can loop the devices devices.forEach( (device) => { console.log(device.productName); foundDevice=device; }) }); //to connect to an device you can use the connectToDevice if (foundDevice) performanceMonitor.connectToDevice(foundDevice);

to disconnect from the performance monitor call the disconnect method.

performanceMonitor.disconnect()

you can retreive data from the monitor by connecting to events. when you do not subscribe to any of the training/stroke/power curve events then the monitor will not do any csafe commands to get data. You have to do your own csafe calls to get data.

logEvent

returns error, info and trace messages. (same event as in the blue tooth ergometer)

connectionStateChangedEvent

Get info on the connection state. (same event as in the blue tooth ergometer)

performanceMonitor.connectionStateChangedEvent.sub(this,(oldState,newState)=>{
    console.log("new connection state="+newState.toString());       
});

strokeStateEvent

Using this event you can see if the rower is doing is rowing and you can see in which phase he is.

performanceMonitor.strokeStateEvent.sub(this,(oldState : ergometer.StrokeState,newState : ergometer.StrokeState)=>{
    console.log("New state:"+newState.toString());
})

trainingDataEvent

Information on the selected training and the state of the training.

performanceMonitor.trainingDataEvent.sub(this,(data :ergometer.TrainingData)=>{
    console.log("training data :"+JSON.stringify(data,null,"  "));
});

strokeDataEvent

Data on the last stroke.

performanceMonitor.strokeDataEvent.sub(this,(data: ergometer.StrokeData)=>{
    console.log("stroke data:"+JSON.stringify(data,null,"  "));
});

powerCurveEvent

The power curve. When you connect to this event the data will be retreived. (same event as in the blue tooth ergometer)

performanceMonitor.powerCurveEvent.sub(this,(data : number[])=>{
    console.log("stroke data:"+JSON.stringify(data,null,"  "));
})

csafe comnunication

Csafe communication is done the same way as the ble commnunication. See the csafe paragraph of the previous chapter how to do csafe commands.

this.performanceMonitor.newCsafeBuffer()

heart rate

When the end user has an PM5 he will normally connect a heart rate device to the concept2 performance monitor and the device will send the heart rate to ergometerjs. Hover older devices like the PM3 do not have heart rate support. For this I have included a class HeartRateMonitorBle which can directly to a blue tooth heart rate device.

HeartRateMonitorBle makes use of the same driver infra structure as the ergometer PerformanceMonitorBle class. The inter face of the heart rate monitor is similar to the blue tooth class the main difference is that this class has a heartRateDataEvent for reading the heart rate.

To start the connection first start scanning for a device,
you should call when the cordova deviceready event is called (or later)

performanceMonitor.startScan((device : ergometer.HeartRateDeviceInfo) : boolean => {                                       
  //return true when you want to connect to the device                                                           
   return device.name=='My device name';                                                                         
});  

to connect at at a later time

performanceMonitor.connectToDevice('my device name'); 

the devices which where found during the scan are collected in

performanceMonitor.devices   

when you connect to a device the scan is stopped, when you want to stop the scan earlier you need to call

performanceMonitor.stopScan()

To disconnect call

performanceMonitor.disconnect()

to receive the heart rate information you have to subscribe to the heartRateDataEvent event

performanceMonitor.heartRateDataEvent.sub(this,this.hearRateData);

An demo of the api is in included in the electron usb debug example.

Examples

Electron

Use this when you want tow write a desktop app using html 5. ErgometerJS can connect using noble to an PM5 device or using.

demos/simple_electron

Record an replay

Record and replay the bluetooth communication. Handy for debuging without having to row on an ergometer. This demo is written using electron, this makes it ideal for debugging the communication because you can do not need to place it on a phone to make it worrk.

demos/recording

Ionic 2

Popular GUI frame work for writing hml5 apps for mobile apps. Since it is html 5 the app is re-usable in web or electron.

demos/ionic_test

React native

Write mobile apps using native components in react using javascript.

Know problem: the used blue tooth library does not (yet?) support direct reading and writing to characteristics. due to this the csafe commands and the power curve do not work.

demos/react_native

Web bluetooth

This is for web application. Directy access the ergometer from the webbrowser. This feature is at the point of writing only works in the latest chrome on a mac and linux.

demos/webbluetooth

Node/electron Usb (hid device)

An example how to connect to an older PM3-4 monitor using usb. Blue tooth native library is not installed, but it can still make use of blue tooth using chrome web ble in electron.

demos/usb_electron

Version which includes all the ergometer js source for debuging purpose.

demos/usb_electron_debug

Web hid example

Future versions will support web hid. With this you can connect using usb to your pm5 directly from chrome. The feature is not yet stable but it works in in chrome canary. with experimental features turned on.

https://github.com/robatwilliams/awesome-webhid#status

demos/web_usb_debug

cordova usb example (includes ble heart rate samle).

for cordova I have created an usb hid plugin which needs to be installed

https://github.com/tijmenvangulik/cordova-usb-hid

The demo compiles by including the original source code. This is good for debugging. It is better to include the lib when you only the lib.

This sample also includes an example how to connect to an heart rate device directly using the HeartRateMonitorBle class. This is usefull for connecting to a PM3 device which does not have heart rate support.

demos/usb_cordova_debug

cordova ble example

Blue tooth example using cordova for mobile platforms.

demos/ble_cordova_debug

ergometerjs's People

Contributors

auspexeu avatar tijmenvangulik 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

ergometerjs's Issues

Can't get Electron demo to work

I'm using a Mac and can't get the Electron example to discover my PM5. I've actually tried using noble's example advertisement-discovery.js and it looks like my device is never "discover"ed.

Have you had success with the Electron version at all?

Thanks!

Issue with Bluetooth Device filtering

Hi, nice work! There is an issue with the filtering for requestDevice. It seems to be related to what services are published when the device is already paired.

navigator.bluetooth.requestDevice({
                            filters: [
                                { services: [ble.PMDEVICE]
                                }
                            ],
                            optionalServices: [ble.PMDEVICE_INFO_SERVICE, ble.PMCONTROL_SERVICE, ble.PMROWING_SERVICE]

ble.PMDEVICE = "ce060000-43e5-11e4-916c-0800200c9a66";
ble.PMDEVICE_INFO_SERVICE = "ce060010-43e5-11e4-916c-0800200c9a66";
ble.PMCONTROL_SERVICE = "ce060020-43e5-11e4-916c-0800200c9a66";
ble.PMROWING_SERVICE = "ce060030-43e5-11e4-916c-0800200c9a66";

The PMDEVICE service may not be available and thus no device is found. Changing the filter to namePrefix : "PM5 " allows the device to be found every time. Filtering on PMDEVICE_INFO_SERVICE instead may also be an option. This was tested on Chrome OS 66.

Questions regarding ErgometerJS application

Hi,

I'm a little new to using Node. Is it possible for me to use ErgometerJS within a node server that would receive REST requests and command multiple concept 2 Ergs? Where would I begin?

Also, are there any plans to get ErgometerJS to work with react-native?

Moses

Cannot get to work with HID on Windows machine

I have a next js app running on linux server.

On my laptop running linux i can connect and receive data, on my company windows laptop i can't, it just says data Time out buffer.

I've tested on my laptop all demos working, on my company laptop nothing of them work.

Anyone running into this problem too?

Im using the USB connection.

Any more info needed to help me just ask, i can send.

image

React-native on Linux and Android

I try to use the library via react-native on Android. (I just started with react-native, so it's possible that I'm making dump mistakes).

'npm install' fails while compiling libusb:

AR(target) Release/obj.target/usb.a
COPY Release/usb.a
CXX(target) Release/obj.target/usb_bindings/src/node_usb.o
../src/node_usb.cc: In function ‘void handleHotplug(std::pair<libusb_device*, libusb_hotplug_event>)’:
../src/node_usb.cc:151:58: warning: ‘v8::Localv8::Value Nan::MakeCallback(v8::Localv8::Object, const char*, int, v8::Localv8::Value*)’ is deprecated [-Wdeprecated-declarations]
Nan::MakeCallback(Nan::New(hotplugThis), "emit", 2, argv);
^
In file included from ../src/helpers.h:3:0,
from ../src/node_usb.h:21,
from ../src/node_usb.cc:1:
../node_modules/nan/nan.h:1001:46: note: declared here
NAN_DEPRECATED inline v8::Localv8::Value MakeCallback(

Shouldn't there be some kind of compile switch so that only the BLE-Version is build?

Ionic 1 demo

It would be nice to have a demo for Ionic 1 as well. Do you plan on making one?

recovery time and drive time

These properties also seem like they have some funky values unless I am totally off with counting in my head... this probably needs some further investigation. Just putting this here to keep track.

setProgram

I am trying to use the following code to select the first pre-programmed workout.

this.performanceMonitor.newCsafeBuffer()
          .setProgram({value: 1})
          .send()

It also correctly sends f1 24 02 01 00 25 f2 to the PM5 and the workoutDuration value gets updated, however the screen on the PM5 does not change at all and if I start pulling it switches back to Just row any idea what I am missing?

Android Chrome events

Hi,

Thanks for this work! My code currently sets up handlers for RowingGeneralStatus and RowingAdditionalStatus1. On my Windows 10 laptop, it works great.

However, on an Android running Chrome, using the same code, the RowingGeneralStatus handler fires as expected, but the RowingAdditionalStatus1 never fires.

Any thoughts?

Thanks,
Jeff

Node JS Demo?

Hello.

Can you let me know how to run any demo with node js? I read that this project also make nodeJS version for linux, (including Raspberry pi <<- which one I need!! ) please?

BLE driver issues

I am having a lot of issues with the BLE driver it seems. After connecting I get this log message a lot NotSupportedError: GATT operation failed for unknown reason. After a while it will transmit partial data from the PM5. However not everything there should be and I also get tons of this logged. wrong csafe frame ending.

Any thoughts how to zero in on this bug?

It seems like this might be related: https://bugs.chromium.org/p/chromium/issues/detail?id=664863

PM3?

Hi,

will this work with PM3? You are mentioning just PM5.

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.