diff --git a/spikes/graphs-fe/rickshaw/.babelrc b/spikes/graphs-fe/rickshaw/.babelrc
new file mode 100644
index 00000000..82cc857a
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/.babelrc
@@ -0,0 +1,15 @@
+{
+ "presets": [
+ "react",
+ "es2015"
+ ],
+ "plugins": [
+ ["transform-object-rest-spread", {
+ "useBuiltIns": true
+ }],
+ "add-module-exports",
+ "transform-es2015-modules-commonjs",
+ "react-hot-loader/babel"
+ ],
+ "sourceMaps": "both"
+}
diff --git a/spikes/graphs-fe/rickshaw/.eslintignore b/spikes/graphs-fe/rickshaw/.eslintignore
new file mode 100644
index 00000000..683e721c
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/.eslintignore
@@ -0,0 +1,3 @@
+/node_modules
+coverage
+.nyc_output
diff --git a/spikes/graphs-fe/rickshaw/.eslintrc b/spikes/graphs-fe/rickshaw/.eslintrc
new file mode 100644
index 00000000..19bd88dc
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/.eslintrc
@@ -0,0 +1,29 @@
+{
+ "extends": "semistandard",
+ "parser": "babel-eslint",
+ "parserOptions": {
+ "ecmaVersion": 7,
+ "ecmaFeatures": {
+ "jsx": true
+ },
+ "sourceType": "module"
+ },
+ "plugins": [
+ "babel",
+ "react"
+ ],
+ "rules": {
+ "generator-star-spacing": 0,
+ "babel/generator-star-spacing": 1,
+ "space-before-function-paren": [2, "never"],
+ "react/jsx-uses-react": 2,
+ "react/jsx-uses-vars": 2,
+ "react/react-in-jsx-scope": 2,
+ "object-curly-newline": ["error", {
+ "minProperties": 1
+ }],
+ "sort-vars": ["error", {
+ "ignoreCase": true
+ }]
+ }
+}
\ No newline at end of file
diff --git a/spikes/graphs-fe/rickshaw/.gitignore b/spikes/graphs-fe/rickshaw/.gitignore
new file mode 100644
index 00000000..c21b0ba0
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/.gitignore
@@ -0,0 +1,4 @@
+/node_modules
+coverage
+.nyc_output
+npm-debug.log
diff --git a/spikes/graphs-fe/rickshaw/client/chart.js b/spikes/graphs-fe/rickshaw/client/chart.js
new file mode 100644
index 00000000..e6d0f0c0
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/client/chart.js
@@ -0,0 +1,77 @@
+const Rickshaw = require('rickshaw');
+const ReactRedux = require('react-redux');
+const React = require('react');
+const style = require('./style.css');
+
+const {
+ connect
+} = ReactRedux;
+
+const {
+ Graph
+} = Rickshaw;
+
+let i = 0;
+
+const Component = React.createClass({
+ ref: function(name) {
+ this._refs = this._refs || {};
+
+ return (el) => {
+ this._refs[name] = el;
+ };
+ },
+ fromData: function(data) {
+ return (data || []).map((d, i) => {
+ return {
+ y: d.cpu,
+ x: i
+ };
+ });
+ },
+ componentDidMount: function() {
+ this._chart = new Graph({
+ element: this._refs.component,
+ renderer: 'bar',
+ width: 500,
+ height: 200,
+ series: [{
+ data: this.fromData(this.props.data)
+ }]
+ });
+
+ this._chart.render();
+ },
+ componentWillReceiveProps: function(nextProps) {
+ this._chart.series[0].data = this.fromData(this.props.data);
+ this._chart.update();
+ },
+ render: function() {
+ const {
+ data = []
+ } = this.props;
+
+ const className = (data.length && data[data.length - 1].cpu > 50)
+ ? style.red
+ : style.blue;
+
+ return (
+
+ );
+ }
+});
+
+const mapStateToProps = ({
+ data
+}) => {
+ return {
+ data
+ };
+};
+
+module.exports = connect(
+ mapStateToProps
+)(Component);
diff --git a/spikes/graphs-fe/rickshaw/client/index.js b/spikes/graphs-fe/rickshaw/client/index.js
new file mode 100644
index 00000000..e4830cde
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/client/index.js
@@ -0,0 +1,46 @@
+const ReactDOM = require('react-dom');
+const React = require('react');
+const store = require('./store')();
+const nes = require('nes/dist/client');
+
+const {
+ Client
+} = nes;
+
+const client = new Client(`ws://${document.location.host}`);
+
+client.connect((err) => {
+ if (err) {
+ throw err;
+ }
+
+ console.log('connected');
+
+ client.subscribe('/stats/5', (update, flag) => {
+ store.dispatch({
+ type: 'UPDATE_STATS',
+ payload: update
+ })
+ }, (err) => {
+ if (err) {
+ throw err;
+ }
+
+ console.log('subscribed');
+ });
+});
+
+const render = () => {
+ const Root = require('./root');
+
+ ReactDOM.render(
+ ,
+ document.getElementById('root')
+ );
+};
+
+render();
+
+if (module.hot) {
+ module.hot.accept('./root', render);
+}
diff --git a/spikes/graphs-fe/rickshaw/client/root.js b/spikes/graphs-fe/rickshaw/client/root.js
new file mode 100644
index 00000000..a584d4f2
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/client/root.js
@@ -0,0 +1,25 @@
+const React = require('react');
+const ReactHotLoader = require('react-hot-loader');
+const ReactRedux = require('react-redux');
+const Chart = require('./chart');
+
+
+const {
+ AppContainer
+} = ReactHotLoader;
+
+const {
+ Provider
+} = ReactRedux;
+
+module.exports = ({
+ store
+}) => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/spikes/graphs-fe/rickshaw/client/store.js b/spikes/graphs-fe/rickshaw/client/store.js
new file mode 100644
index 00000000..b0e8763d
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/client/store.js
@@ -0,0 +1,25 @@
+const takeRight = require('lodash.takeright');
+const redux = require('redux');
+
+const {
+ createStore,
+ compose,
+ applyMiddleware
+} = redux;
+
+const reducer = (state, action) => {
+ if (action.type !== 'UPDATE_STATS') {
+ return state;
+ }
+
+ const data = (state.data || []).concat([action.payload]);
+
+ return {
+ ...state,
+ data: takeRight(data, 50)
+ };
+};
+
+module.exports = (state = Object.freeze({})) => {
+ return createStore(reducer, state);
+};
diff --git a/spikes/graphs-fe/rickshaw/client/style.css b/spikes/graphs-fe/rickshaw/client/style.css
new file mode 100644
index 00000000..a5373319
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/client/style.css
@@ -0,0 +1,9 @@
+.rickshaw_graph.red rect {
+ fill: red;
+ border-top: 1px brown solid;
+}
+
+.rickshaw_graph.blue rect {
+ fill: blue;
+ border-top: 1px darkblue solid;
+}
diff --git a/spikes/graphs-fe/rickshaw/package.json b/spikes/graphs-fe/rickshaw/package.json
new file mode 100644
index 00000000..810dce72
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/package.json
@@ -0,0 +1,57 @@
+{
+ "name": "rickshaw-graphing-spike",
+ "private": true,
+ "license": "private",
+ "main": "server/index.js",
+ "dependencies": {
+ "autoprefixer": "^6.5.1",
+ "babel-eslint": "^7.0.0",
+ "babel-loader": "^6.2.5",
+ "babel-plugin-add-module-exports": "^0.2.1",
+ "babel-plugin-transform-es2015-modules-commonjs": "^6.16.0",
+ "babel-plugin-transform-object-rest-spread": "^6.16.0",
+ "babel-plugin-transform-runtime": "^6.15.0",
+ "babel-preset-es2015": "^6.16.0",
+ "babel-preset-react": "^6.16.0",
+ "babel-preset-react-hmre": "^1.1.1",
+ "babel-runtime": "^6.11.6",
+ "build-array": "^1.0.0",
+ "component-emitter": "^1.2.1",
+ "css-loader": "^0.25.0",
+ "hapi": "^15.2.0",
+ "hapi-webpack-dev-plugin": "^1.1.4",
+ "inert": "^4.0.2",
+ "lodash.takeright": "^4.1.1",
+ "nes": "^6.3.1",
+ "postcss-loader": "^1.0.0",
+ "postcss-modules-values": "^1.2.2",
+ "postcss-nested": "^1.0.0",
+ "react": "^15.3.2",
+ "react-dom": "^15.3.2",
+ "react-hot-loader": "^3.0.0-beta.6",
+ "react-redux": "^4.4.5",
+ "redux": "^3.6.0",
+ "require-dir": "^0.3.1",
+ "rickshaw": "^1.6.0",
+ "style-loader": "^0.13.1",
+ "webpack": "^1.13.2",
+ "webpack-dev-server": "^1.16.2"
+ },
+ "devDependencies": {
+ "babel-register": "^6.16.3",
+ "eslint": "^3.8.1",
+ "eslint-config-semistandard": "^7.0.0",
+ "eslint-config-standard": "^6.2.0",
+ "eslint-plugin-babel": "^3.3.0",
+ "eslint-plugin-promise": "^3.3.0",
+ "eslint-plugin-react": "^6.4.1",
+ "eslint-plugin-standard": "^2.0.1",
+ "json-loader": "^0.5.4"
+ },
+ "ava": {
+ "require": [
+ "babel-register"
+ ],
+ "babel": "inherit"
+ }
+}
diff --git a/spikes/graphs-fe/rickshaw/readme.md b/spikes/graphs-fe/rickshaw/readme.md
new file mode 100644
index 00000000..1de00e6b
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/readme.md
@@ -0,0 +1,8 @@
+# rickshaw
+
+![](http://g.recordit.co/4fybFRWBl4.gif)
+
+## summary
+
+ - [x] fast (handles 100ms updates well, or even 16ms)
+ - [x] can be styled with css (it's just an svg with 's)
diff --git a/spikes/graphs-fe/rickshaw/server/index.js b/spikes/graphs-fe/rickshaw/server/index.js
new file mode 100644
index 00000000..b2a6a529
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/server/index.js
@@ -0,0 +1,29 @@
+const requireDir = require('require-dir');
+const plugins = require('./plugins');
+const routes = requireDir('./routes');
+const Hapi = require('hapi');
+const path = require('path');
+const fs = require('fs');
+
+const server = new Hapi.Server();
+
+server.connection({
+ host: 'localhost',
+ port: 8000
+});
+
+server.register(plugins, (err) => {
+ if (err) {
+ throw err;
+ }
+
+ Object.keys(routes).forEach((name) => {
+ routes[name](server);
+ });
+
+ server.start((err) => {
+ server.connections.forEach((conn) => {
+ console.log(`started at: ${conn.info.uri}`);
+ });
+ });
+});
diff --git a/spikes/graphs-fe/rickshaw/server/metric.js b/spikes/graphs-fe/rickshaw/server/metric.js
new file mode 100644
index 00000000..24015f70
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/server/metric.js
@@ -0,0 +1,34 @@
+const Emitter = require('component-emitter');
+
+const cdm = {};
+
+module.exports = (server) => ({
+ on: (id) => {
+ console.log('on', cdm[id]);
+ if (cdm[id] && (cdm[id].sockets > 0)) {
+ cdm[id].sockets +=1;
+ return;
+ }
+
+
+ let messageId = 0;
+ const interval = setInterval(() => {
+ console.log(`publishing /stats/${id}`);
+
+ server.publish(`/stats/${id}`, {
+ when: new Date().getTime(),
+ cpu: Math.random() * 100
+ });
+ }, 100);
+
+ cdm[id] = {
+ interval,
+ sockets: 1
+ };
+ },
+ off: (id) => {
+ if (!(cdm[id].sockets -= 1)) {
+ clearInterval(cdm[id].interval);
+ }
+ }
+});
diff --git a/spikes/graphs-fe/rickshaw/server/plugins.js b/spikes/graphs-fe/rickshaw/server/plugins.js
new file mode 100644
index 00000000..84944329
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/server/plugins.js
@@ -0,0 +1,15 @@
+const webpack = require('webpack');
+const path = require('path');
+
+const cfg = require('../webpack.config.js');
+
+module.exports = [
+ require('inert'),
+ require('nes'), {
+ register: require('hapi-webpack-dev-plugin'),
+ options: {
+ compiler: webpack(cfg),
+ devIndex: path.join(__dirname, '../static')
+ }
+ }
+];
diff --git a/spikes/graphs-fe/rickshaw/server/routes/home.js b/spikes/graphs-fe/rickshaw/server/routes/home.js
new file mode 100644
index 00000000..48ead969
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/server/routes/home.js
@@ -0,0 +1,11 @@
+const path = require('path');
+
+module.exports = (server) => {
+ server.route({
+ method: 'GET',
+ path: '/',
+ handler: (request, reply) => {
+ reply.file(path.join(__dirname, '../../static/index.html'));
+ }
+ });
+};
diff --git a/spikes/graphs-fe/rickshaw/server/routes/metrics.js b/spikes/graphs-fe/rickshaw/server/routes/metrics.js
new file mode 100644
index 00000000..e6e1c261
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/server/routes/metrics.js
@@ -0,0 +1,18 @@
+const Metric = require('../metric');
+
+module.exports = (server) => {
+ const metric = Metric(server);
+
+ server.subscription('/stats/{id}', {
+ onSubscribe: (socket, path, params, next) => {
+ console.log('onSubscribe');
+ metric.on(params.id);
+ next();
+ },
+ onUnsubscribe: (socket, path, params, next) => {
+ console.log('onUnsubscribe');
+ metric.off(params.id);
+ next();
+ }
+ });
+};
diff --git a/spikes/graphs-fe/rickshaw/server/routes/static.js b/spikes/graphs-fe/rickshaw/server/routes/static.js
new file mode 100644
index 00000000..5a41f602
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/server/routes/static.js
@@ -0,0 +1,15 @@
+const path = require('path');
+
+module.exports = (server) => {
+ // server.route({
+ // method: 'GET',
+ // path: '/{param*}',
+ // handler: {
+ // directory: {
+ // path: path.join(__dirname, '../../static'),
+ // redirectToSlash: true,
+ // index: true
+ // }
+ // }
+ // });
+};
diff --git a/spikes/graphs-fe/rickshaw/server/routes/version.js b/spikes/graphs-fe/rickshaw/server/routes/version.js
new file mode 100644
index 00000000..987747cb
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/server/routes/version.js
@@ -0,0 +1,18 @@
+const Pkg = require('../../package.json');
+
+const internals = {
+ response: {
+ version: Pkg.version
+ }
+};
+
+module.exports = (server) => {
+ server.route({
+ method: 'GET',
+ path: '/ops/version',
+ config: {
+ description: 'Returns the version of the server',
+ handler: (request, reply) => reply(internals.response)
+ }
+ });
+};
diff --git a/spikes/graphs-fe/rickshaw/static/index.html b/spikes/graphs-fe/rickshaw/static/index.html
new file mode 100644
index 00000000..5ef85ffd
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/static/index.html
@@ -0,0 +1,22 @@
+
+
+
+ React Boilerplate
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spikes/graphs-fe/rickshaw/webpack.config.js b/spikes/graphs-fe/rickshaw/webpack.config.js
new file mode 100644
index 00000000..7295526a
--- /dev/null
+++ b/spikes/graphs-fe/rickshaw/webpack.config.js
@@ -0,0 +1,69 @@
+const webpack = require('webpack');
+const path = require('path');
+
+const config = {
+ debug: true,
+ devtool: 'source-map',
+ context: path.join(__dirname, './client'),
+ app: path.join(__dirname, './client/index.js'),
+ entry: [
+ 'webpack-dev-server/client?http://localhost:8080',
+ 'webpack/hot/only-dev-server',
+ 'react-hot-loader/patch',
+ './index.js'
+ ],
+ output: {
+ path: path.join(__dirname, './static'),
+ publicPath: '/static/',
+ filename: 'bundle.js'
+ },
+ plugins: [
+ new webpack.HotModuleReplacementPlugin(),
+ new webpack.NoErrorsPlugin()
+ ],
+ postcss: () => {
+ return [
+ require('postcss-modules-values'),
+ require('postcss-nested'),
+ require('autoprefixer')
+ ];
+ },
+ module: {
+ loaders: [{
+ test: /js?$/,
+ exclude: /node_modules/,
+ include: [
+ path.join(__dirname, './client')
+ ],
+ loaders: ['babel']
+ }, {
+ test: /\.json?$/,
+ exclude: /node_modules/,
+ include: [
+ path.join(__dirname, './client')
+ ],
+ loaders: ['json']
+ }, {
+ test: /\.css$/,
+ exclude: /node_modules/,
+ include: [
+ path.join(__dirname, './client')
+ ],
+ loader: 'style-loader!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader'
+ }]
+ }
+};
+
+const devServer = {
+ hot: true,
+ compress: true,
+ lazy: false,
+ publicPath: config.output.publicPath,
+ historyApiFallback: {
+ index: './static/index.html'
+ }
+};
+
+module.exports = Object.assign({
+ devServer
+}, config);