diff --git a/README.md b/README.md index 9276b01..855c846 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,18 @@ -#Plotly Node API +# Plotly Node API [![Circle CI](https://circleci.com/gh/plotly/plotly-nodejs/tree/master.svg?style=svg)](https://circleci.com/gh/plotly/plotly-nodejs/tree/master) > Analyze and Visualize Data, Together -If you have a question about streaming let us know or open an issue! - -`ben@plot.ly` && `alexandre@plot.ly` - ## Streaming Plot Examples - [mock sensor stream](http://plot.ly/~streaming-demos/6/) - [math bar fight](http://plot.ly/~streaming-demos/44/) -##Installation +## Installation ```javascript npm install plotly ``` -##Usage +## Usage ```javascript var plotly = require('plotly')('username','apiKey'); @@ -31,19 +27,19 @@ plotly.plot(data,graphOptions,function() { }); ``` -####Full REST API Documentation can be found here: [https://plot.ly/api/rest/](https://plot.ly/api/rest/) +#### Full REST API Documentation can be found here: [https://plot.ly/api/rest/](https://plot.ly/api/rest/) Sign up for plotly here: [https://plot.ly/](https://plot.ly/) and obtain your API key and Stream Tokens in your plotly settings: [https://plot.ly/settings](https://plot.ly/settings). -#Methods -##var plotly = require('plotly')(username, apiKey) +# Methods +## var plotly = require('plotly')(username, apiKey) `username` is a string containing your username `apiKey` is a string containing your API key ```javascript var plotly = require('plotly')('username', 'apiKey'); ``` -##plotly.plot(data,graphOptions[, callback]) +## plotly.plot(data,graphOptions[, callback]) Plotly graphs are described declaratively with a data JSON Object and a graphOptions JSON Object. `data` is an array of Objects and with each object containing data and styling information of separate graph traces. Docs: [https://plot.ly/api/rest](https://plot.ly/api/rest) `graphOptions` is an Object containing styling options like axis information and titles for your graph. Docs: [https://plot.ly/api/rest](https://plot.ly/api/rest) @@ -62,7 +58,7 @@ plotly.plot(data, graphOptions, function (err, msg) { console.log(msg); }); ``` -##var stream = plotly.stream(token[, callback]) +## var stream = plotly.stream(token[, callback]) `token` accepts a token string `callback(res)` where `res` is a the response object with the following attributes : `res.msg`, `res.statusCode` @@ -160,7 +156,7 @@ Plotly.plot(data, graphOptions, function (err, resp) { ``` -##plotly.getFigure(fileOwner, fileId[, callback]) +## plotly.getFigure(fileOwner, fileId[, callback]) `file_owner` accepts a string of the file owner's name `fileId` is an integer, representing the graph ID `callback(figure)` where `figure` is a the JSON object of the graph figure @@ -174,9 +170,9 @@ plotly.getFigure('fileOwner', 'fileId', function (err, figure) { }); ``` -##plotly.getImage(figure[, options, callback]) +## plotly.getImage(figure[, options, callback]) `figure` is a JSON object of the graph figure -`options.format` | `jpg`, `png`, `pdf`, `eps`, `webp` +`options.format` | `jpeg`, `png`, `pdf`, `eps`, `webp` `options.width` | width in `px` (default : 700) `options.height` | height in `px` (default : 500) @@ -233,3 +229,16 @@ plotly.getFigure('fileOwner', 'fileId', function (err, figure) { }); }); ``` + +## plotly.deletePlot(fid[, callback]) +`fid` is a String, the id of the plot you wish you delete +`callback` is a function with `err` and `plot` as parameters. `err`, if present, is the error message returned from the request. `plot` is the plot that was deleted. + +```javascript +var plotly = require('../.')('username','apiKey'); + +plotly.deletePlot('88', function (err, plot) { + if (err) console.log(err) + else console.log(plot); +}); +``` diff --git a/examples/delete-plot.js b/examples/delete-plot.js new file mode 100644 index 0000000..a3d1afc --- /dev/null +++ b/examples/delete-plot.js @@ -0,0 +1,8 @@ +'use strict'; + +var plotly = require('../.')('alexander.daniel','u1jactdk3m'); + +plotly.deletePlot('2718', function (err, plot) { + if (err) console.log(err); + else console.log(plot); +}); diff --git a/examples/streaming-multiple-traces.js b/examples/streaming-multiple-traces.js new file mode 100644 index 0000000..1921b5c --- /dev/null +++ b/examples/streaming-multiple-traces.js @@ -0,0 +1,46 @@ +var config = require('./config.json'), + username = config.user, + apikey = config.apikey, + tokens = config.tokens, + Plotly = require('../.')(username, apikey), + Signal = require('random-signal'); + +function initTrace(i) { + return { + x: [], // init. data arrays + y: [], + type: 'scatter', + mode: 'lines+markers', + stream: { + "token": tokens[i], + "maxpoints": 100 + } + }; +} + +var data = [0, 1].map(initTrace); + +var layout = { + filename: "stream-multiple-traces", + fileopt: "overwrite", + layout: { + title: "streaming mock sensor data" + }, + world_readable: true +}; + + +Plotly.plot(data, layout, function (err, resp) { + if (err) return console.log("ERROR", err, data); + console.log(resp); + + [0, 1].forEach(function(i) { + var plotlystream = Plotly.stream(tokens[i], function() {}), + signalstream = Signal({tdelta: 100}); + + plotlystream.on("error", function (err) { signalstream.destroy(); }); + + signalstream.pipe(plotlystream); + }); + +}); diff --git a/examples/streaming-style-attributes.js b/examples/streaming-style-attributes.js new file mode 100644 index 0000000..dd15a9e --- /dev/null +++ b/examples/streaming-style-attributes.js @@ -0,0 +1,57 @@ +var config = require('./config.json'), + username = config.user, + apikey = config.apikey, + tokens = config.tokens, + Plotly = require('../.')(username, apikey); + +function initTrace(token) { + return { + x: [], // init. data arrays + y: [], + type: 'scatter', + mode: 'markers', + marker: { color: '#91C149' }, + stream: { + 'token': token, + 'maxpoints': 100 + } + }; +} + +var data = tokens.map(initTrace); + +var layout = { + filename: 'stream-style-attributes', + fileopt: 'overwrite', + layout: { + title: 'streaming data and style attributes' + }, + world_readable: true +}; + +Plotly.plot(data, layout, function(err, resp) { + if(err) return console.log('ERROR', err, data); + console.log(resp); + + tokens.forEach(function(token) { + var plotlystream = Plotly.stream(token, function() { + clearInterval(loop); + }), + i = 0; + + var loop = setInterval(function() { + var streamData = { + x: Math.random(), + y: 2 * Math.random(), + marker: { color: '#91C149' } + }; + + // pts below threshold get a different color + if(streamData.y < 1) streamData.marker.color = '#000000'; + + plotlystream.write(JSON.stringify(streamData) + '\n'); + i++; + + }, 1000); + }); +}); diff --git a/index.js b/index.js index f2a9296..01371e9 100644 --- a/index.js +++ b/index.js @@ -25,7 +25,7 @@ function Plotly(username,apiKey) { this.port = 443; } this.streamHost = ''; - this.version='1.0.2'; + this.version='1.0.4'; this.platform='nodejs'; this.origin='plot'; } @@ -53,7 +53,7 @@ Plotly.prototype.plot = function(data, graphOptions, callback) { } // trim off last ambersand - urlencoded = urlencoded.substring(0, urlencoded.length - 1); + urlencoded = new Buffer(urlencoded.substring(0, urlencoded.length - 1), 'utf8'); var options = { host: self.host, @@ -69,7 +69,13 @@ Plotly.prototype.plot = function(data, graphOptions, callback) { var req = https.request(options, function (res) { parseRes(res, function (err, body) { - body = JSON.parse(body); + + /* Try to parse the response */ + try { + body = JSON.parse(body); + } catch (e) { + callback(e); + } if ( body['stream-status'] != undefined) { self.streamHost = url.parse(body['stream-host']).hostname; @@ -135,6 +141,10 @@ Plotly.prototype.stream = function(token, callback) { } }); + stream.on('error', function (err) { + callback(err); + }); + if (stream.setTimeout) stream.setTimeout(Math.pow(2, 32) * 1000); return stream; @@ -165,16 +175,30 @@ Plotly.prototype.getFigure = function (fileOwner, fileId, callback) { var req = https.get(options, function (res) { parseRes(res, function (err, body) { - if (JSON.parse(body).error) { - err = JSON.parse(body).error; - callback(err); - } else { - var figure = JSON.parse(body).payload.figure; + + /* Try to parse the response */ + try { + body = JSON.parse(body); + } catch (e) { + callback(e); + } + + if (body.error) { + callback(body.error); + } + + else { + var figure = body.payload.figure; callback(null, figure); } + }); }); + req.on('error', function (err) { + callback(err); + }); + req.end(); }; @@ -219,10 +243,64 @@ Plotly.prototype.getImage = function (figure, opts, callback) { } var req = https.request(options, handleResponse); + + req.on('error', function (err) { + callback(err); + }); + req.write(payload); req.end(); }; +Plotly.prototype.deletePlot = function (fid, callback) { + if (!callback) callback = function () {}; + + var self = this; + + // Create the base64 authstring from buffer + var encodedAPIAuth = new Buffer(this.username + ':' + this.apiKey).toString('base64'); + + var options = { + host: 'api.plot.ly', + port: this.port, + path: '/v2/files/' + this.username + ':' + fid + '/trash', + method: 'POST', + agent: false, + withCredentials: true, + headers: { + 'Plotly-Client-Platform': 'nodejs ' + this.version, + 'authorization': 'Basic ' + encodedAPIAuth + } + }; + + var req = https.request(options, function (res) { + parseRes(res, function (err, body) { + + if (res.statusCode === 200) { + + callback(null, body); + + } else { + + var errObj = { + statusCode: res.statusCode, + err: body, + statusMessage: res.statusMessage + }; + + callback(errObj); // Pass out the error message from the backend + } + + }); + }); + + req.on('error', function (err) { + callback(err); + }); + + req.end(); +}; + // response parse helper fn function parseRes (res, cb) { var body = ''; diff --git a/package.json b/package.json index 8036ec6..423ced4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plotly", - "version": "1.0.2", + "version": "1.0.6", "description": "Simple node.js wrapper for the plot.ly API", "main": "index.js", "devDependencies": { diff --git a/test/test.js b/test/test.js index db0c3ce..073f323 100644 --- a/test/test.js +++ b/test/test.js @@ -3,207 +3,226 @@ var test = require('tape'); test('makes a rest call', function (t) { - t.plan(2); + t.plan(2); - var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); - var data = [{x:[0,1,2], y:[3,2,1], type: 'bar'}]; - var layout = {fileopt : 'overwrite', filename : 'nodenodenodetest'}; + var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); + var data = [{x:[0,1,2], y:[3,2,1], type: 'bar'}]; + var layout = {fileopt : 'overwrite', filename : 'nodenodenodetest'}; - plotly.plot(data, layout, function (err, msg) { - t.isEqual(msg.url, 'https://plot.ly/~node-test-account/0', 'url matches'); - t.notOk(err ,'no error'); - t.end(); - }); + plotly.plot(data, layout, function (err, msg) { + t.isEqual(msg.url, 'https://plot.ly/~node-test-account/0', 'url matches'); + t.notOk(err ,'no error'); + t.end(); + }); }); test('makes a rest call', function (t) { - t.plan(2); - - var options = { - username: 'node-test-account', - apiKey: 'tpmz9ye8hg', - host: 'plot.ly', - port: 443 - }; - - var plotly = require('../index')(options); - var data = [{x:[0,1,2], y:[3,2,1], type: 'bar'}]; - var layout = {fileopt : 'overwrite', filename : 'nodenodenodetest'}; - - plotly.plot(data, layout, function (err, msg) { - t.isEqual(msg.url, 'https://plot.ly/~node-test-account/0', 'url matches'); - t.notOk(err ,'no error'); - t.end(); - }); + t.plan(2); + + var options = { + username: 'node-test-account', + apiKey: 'tpmz9ye8hg', + host: 'plot.ly', + port: 443 + }; + + var plotly = require('../index')(options); + var data = [{x:[0,1,2], y:[3,2,1], type: 'bar'}]; + var layout = {fileopt : 'overwrite', filename : 'nodenodenodetest'}; + + plotly.plot(data, layout, function (err, msg) { + t.isEqual(msg.url, 'https://plot.ly/~node-test-account/0', 'url matches'); + t.notOk(err ,'no error'); + t.end(); + }); }); test('makes a rest call', function (t) { - t.plan(2); - var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); - var data = [{x:[0,1,2], y:[3,2,1], type: 'bar'}]; - var layout = {fileopt : 'overwrite', filename : 'nodenodenodetest'}; - - plotly.plot(data, layout, function (err, msg) { - t.isEqual(msg.url, 'https://plot.ly/~node-test-account/0', 'url matches'); - t.notOk(err, 'no error'); - t.end(); - }); + t.plan(2); + var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); + var data = [{x:[0,1,2], y:[3,2,1], type: 'bar'}]; + var layout = {fileopt : 'overwrite', filename : 'nodenodenodetest'}; + + plotly.plot(data, layout, function (err, msg) { + t.isEqual(msg.url, 'https://plot.ly/~node-test-account/0', 'url matches'); + t.notOk(err, 'no error'); + t.end(); + }); }); test('plot with incorrect userdata and return error', function (t) { - t.plan(1); + t.plan(1); - var plotly = require('../index')('node-test-accountasdfadsgaghaha', 'tpmz9ye8hg'); - var data = [{x:[0,1,2], y:[3,2,1], type: 'bar'}]; - var layout = {fileopt : 'overwrite', filename : 'nodenodenodetest'}; + var plotly = require('../index')('node-test-accountasdfadsgaghaha', 'tpmz9ye8hg'); + var data = [{x:[0,1,2], y:[3,2,1], type: 'bar'}]; + var layout = {fileopt : 'overwrite', filename : 'nodenodenodetest'}; - plotly.plot(data, layout, function (err, msg) { + plotly.plot(data, layout, function (err, msg) { - var errMessage = msg.message.split(',')[0]; + var errMessage = msg.message.split(',')[0]; - t.isEqual(errMessage, 'Aw', 'incorrect user info returns error... not properly though.'); - t.end(); - }); + t.isEqual(errMessage, 'Aw', 'incorrect user info returns error... not properly though.'); + t.end(); + }); }); test('makes a rest call with host foo', function (t) { - t.plan(1); - var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); - plotly.host = 'foo'; - var data = [{x:[0,1,2], y:[3,2,1], type: 'bar'}]; - var layout = {fileopt : 'overwrite', filename : 'nodenodenodetest'}; - - plotly.plot(data, layout, function (err) { - t.ok(err, 'error received as host was set to "foo"'); - t.end(); - }); + t.plan(1); + var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); + plotly.host = 'foo'; + var data = [{x:[0,1,2], y:[3,2,1], type: 'bar'}]; + var layout = {fileopt : 'overwrite', filename : 'nodenodenodetest'}; + + plotly.plot(data, layout, function (err) { + t.ok(err, 'error received as host was set to "foo"'); + t.end(); + }); }); test('makes a rest call with no callback', function (t) { - t.plan(1); - var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); - plotly.host = 'foo'; - var data = [{x:[0,1,2], y:[3,2,1], type: 'bar'}]; - var layout = {fileopt : 'overwrite', filename : 'nodenodenodetest'}; + t.plan(1); + var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); + plotly.host = 'foo'; + var data = [{x:[0,1,2], y:[3,2,1], type: 'bar'}]; + var layout = {fileopt : 'overwrite', filename : 'nodenodenodetest'}; - plotly.plot(data, layout); - t.ok(true); - t.end(); + plotly.plot(data, layout); + t.ok(true); + t.end(); }); test('makes a rest call with object for data', function (t) { - t.plan(1); - var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); - plotly.host = 'foo'; - var data = {x:[0,1,2], y:[3,2,1], type: 'bar'}; - var layout = {fileopt : 'overwrite', filename : 'nodenodenodetest'}; + t.plan(1); + var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); + plotly.host = 'foo'; + var data = {x:[0,1,2], y:[3,2,1], type: 'bar'}; + var layout = {fileopt : 'overwrite', filename : 'nodenodenodetest'}; - plotly.plot(data, layout); - t.ok(true); - t.end(); + plotly.plot(data, layout); + t.ok(true); + t.end(); }); test('getFigure', function (t) { - t.plan(1); - var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); - plotly.getFigure('node-test-account', '0', function (err, figure) { - t.ok(figure); - t.end(); - }); + t.plan(1); + var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); + plotly.getFigure('node-test-account', '0', function (err, figure) { + t.ok(figure); + t.end(); + }); }); test('getFigure error', function (t) { - t.plan(1); - var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); - plotly.getFigure('node-test-account', '99', function (err) { + t.plan(1); + var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); + plotly.getFigure('node-test-account', '99', function (err) { - t.ok(err); - t.end(); - }); + t.ok(err); + t.end(); + }); }); test('getImage, good and error', function (t) { - t.plan(2); - var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); - - var trace1 = { - x: [1, 2, 3, 4], - y: [10, 15, 13, 17], - type: 'scatter' - }; - - var trace2 = { - x: [1, 2, 3, 4], - y: [16, 5, 11, 9], - type: 'scatter' - }; - - var figure = { - 'data': [trace1, trace2] - }; - - plotly.getImage(figure, {}, function (err, imageData) { - t.notOk(err); - t.ok(imageData); - t.end(); - }); + t.plan(2); + var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); + + var trace1 = { + x: [1, 2, 3, 4], + y: [10, 15, 13, 17], + type: 'scatter' + }; + + var trace2 = { + x: [1, 2, 3, 4], + y: [16, 5, 11, 9], + type: 'scatter' + }; + + var figure = { + 'data': [trace1, trace2] + }; + + plotly.getImage(figure, {}, function (err, imageData) { + t.notOk(err); + t.ok(imageData); + t.end(); + }); }); test('getImage, imageserver error', function (t) { - t.plan(2); - var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); - - var trace1 = { - x: [1, 2, 3, 4], - y: [10, 15, 13, 17], - type: 'scatter' - }; - - var trace2 = { - x: [1, 2, 3, 4], - y: [16, 5, 11, 9], - type: 'scatter' - }; - - var data = [trace1, trace2]; - plotly.getImage(data, 'img', function (err, imageData) { - t.ok(err); - t.notOk(imageData); - t.end(); - }); + t.plan(2); + var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); + + var trace1 = { + x: [1, 2, 3, 4], + y: [10, 15, 13, 17], + type: 'scatter' + }; + + var trace2 = { + x: [1, 2, 3, 4], + y: [16, 5, 11, 9], + type: 'scatter' + }; + + var data = [trace1, trace2]; + plotly.getImage(data, 'img', function (err, imageData) { + t.ok(err); + t.notOk(imageData); + t.end(); + }); }); +test('creates a plot with UTF chars in filename', function (t) { + t.plan(1); + + var options = { + username: 'node-test-account', + apiKey: 'tpmz9ye8hg', + host: 'plot.ly', + port: 443 + }; + + var plotly = require('../index')(options); + var data = [{x:[0,1,2], y:[3,2,1], type: 'bar'}]; + var layout = {fileopt : 'overwrite', filename : 'üüüü'}; + plotly.plot(data, layout, function (err, msg) { + t.notOk(err ,'no error'); + t.end(); + }); + +}); test('streams some data', function (t) { - t.plan(1); - var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); - var initdata = [{x:[], y:[], stream:{token:'i9zxn8goas', maxpoints:200}}]; - var initlayout = {fileopt : 'extend', filename : 'nodenodenode-test-stream'}; - - plotly.plot(initdata, initlayout, function (err) { - if (err) return console.log(err); - - var streamObject = JSON.stringify({ x : 1, y : 1 }); - - var stream = plotly.stream({ - token: 'i9zxn8goas', - host: undefined, - port: null - }); - - setInterval(function () { - stream.write(streamObject+'\n'); - }, 500); - - setTimeout(function () { - t.ok(true, 'no errors'); - t.end(); - process.exit(0); - }, 5000); - }); + t.plan(1); + var plotly = require('../index')('node-test-account', 'tpmz9ye8hg'); + var initdata = [{x:[], y:[], stream:{token:'i9zxn8goas', maxpoints:200}}]; + var initlayout = {fileopt : 'extend', filename : 'nodenodenode-test-stream'}; + + plotly.plot(initdata, initlayout, function (err) { + if (err) return console.log(err); + + var streamObject = JSON.stringify({ x : 1, y : 1 }); + + var stream = plotly.stream({ + token: 'i9zxn8goas', + host: undefined, + port: null + }); + + setInterval(function () { + stream.write(streamObject+'\n'); + }, 500); + + setTimeout(function () { + t.ok(true, 'no errors'); + t.end(); + process.exit(0); + }, 5000); + }); });