diff --git a/spikes/graphs-fe/c3js/.babelrc b/spikes/graphs-fe/c3js/.babelrc
new file mode 100644
index 00000000..82cc857a
--- /dev/null
+++ b/spikes/graphs-fe/c3js/.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/c3js/.eslintignore b/spikes/graphs-fe/c3js/.eslintignore
new file mode 100644
index 00000000..683e721c
--- /dev/null
+++ b/spikes/graphs-fe/c3js/.eslintignore
@@ -0,0 +1,3 @@
+/node_modules
+coverage
+.nyc_output
diff --git a/spikes/graphs-fe/c3js/.eslintrc b/spikes/graphs-fe/c3js/.eslintrc
new file mode 100644
index 00000000..19bd88dc
--- /dev/null
+++ b/spikes/graphs-fe/c3js/.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/c3js/.gitignore b/spikes/graphs-fe/c3js/.gitignore
new file mode 100644
index 00000000..c21b0ba0
--- /dev/null
+++ b/spikes/graphs-fe/c3js/.gitignore
@@ -0,0 +1,4 @@
+/node_modules
+coverage
+.nyc_output
+npm-debug.log
diff --git a/spikes/graphs-fe/c3js/client/c3js.js b/spikes/graphs-fe/c3js/client/c3js.js
new file mode 100644
index 00000000..13560860
--- /dev/null
+++ b/spikes/graphs-fe/c3js/client/c3js.js
@@ -0,0 +1,43 @@
+const C3Graph = require('react-c3js').default;
+const ReactRedux = require('react-redux');
+const React = require('react');
+
+const {
+ connect
+} = ReactRedux;
+
+const Graph = React.createClass({
+ render: function() {
+ const {
+ data = []
+ } = this.props;
+
+ const cpu = data.map((d) => Math.floor(d.cpu));
+ const datatime = data.map((d, i) => i);
+
+ const formattedData = {
+ x: 'x',
+ columns: [
+ ['x'].concat(datatime),
+ ['cpu'].concat(cpu)
+ ]
+ };
+
+ return (
+
+ );
+ }
+});
+
+const mapStateToProps = ({
+ data
+}) => {
+ return {
+ data
+ };
+};
+
+module.exports = connect(mapStateToProps)(Graph);
+
diff --git a/spikes/graphs-fe/c3js/client/index.js b/spikes/graphs-fe/c3js/client/index.js
new file mode 100644
index 00000000..e4830cde
--- /dev/null
+++ b/spikes/graphs-fe/c3js/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/c3js/client/root.js b/spikes/graphs-fe/c3js/client/root.js
new file mode 100644
index 00000000..d1b87698
--- /dev/null
+++ b/spikes/graphs-fe/c3js/client/root.js
@@ -0,0 +1,24 @@
+const React = require('react');
+const ReactHotLoader = require('react-hot-loader');
+const ReactRedux = require('react-redux');
+const Graph = require('./c3js');
+
+const {
+ AppContainer
+} = ReactHotLoader;
+
+const {
+ Provider
+} = ReactRedux;
+
+module.exports = ({
+ store
+}) => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/spikes/graphs-fe/c3js/client/store.js b/spikes/graphs-fe/c3js/client/store.js
new file mode 100644
index 00000000..b0e8763d
--- /dev/null
+++ b/spikes/graphs-fe/c3js/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/c3js/package.json b/spikes/graphs-fe/c3js/package.json
new file mode 100644
index 00000000..037e35ad
--- /dev/null
+++ b/spikes/graphs-fe/c3js/package.json
@@ -0,0 +1,57 @@
+{
+ "name": "redux-thunks-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",
+ "c3": "^0.4.11",
+ "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-c3js": "^0.1.9",
+ "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",
+ "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/c3js/readme.md b/spikes/graphs-fe/c3js/readme.md
new file mode 100644
index 00000000..cd8d1d19
--- /dev/null
+++ b/spikes/graphs-fe/c3js/readme.md
@@ -0,0 +1,6 @@
+# Graphing Spikes
+
+## Plotly
+
+Adding plotly through npm and created a plotly graph components
+Running three different graph types of the same data on the same page
diff --git a/spikes/graphs-fe/c3js/server/index.js b/spikes/graphs-fe/c3js/server/index.js
new file mode 100644
index 00000000..b2a6a529
--- /dev/null
+++ b/spikes/graphs-fe/c3js/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/c3js/server/metric.js b/spikes/graphs-fe/c3js/server/metric.js
new file mode 100644
index 00000000..8218d167
--- /dev/null
+++ b/spikes/graphs-fe/c3js/server/metric.js
@@ -0,0 +1,35 @@
+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
+ });
+ }, 200);
+
+ cdm[id] = {
+ interval,
+ sockets: 1
+ };
+ },
+ off: (id) => {
+ if (!(cdm[id].sockets -= 1)) {
+ clearInterval(cdm[id].interval);
+ }
+ }
+});
+
diff --git a/spikes/graphs-fe/c3js/server/plugins.js b/spikes/graphs-fe/c3js/server/plugins.js
new file mode 100644
index 00000000..84944329
--- /dev/null
+++ b/spikes/graphs-fe/c3js/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/c3js/server/routes/home.js b/spikes/graphs-fe/c3js/server/routes/home.js
new file mode 100644
index 00000000..48ead969
--- /dev/null
+++ b/spikes/graphs-fe/c3js/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/c3js/server/routes/metrics.js b/spikes/graphs-fe/c3js/server/routes/metrics.js
new file mode 100644
index 00000000..e6e1c261
--- /dev/null
+++ b/spikes/graphs-fe/c3js/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/c3js/server/routes/static.js b/spikes/graphs-fe/c3js/server/routes/static.js
new file mode 100644
index 00000000..5a41f602
--- /dev/null
+++ b/spikes/graphs-fe/c3js/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/c3js/server/routes/version.js b/spikes/graphs-fe/c3js/server/routes/version.js
new file mode 100644
index 00000000..987747cb
--- /dev/null
+++ b/spikes/graphs-fe/c3js/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/c3js/static/index.html b/spikes/graphs-fe/c3js/static/index.html
new file mode 100644
index 00000000..44de5a31
--- /dev/null
+++ b/spikes/graphs-fe/c3js/static/index.html
@@ -0,0 +1,181 @@
+
+
+
+ React Boilerplate
+
+
+
+
+
+
+
+
diff --git a/spikes/graphs-fe/c3js/webpack.config.js b/spikes/graphs-fe/c3js/webpack.config.js
new file mode 100644
index 00000000..7295526a
--- /dev/null
+++ b/spikes/graphs-fe/c3js/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);
diff --git a/spikes/graphs-fe/chartjs/.babelrc b/spikes/graphs-fe/chartjs/.babelrc
new file mode 100644
index 00000000..82cc857a
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/.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/chartjs/.eslintignore b/spikes/graphs-fe/chartjs/.eslintignore
new file mode 100644
index 00000000..683e721c
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/.eslintignore
@@ -0,0 +1,3 @@
+/node_modules
+coverage
+.nyc_output
diff --git a/spikes/graphs-fe/chartjs/.eslintrc b/spikes/graphs-fe/chartjs/.eslintrc
new file mode 100644
index 00000000..19bd88dc
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/.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/chartjs/.gitignore b/spikes/graphs-fe/chartjs/.gitignore
new file mode 100644
index 00000000..c21b0ba0
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/.gitignore
@@ -0,0 +1,4 @@
+/node_modules
+coverage
+.nyc_output
+npm-debug.log
diff --git a/spikes/graphs-fe/chartjs/client/chart.js b/spikes/graphs-fe/chartjs/client/chart.js
new file mode 100644
index 00000000..fd2e5dbe
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/client/chart.js
@@ -0,0 +1,65 @@
+const buildArray = require('build-array');
+const Chart = require('chart.js');
+const ReactRedux = require('react-redux');
+const React = require('react');
+
+const {
+ connect
+} = ReactRedux;
+
+const Component = React.createClass({
+ ref: function(name) {
+ this._refs = this._refs || {};
+
+ return (el) => {
+ this._refs[name] = el;
+ };
+ },
+ fromData: function(data) {
+ return (data || []).map((d) => {
+ return d.cpu;
+ });
+ },
+ componentDidMount: function() {
+ const bars = this.fromData(this.props.data);
+
+ this._chart = new Chart(this._refs.component, {
+ type: 'bar',
+ data: {
+ labels: buildArray(bars.length).map((v, i) => ''),
+ datasets: [{
+ data: bars
+ }]
+ }
+ });
+ },
+ componentWillReceiveProps: function(nextProps) {
+ const bars = this.fromData(this.props.data);
+
+ this._chart.data.labels = buildArray(bars.length).map((v, i) => '');
+ this._chart.data.datasets[0].data = bars;
+
+ this._chart.update(0);
+ },
+ render: function() {
+ return (
+
+ );
+ }
+});
+
+const mapStateToProps = ({
+ data
+}) => {
+ return {
+ data
+ };
+};
+
+module.exports = connect(
+ mapStateToProps
+)(Component);
diff --git a/spikes/graphs-fe/chartjs/client/index.js b/spikes/graphs-fe/chartjs/client/index.js
new file mode 100644
index 00000000..e4830cde
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/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/chartjs/client/root.js b/spikes/graphs-fe/chartjs/client/root.js
new file mode 100644
index 00000000..a584d4f2
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/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/chartjs/client/store.js b/spikes/graphs-fe/chartjs/client/store.js
new file mode 100644
index 00000000..b0e8763d
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/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/chartjs/package.json b/spikes/graphs-fe/chartjs/package.json
new file mode 100644
index 00000000..14747cc8
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/package.json
@@ -0,0 +1,57 @@
+{
+ "name": "chartjs-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",
+ "chart.js": "^2.3.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",
+ "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/chartjs/readme.md b/spikes/graphs-fe/chartjs/readme.md
new file mode 100644
index 00000000..fe0718a4
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/readme.md
@@ -0,0 +1,9 @@
+# ChartJS
+
+![](http://g.recordit.co/N8vdP8DBk4.gif)
+
+## summary
+
+ - [x] customisable via js
+ - [x] fast (handles 100ms updates well)
+ - [x] easy to update data
diff --git a/spikes/graphs-fe/chartjs/server/index.js b/spikes/graphs-fe/chartjs/server/index.js
new file mode 100644
index 00000000..b2a6a529
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/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/chartjs/server/metric.js b/spikes/graphs-fe/chartjs/server/metric.js
new file mode 100644
index 00000000..0d64fad2
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/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
+ });
+ }, 400);
+
+ cdm[id] = {
+ interval,
+ sockets: 1
+ };
+ },
+ off: (id) => {
+ if (!(cdm[id].sockets -= 1)) {
+ clearInterval(cdm[id].interval);
+ }
+ }
+});
diff --git a/spikes/graphs-fe/chartjs/server/plugins.js b/spikes/graphs-fe/chartjs/server/plugins.js
new file mode 100644
index 00000000..84944329
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/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/chartjs/server/routes/home.js b/spikes/graphs-fe/chartjs/server/routes/home.js
new file mode 100644
index 00000000..48ead969
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/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/chartjs/server/routes/metrics.js b/spikes/graphs-fe/chartjs/server/routes/metrics.js
new file mode 100644
index 00000000..e6e1c261
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/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/chartjs/server/routes/static.js b/spikes/graphs-fe/chartjs/server/routes/static.js
new file mode 100644
index 00000000..5a41f602
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/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/chartjs/server/routes/version.js b/spikes/graphs-fe/chartjs/server/routes/version.js
new file mode 100644
index 00000000..987747cb
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/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/chartjs/static/index.html b/spikes/graphs-fe/chartjs/static/index.html
new file mode 100644
index 00000000..86a0d914
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/static/index.html
@@ -0,0 +1,12 @@
+
+
+
+ React Boilerplate
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spikes/graphs-fe/chartjs/webpack.config.js b/spikes/graphs-fe/chartjs/webpack.config.js
new file mode 100644
index 00000000..7295526a
--- /dev/null
+++ b/spikes/graphs-fe/chartjs/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);
diff --git a/spikes/graphs-fe/epoch/.babelrc b/spikes/graphs-fe/epoch/.babelrc
new file mode 100644
index 00000000..82cc857a
--- /dev/null
+++ b/spikes/graphs-fe/epoch/.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/epoch/.eslintignore b/spikes/graphs-fe/epoch/.eslintignore
new file mode 100644
index 00000000..683e721c
--- /dev/null
+++ b/spikes/graphs-fe/epoch/.eslintignore
@@ -0,0 +1,3 @@
+/node_modules
+coverage
+.nyc_output
diff --git a/spikes/graphs-fe/epoch/.eslintrc b/spikes/graphs-fe/epoch/.eslintrc
new file mode 100644
index 00000000..19bd88dc
--- /dev/null
+++ b/spikes/graphs-fe/epoch/.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/epoch/.gitignore b/spikes/graphs-fe/epoch/.gitignore
new file mode 100644
index 00000000..c21b0ba0
--- /dev/null
+++ b/spikes/graphs-fe/epoch/.gitignore
@@ -0,0 +1,4 @@
+/node_modules
+coverage
+.nyc_output
+npm-debug.log
diff --git a/spikes/graphs-fe/epoch/client/epoch.js b/spikes/graphs-fe/epoch/client/epoch.js
new file mode 100644
index 00000000..f17ba7b0
--- /dev/null
+++ b/spikes/graphs-fe/epoch/client/epoch.js
@@ -0,0 +1,212 @@
+// injects into `window` (ikr)
+require('epoch-charting');
+
+const ReactRedux = require('react-redux');
+const React = require('react');
+
+const {
+ // Chart: {
+ // Bar
+ // }
+ Time: {
+ Bar
+ }
+} = window.Epoch;
+
+const {
+ connect
+} = ReactRedux;
+
+const style = {
+ height: '220px'
+};
+
+const EpochGraph = React.createClass({
+ ref: function(name) {
+ this._refs = this._refs || {};
+
+ return (el) => {
+ this._refs[name] = el;
+ };
+ },
+ fromData: function(data) {
+ return (data || []).map((d) => {
+ return {
+ y: d.cpu,
+ time: d.when
+ };
+ });
+ },
+ componentDidMount: function() {
+ this.chart = new Bar({
+ el: this._refs.component,
+ type: 'time.bar',
+ data: [{
+ label: 'A',
+ values: []
+ }]
+ });
+
+
+ // {
+ // time: 1478605670,
+ // y: 2
+ // }, {
+ // time: 1478605671,
+ // y: 1.9876883405951378
+ // }, {
+ // time: 1478605672,
+ // y: 1.9510565162951536
+ // }, {
+ // time: 1478605673,
+ // y: 1.8910065241883678
+ // }, {
+ // time: 1478605674,
+ // y: 1.8090169943749475
+ // }, {
+ // time: 1478605675,
+ // y: 1.7071067811865475
+ // }, {
+ // time: 1478605676,
+ // y: 1.5877852522924731
+ // }, {
+ // time: 1478605677,
+ // y: 1.4539904997395467
+ // }, {
+ // time: 1478605678,
+ // y: 1.3090169943749475
+ // }, {
+ // time: 1478605679,
+ // y: 1.156434465040231
+ // }, {
+ // time: 1478605680,
+ // y: 1
+ // }, {
+ // time: 1478605681,
+ // y: 0.8435655349597694
+ // }, {
+ // time: 1478605682,
+ // y: 0.6909830056250527
+ // }, {
+ // time: 1478605683,
+ // y: 0.5460095002604533
+ // }, {
+ // time: 1478605684,
+ // y: 0.412214747707527
+ // }, {
+ // time: 1478605685,
+ // y: 0.29289321881345254
+ // }, {
+ // time: 1478605686,
+ // y: 0.19098300562505266
+ // }, {
+ // time: 1478605687,
+ // y: 0.10899347581163221
+ // }, {
+ // time: 1478605688,
+ // y: 0.04894348370484647
+ // }, {
+ // time: 1478605689,
+ // y: 0.01231165940486234
+ // }, {
+ // time: 1478605690,
+ // y: 0
+ // }, {
+ // time: 1478605691,
+ // y: 0.01231165940486223
+ // }, {
+ // time: 1478605692,
+ // y: 0.04894348370484625
+ // }, {
+ // time: 1478605693,
+ // y: 0.1089934758116321
+ // }, {
+ // time: 1478605694,
+ // y: 0.19098300562505255
+ // }, {
+ // time: 1478605695,
+ // y: 0.2928932188134523
+ // }, {
+ // time: 1478605696,
+ // y: 0.41221474770752675
+ // }, {
+ // time: 1478605697,
+ // y: 0.546009500260453
+ // }, {
+ // time: 1478605698,
+ // y: 0.6909830056250524
+ // }, {
+ // time: 1478605699,
+ // y: 0.8435655349597689
+ // }, {
+ // time: 1478605700,
+ // y: 0.9999999999999998
+ // }, {
+ // time: 1478605701,
+ // y: 1.1564344650402307
+ // }, {
+ // time: 1478605702,
+ // y: 1.3090169943749472
+ // }, {
+ // time: 1478605703,
+ // y: 1.4539904997395467
+ // }, {
+ // time: 1478605704,
+ // y: 1.587785252292473
+ // }, {
+ // time: 1478605705,
+ // y: 1.7071067811865475
+ // }, {
+ // time: 1478605706,
+ // y: 1.8090169943749475
+ // }, {
+ // time: 1478605707,
+ // y: 1.8910065241883678
+ // }, {
+ // time: 1478605708,
+ // y: 1.9510565162951536
+ // }, {
+ // time: 1478605709,
+ // y: 1.9876883405951378
+ // }
+
+
+ // this.chart = new Bar({
+ // el: this._refs.component,
+ // data: [{
+ // values: [{
+ // x: 'A',
+ // y: 20
+ // }, {
+ // x: 'B',
+ // y: 39
+ // }, {
+ // x: 'C',
+ // y: 8
+ // }, ]
+ // }]
+ // });
+ },
+ componentWillReceiveProps: function(nextProps) {
+ this.fromData(this.props.data).forEach((r) => this.chart.push([r]));
+ },
+ render: function() {
+ return (
+
+ );
+ }
+});
+
+const mapStateToProps = ({
+ data
+}) => {
+ return {
+ data
+ };
+};
+
+module.exports = connect(mapStateToProps)(EpochGraph);
diff --git a/spikes/graphs-fe/epoch/client/index.js b/spikes/graphs-fe/epoch/client/index.js
new file mode 100644
index 00000000..e4830cde
--- /dev/null
+++ b/spikes/graphs-fe/epoch/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/epoch/client/root.js b/spikes/graphs-fe/epoch/client/root.js
new file mode 100644
index 00000000..2a95f427
--- /dev/null
+++ b/spikes/graphs-fe/epoch/client/root.js
@@ -0,0 +1,25 @@
+const React = require('react');
+const ReactHotLoader = require('react-hot-loader');
+const ReactRedux = require('react-redux');
+const EpochGraph = require('./epoch');
+
+
+const {
+ AppContainer
+} = ReactHotLoader;
+
+const {
+ Provider
+} = ReactRedux;
+
+module.exports = ({
+ store
+}) => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/spikes/graphs-fe/epoch/client/store.js b/spikes/graphs-fe/epoch/client/store.js
new file mode 100644
index 00000000..b0e8763d
--- /dev/null
+++ b/spikes/graphs-fe/epoch/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/epoch/package.json b/spikes/graphs-fe/epoch/package.json
new file mode 100644
index 00000000..333189a8
--- /dev/null
+++ b/spikes/graphs-fe/epoch/package.json
@@ -0,0 +1,57 @@
+{
+ "name": "epoch-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",
+ "component-emitter": "^1.2.1",
+ "css-loader": "^0.25.0",
+ "d3": "^4.3.0",
+ "epoch-charting": "^0.8.4",
+ "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",
+ "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/epoch/readme.md b/spikes/graphs-fe/epoch/readme.md
new file mode 100644
index 00000000..8b475ec5
--- /dev/null
+++ b/spikes/graphs-fe/epoch/readme.md
@@ -0,0 +1,6 @@
+# Epoch
+
+![](http://g.recordit.co/4LVH7PlAJP.gif)
+
+## summary
+
diff --git a/spikes/graphs-fe/epoch/server/index.js b/spikes/graphs-fe/epoch/server/index.js
new file mode 100644
index 00000000..b2a6a529
--- /dev/null
+++ b/spikes/graphs-fe/epoch/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/epoch/server/metric.js b/spikes/graphs-fe/epoch/server/metric.js
new file mode 100644
index 00000000..0d64fad2
--- /dev/null
+++ b/spikes/graphs-fe/epoch/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
+ });
+ }, 400);
+
+ cdm[id] = {
+ interval,
+ sockets: 1
+ };
+ },
+ off: (id) => {
+ if (!(cdm[id].sockets -= 1)) {
+ clearInterval(cdm[id].interval);
+ }
+ }
+});
diff --git a/spikes/graphs-fe/epoch/server/plugins.js b/spikes/graphs-fe/epoch/server/plugins.js
new file mode 100644
index 00000000..84944329
--- /dev/null
+++ b/spikes/graphs-fe/epoch/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/epoch/server/routes/home.js b/spikes/graphs-fe/epoch/server/routes/home.js
new file mode 100644
index 00000000..48ead969
--- /dev/null
+++ b/spikes/graphs-fe/epoch/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/epoch/server/routes/metrics.js b/spikes/graphs-fe/epoch/server/routes/metrics.js
new file mode 100644
index 00000000..e6e1c261
--- /dev/null
+++ b/spikes/graphs-fe/epoch/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/epoch/server/routes/static.js b/spikes/graphs-fe/epoch/server/routes/static.js
new file mode 100644
index 00000000..5a41f602
--- /dev/null
+++ b/spikes/graphs-fe/epoch/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/epoch/server/routes/version.js b/spikes/graphs-fe/epoch/server/routes/version.js
new file mode 100644
index 00000000..987747cb
--- /dev/null
+++ b/spikes/graphs-fe/epoch/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/epoch/static/index.html b/spikes/graphs-fe/epoch/static/index.html
new file mode 100644
index 00000000..86a0d914
--- /dev/null
+++ b/spikes/graphs-fe/epoch/static/index.html
@@ -0,0 +1,12 @@
+
+
+
+ React Boilerplate
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spikes/graphs-fe/epoch/webpack.config.js b/spikes/graphs-fe/epoch/webpack.config.js
new file mode 100644
index 00000000..9b96f745
--- /dev/null
+++ b/spikes/graphs-fe/epoch/webpack.config.js
@@ -0,0 +1,72 @@
+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(),
+ new webpack.ProvidePlugin({
+ 'd3': 'd3'
+ })
+ ],
+ 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);
diff --git a/spikes/graphs-fe/nvd3/.babelrc b/spikes/graphs-fe/nvd3/.babelrc
new file mode 100644
index 00000000..82cc857a
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/.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/nvd3/.eslintignore b/spikes/graphs-fe/nvd3/.eslintignore
new file mode 100644
index 00000000..683e721c
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/.eslintignore
@@ -0,0 +1,3 @@
+/node_modules
+coverage
+.nyc_output
diff --git a/spikes/graphs-fe/nvd3/.eslintrc b/spikes/graphs-fe/nvd3/.eslintrc
new file mode 100644
index 00000000..19bd88dc
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/.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/nvd3/.gitignore b/spikes/graphs-fe/nvd3/.gitignore
new file mode 100644
index 00000000..c21b0ba0
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/.gitignore
@@ -0,0 +1,4 @@
+/node_modules
+coverage
+.nyc_output
+npm-debug.log
diff --git a/spikes/graphs-fe/nvd3/client/chart.js b/spikes/graphs-fe/nvd3/client/chart.js
new file mode 100644
index 00000000..0cbc34b8
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/client/chart.js
@@ -0,0 +1,58 @@
+const NVD3Chart = require('react-nvd3');
+const ReactRedux = require('react-redux');
+const React = require('react');
+
+const {
+ connect
+} = ReactRedux;
+
+const Component = ({
+ data
+}) => {
+ const datum = [{
+ key: 'test',
+ values: (data || []).map((v, i) => ({
+ label: `${i}`,
+ value: v.cpu
+ }))
+ }];
+
+ const context = {
+ getColor: (i) => {
+ if (i.value > 50) {
+ return 'red';
+ }
+
+ return 'green';
+ }
+ };
+
+ const color = {
+ name: 'getColor',
+ type: 'function'
+ };
+
+ return (
+
+ );
+};
+
+const mapStateToProps = ({
+ data
+}) => {
+ return {
+ data
+ };
+};
+
+module.exports = connect(
+ mapStateToProps
+)(Component);
diff --git a/spikes/graphs-fe/nvd3/client/index.js b/spikes/graphs-fe/nvd3/client/index.js
new file mode 100644
index 00000000..e4830cde
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/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/nvd3/client/root.js b/spikes/graphs-fe/nvd3/client/root.js
new file mode 100644
index 00000000..a584d4f2
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/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/nvd3/client/store.js b/spikes/graphs-fe/nvd3/client/store.js
new file mode 100644
index 00000000..b0e8763d
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/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/nvd3/package.json b/spikes/graphs-fe/nvd3/package.json
new file mode 100644
index 00000000..ec9dac85
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/package.json
@@ -0,0 +1,57 @@
+{
+ "name": "nvd3-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-nvd3": "^0.5.7",
+ "react-redux": "^4.4.5",
+ "redux": "^3.6.0",
+ "require-dir": "^0.3.1",
+ "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/nvd3/readme.md b/spikes/graphs-fe/nvd3/readme.md
new file mode 100644
index 00000000..c026614c
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/readme.md
@@ -0,0 +1,9 @@
+# NVD3
+
+![](http://g.recordit.co/BGRyJNZxME.gif)
+
+## summary
+
+ - [x] customisable via js and css
+ - [x] fast (handles 100ms updates well)
+ - [x] simple react integration
diff --git a/spikes/graphs-fe/nvd3/server/index.js b/spikes/graphs-fe/nvd3/server/index.js
new file mode 100644
index 00000000..b2a6a529
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/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/nvd3/server/metric.js b/spikes/graphs-fe/nvd3/server/metric.js
new file mode 100644
index 00000000..24015f70
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/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/nvd3/server/plugins.js b/spikes/graphs-fe/nvd3/server/plugins.js
new file mode 100644
index 00000000..84944329
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/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/nvd3/server/routes/home.js b/spikes/graphs-fe/nvd3/server/routes/home.js
new file mode 100644
index 00000000..48ead969
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/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/nvd3/server/routes/metrics.js b/spikes/graphs-fe/nvd3/server/routes/metrics.js
new file mode 100644
index 00000000..e6e1c261
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/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/nvd3/server/routes/static.js b/spikes/graphs-fe/nvd3/server/routes/static.js
new file mode 100644
index 00000000..5a41f602
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/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/nvd3/server/routes/version.js b/spikes/graphs-fe/nvd3/server/routes/version.js
new file mode 100644
index 00000000..987747cb
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/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/nvd3/static/index.html b/spikes/graphs-fe/nvd3/static/index.html
new file mode 100644
index 00000000..5ef85ffd
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/static/index.html
@@ -0,0 +1,22 @@
+
+
+
+ React Boilerplate
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spikes/graphs-fe/nvd3/webpack.config.js b/spikes/graphs-fe/nvd3/webpack.config.js
new file mode 100644
index 00000000..7295526a
--- /dev/null
+++ b/spikes/graphs-fe/nvd3/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);
diff --git a/spikes/graphs-fe/plotly/.babelrc b/spikes/graphs-fe/plotly/.babelrc
new file mode 100644
index 00000000..82cc857a
--- /dev/null
+++ b/spikes/graphs-fe/plotly/.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/plotly/.eslintignore b/spikes/graphs-fe/plotly/.eslintignore
new file mode 100644
index 00000000..683e721c
--- /dev/null
+++ b/spikes/graphs-fe/plotly/.eslintignore
@@ -0,0 +1,3 @@
+/node_modules
+coverage
+.nyc_output
diff --git a/spikes/graphs-fe/plotly/.eslintrc b/spikes/graphs-fe/plotly/.eslintrc
new file mode 100644
index 00000000..19bd88dc
--- /dev/null
+++ b/spikes/graphs-fe/plotly/.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/plotly/.gitignore b/spikes/graphs-fe/plotly/.gitignore
new file mode 100644
index 00000000..c21b0ba0
--- /dev/null
+++ b/spikes/graphs-fe/plotly/.gitignore
@@ -0,0 +1,4 @@
+/node_modules
+coverage
+.nyc_output
+npm-debug.log
diff --git a/spikes/graphs-fe/plotly/client/index.js b/spikes/graphs-fe/plotly/client/index.js
new file mode 100644
index 00000000..e4830cde
--- /dev/null
+++ b/spikes/graphs-fe/plotly/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/plotly/client/plotly.js b/spikes/graphs-fe/plotly/client/plotly.js
new file mode 100644
index 00000000..5b3a09a9
--- /dev/null
+++ b/spikes/graphs-fe/plotly/client/plotly.js
@@ -0,0 +1,68 @@
+const Plotly = require('react-plotlyjs');
+const ReactRedux = require('react-redux');
+const React = require('react');
+
+const {
+ connect
+} = ReactRedux;
+
+const PlotlyGraph = React.createClass({
+ render: function() {
+ const {
+ data = []
+ } = this.props;
+
+ const cpu = data.map((d) => Math.floor(d.cpu));
+ const datatime = data.map((d, i) => i);
+
+ const graphTypes = [{
+ type: 'bar',
+ marker: {
+ color: 'rgba(205, 54, 54, 0.3)'
+ }
+ }, {
+ type: 'bar',
+ marker: {
+ color: 'rgba(54, 74, 205, 0.3)'
+ }
+ }];
+
+ const graphs = graphTypes.map((graphType, i) => {
+ const data = {
+ type: graphType.type,
+ mode: graphType.mode,
+ marker: graphType.marker,
+ x: datatime,
+ y: cpu
+ };
+
+ const layout = {
+ barmode: graphType.mode
+ };
+
+ return (
+
+ );
+ });
+
+ return (
+
+ {graphs}
+
+ )
+ }
+});
+
+const mapStateToProps = ({
+ data
+}) => {
+ return {
+ data
+ };
+};
+
+module.exports = connect(mapStateToProps)(PlotlyGraph);
diff --git a/spikes/graphs-fe/plotly/client/root.js b/spikes/graphs-fe/plotly/client/root.js
new file mode 100644
index 00000000..886a55db
--- /dev/null
+++ b/spikes/graphs-fe/plotly/client/root.js
@@ -0,0 +1,25 @@
+const React = require('react');
+const ReactHotLoader = require('react-hot-loader');
+const ReactRedux = require('react-redux');
+const PlotlyGraph = require('./plotly');
+
+
+const {
+ AppContainer
+} = ReactHotLoader;
+
+const {
+ Provider
+} = ReactRedux;
+
+module.exports = ({
+ store
+}) => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/spikes/graphs-fe/plotly/client/store.js b/spikes/graphs-fe/plotly/client/store.js
new file mode 100644
index 00000000..b0e8763d
--- /dev/null
+++ b/spikes/graphs-fe/plotly/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/plotly/package.json b/spikes/graphs-fe/plotly/package.json
new file mode 100644
index 00000000..e0f4e16f
--- /dev/null
+++ b/spikes/graphs-fe/plotly/package.json
@@ -0,0 +1,56 @@
+{
+ "name": "redux-thunks-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",
+ "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-plotlyjs": "^0.3.7",
+ "react-redux": "^4.4.5",
+ "redux": "^3.6.0",
+ "require-dir": "^0.3.1",
+ "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/plotly/readme.md b/spikes/graphs-fe/plotly/readme.md
new file mode 100644
index 00000000..da647410
--- /dev/null
+++ b/spikes/graphs-fe/plotly/readme.md
@@ -0,0 +1,17 @@
+# Plotly
+
+![](http://g.recordit.co/FtMgVC8tE3.gif)
+
+Adding plotly through npm and created a plotly graph components
+Running three different graph types of the same data on the same page
+
+## summary
+
+Pros:
+ - [x] simple and complete api for static graphs
+
+Cons:
+ - [ ] very slow on consecutive renders (if they happen in short intervals)
+ - [ ] lacking api for real time graphs
+ - [ ] `restyle` or `recalc` are only useful for style changes
+ - [ ] `extendTraces` API is not documented and not obvious
diff --git a/spikes/graphs-fe/plotly/server/index.js b/spikes/graphs-fe/plotly/server/index.js
new file mode 100644
index 00000000..b2a6a529
--- /dev/null
+++ b/spikes/graphs-fe/plotly/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/plotly/server/metric.js b/spikes/graphs-fe/plotly/server/metric.js
new file mode 100644
index 00000000..0d64fad2
--- /dev/null
+++ b/spikes/graphs-fe/plotly/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
+ });
+ }, 400);
+
+ cdm[id] = {
+ interval,
+ sockets: 1
+ };
+ },
+ off: (id) => {
+ if (!(cdm[id].sockets -= 1)) {
+ clearInterval(cdm[id].interval);
+ }
+ }
+});
diff --git a/spikes/graphs-fe/plotly/server/plugins.js b/spikes/graphs-fe/plotly/server/plugins.js
new file mode 100644
index 00000000..84944329
--- /dev/null
+++ b/spikes/graphs-fe/plotly/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/plotly/server/routes/home.js b/spikes/graphs-fe/plotly/server/routes/home.js
new file mode 100644
index 00000000..48ead969
--- /dev/null
+++ b/spikes/graphs-fe/plotly/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/plotly/server/routes/metrics.js b/spikes/graphs-fe/plotly/server/routes/metrics.js
new file mode 100644
index 00000000..e6e1c261
--- /dev/null
+++ b/spikes/graphs-fe/plotly/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/plotly/server/routes/static.js b/spikes/graphs-fe/plotly/server/routes/static.js
new file mode 100644
index 00000000..5a41f602
--- /dev/null
+++ b/spikes/graphs-fe/plotly/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/plotly/server/routes/version.js b/spikes/graphs-fe/plotly/server/routes/version.js
new file mode 100644
index 00000000..987747cb
--- /dev/null
+++ b/spikes/graphs-fe/plotly/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/plotly/static/index.html b/spikes/graphs-fe/plotly/static/index.html
new file mode 100644
index 00000000..c5a61365
--- /dev/null
+++ b/spikes/graphs-fe/plotly/static/index.html
@@ -0,0 +1,11 @@
+
+
+
+ React Boilerplate
+
+
+
+
+
+
+
diff --git a/spikes/graphs-fe/plotly/webpack.config.js b/spikes/graphs-fe/plotly/webpack.config.js
new file mode 100644
index 00000000..7295526a
--- /dev/null
+++ b/spikes/graphs-fe/plotly/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);
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);
diff --git a/spikes/graphs-matrix/chartjs/.babelrc b/spikes/graphs-matrix/chartjs/.babelrc
new file mode 100644
index 00000000..82cc857a
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/.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-matrix/chartjs/.eslintignore b/spikes/graphs-matrix/chartjs/.eslintignore
new file mode 100644
index 00000000..683e721c
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/.eslintignore
@@ -0,0 +1,3 @@
+/node_modules
+coverage
+.nyc_output
diff --git a/spikes/graphs-matrix/chartjs/.eslintrc b/spikes/graphs-matrix/chartjs/.eslintrc
new file mode 100644
index 00000000..19bd88dc
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/.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-matrix/chartjs/.gitignore b/spikes/graphs-matrix/chartjs/.gitignore
new file mode 100644
index 00000000..c21b0ba0
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/.gitignore
@@ -0,0 +1,4 @@
+/node_modules
+coverage
+.nyc_output
+npm-debug.log
diff --git a/spikes/graphs-matrix/chartjs/client/actions.js b/spikes/graphs-matrix/chartjs/client/actions.js
new file mode 100644
index 00000000..d48d610c
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/client/actions.js
@@ -0,0 +1,64 @@
+const take = require('lodash.take');
+
+const actions = {
+ 'UPDATE_STATS': (state, action) => {
+ const data = [action.payload].concat(state[action.subscription] || []);
+
+ return {
+ ...state,
+ [action.subscription]: take(data, state.windowSize)
+ };
+ }
+};
+
+module.exports = (state, action) => {
+ return !actions[action.type] ? state : actions[action.type](state, action);
+};
+
+module.exports.subscribe = (id) => (dispatch, getState) => {
+ const {
+ ws
+ } = getState();
+
+ const p = new Promise((resolve, reject) => {
+ ws.subscribe(`/stats/${id}`, (update, flag) => {
+ dispatch({
+ type: 'UPDATE_STATS',
+ payload: update,
+ subscription: id
+ });
+ }, (err) => {
+ if (err) {
+ return reject(err);
+ }
+
+ resolve();
+ });
+ });
+
+ return dispatch({
+ action: 'SUBSCRIBE',
+ payload: p
+ });
+};
+
+module.exports.unsubscribe = (id) => (dispatch, getState) => {
+ const {
+ ws
+ } = getState();
+
+ const p = new Promise((resolve, reject) => {
+ ws.unsubscribe(`/stats/${id}`, null, (err) => {
+ if (err) {
+ return reject(err);
+ }
+
+ resolve();
+ });
+ });
+
+ return dispatch({
+ action: 'UNSUBSCRIBE',
+ payload: p
+ });
+};
\ No newline at end of file
diff --git a/spikes/graphs-matrix/chartjs/client/chart.js b/spikes/graphs-matrix/chartjs/client/chart.js
new file mode 100644
index 00000000..cae1536f
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/client/chart.js
@@ -0,0 +1,144 @@
+const buildArray = require('build-array');
+const Chart = require('chart.js');
+const React = require('react');
+
+// borderSkipped
+// patch `.draw` to support `borderSkipped`:
+// Chart.elements.Rectangle.prototype.draw = function() {
+// var ctx = this._chart.ctx;
+// var vm = this._view;
+//
+// var halfWidth = vm.width / 2,
+// leftX = vm.x - halfWidth,
+// rightX = vm.x + halfWidth,
+// top = vm.base - (vm.base - vm.y),
+// halfStroke = vm.borderWidth / 2;
+//
+// // Canvas doesn't allow us to stroke inside the width so we can
+// // adjust the sizes to fit if we're setting a stroke on the line
+// if (vm.borderWidth) {
+// leftX += halfStroke;
+// rightX -= halfStroke;
+// top += halfStroke;
+// }
+//
+// ctx.beginPath();
+// ctx.fillStyle = vm.backgroundColor;
+// ctx.strokeStyle = vm.borderColor;
+// ctx.lineWidth = vm.borderWidth;
+//
+// var borderSkipped = !Array.isArray(vm.borderSkipped)
+// ? [vm.borderSkipped]
+// : vm.borderSkipped;
+//
+// // Corner points, from bottom-left to bottom-right clockwise
+// // | 1 2 |
+// // | 0 3 |
+// var corners = [
+// [leftX, vm.base, (borderSkipped.indexOf('bottom') >= 0), 'bottom'],
+// [leftX, top, (borderSkipped.indexOf('left') >= 0), 'left'],
+// [rightX, top, (borderSkipped.indexOf('top') >= 0), 'top'],
+// [rightX, vm.base, (borderSkipped.indexOf('right') >= 0), 'right']
+// ];
+//
+// function cornerAt(index) {
+// return corners[index % 4];
+// }
+//
+// // Draw rectangle from 'startCorner'
+// var corner = cornerAt(0);
+// ctx.moveTo(corner[0], corner[1]);
+//
+// for (var i = 1; i < 5; i++) {
+// corner = cornerAt(i);
+// ctx.lineTo(corner[0], corner[1]);
+//
+// if (!corner[2]) {
+// ctx.stroke();
+// }
+// }
+//
+// console.log(corners);
+//
+// ctx.fill();
+// };
+
+module.exports = React.createClass({
+ ref: function(name) {
+ this._refs = this._refs || {};
+
+ return (el) => {
+ this._refs[name] = el;
+ };
+ },
+ fromData: function(data) {
+ return (data || []).map((d) => {
+ return d.cpu;
+ });
+ },
+ componentDidMount: function() {
+ const {
+ data = [],
+ bg,
+ border
+ } = this.props;
+
+ const bars = this.fromData(data);
+
+ this._chart = new Chart(this._refs.component, {
+ type: 'bar',
+ options: {
+ elements: {
+ rectangle: {
+ borderSkipped: ['bottom', 'left', 'right']
+ }
+ },
+ scales: {
+ xAxes: [{
+ display: false
+ }],
+ yAxes: [{
+ display: false
+ }]
+ },
+ legend: {
+ display: false
+ }
+ },
+ data: {
+ labels: buildArray(bars.length).map((v, i) => ''),
+ datasets: [{
+ borderWidth: 1,
+ borderColor: border,
+ backgroundColor: bg,
+ data: bars
+ }]
+ }
+ });
+ },
+ componentWillReceiveProps: function(nextProps) {
+ const {
+ data = [],
+ bg,
+ border
+ } = this.props;
+
+ const bars = this.fromData(data);
+
+ this._chart.data.labels = buildArray(bars.length).map((v, i) => '');
+ this._chart.data.datasets[0].backgroundColor = bg;
+ this._chart.data.datasets[0].borderColor = border;
+ this._chart.data.datasets[0].data = bars;
+
+ this._chart.update(0);
+ },
+ render: function() {
+ return (
+
+ );
+ }
+});
diff --git a/spikes/graphs-matrix/chartjs/client/index.js b/spikes/graphs-matrix/chartjs/client/index.js
new file mode 100644
index 00000000..cc89c799
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/client/index.js
@@ -0,0 +1,38 @@
+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');
+});
+
+const store = Store({
+ ws: client,
+ windowSize: 20
+});
+
+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-matrix/chartjs/client/matrix.js b/spikes/graphs-matrix/chartjs/client/matrix.js
new file mode 100644
index 00000000..af1eb755
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/client/matrix.js
@@ -0,0 +1,104 @@
+const React = require('react');
+const buildArray = require('build-array');
+const ReactRedux = require('react-redux');
+const Chart = require('./chart');
+const actions = require('./actions');
+
+const {
+ connect
+} = ReactRedux;
+
+const {
+ subscribe,
+ unsubscribe
+} = actions;
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ windowSize: state.windowSize,
+ data: state[ownProps.id]
+ };
+};
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+ return {
+ subscribe: () => {
+ return dispatch(subscribe(ownProps.id));
+ },
+ unsubscribe: () => {
+ return unsubscribe(ownProps.id);
+ }
+ }
+};
+
+const Graph = connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(React.createClass({
+ componentWillMount: function() {
+ this.props.subscribe();
+ },
+ componentWillUnmount: function() {
+ this.props.unsubscribe();
+ },
+ render: function() {
+ const {
+ data = [],
+ windowSize
+ } = this.props;
+
+ const _data = buildArray(windowSize).map((v, i) => {
+ return data[i] ? data[i] : {
+ cpu: 0,
+ when: new Date().getTime()
+ };
+ });
+
+ const median = data.reduce((sum, v) => (sum + v.cpu), 0) / data.length;
+
+ const bg = median > 50
+ ? 'rgba(205, 54, 54, 0.3)'
+ : 'rgba(54, 74, 205, 0.3)';
+
+ const border = median > 50
+ ? 'rgba(248, 51, 51, 0.5)'
+ : 'rgba(54, 73, 205, 0.5)';
+
+ return (
+
+ );
+ }
+}));
+
+module.exports = ({
+ x,
+ y
+}) => {
+ const m = buildArray(y).map((v, i) => {
+ const m = buildArray(x).map((v, y) => {
+ const id = `${i}${y}`;
+ return (
+
+
+
+ );
+ });
+
+ return (
+
+ {m}
+
+ );
+ });
+
+ return (
+
+ {m}
+
+ );
+};
diff --git a/spikes/graphs-matrix/chartjs/client/root.js b/spikes/graphs-matrix/chartjs/client/root.js
new file mode 100644
index 00000000..58534619
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/client/root.js
@@ -0,0 +1,24 @@
+const React = require('react');
+const ReactHotLoader = require('react-hot-loader');
+const ReactRedux = require('react-redux');
+const Matrix = require('./matrix');
+
+const {
+ AppContainer
+} = ReactHotLoader;
+
+const {
+ Provider
+} = ReactRedux;
+
+module.exports = ({
+ store
+}) => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/spikes/graphs-matrix/chartjs/client/store.js b/spikes/graphs-matrix/chartjs/client/store.js
new file mode 100644
index 00000000..27f9837a
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/client/store.js
@@ -0,0 +1,21 @@
+const createLogger = require('redux-logger');
+const promiseMiddleware = require('redux-promise-middleware').default;
+const thunk = require('redux-thunk').default;
+const redux = require('redux');
+const reducer = require('./actions');
+
+const {
+ createStore,
+ compose,
+ applyMiddleware
+} = redux;
+
+module.exports = (state = Object.freeze({})) => {
+ return createStore(reducer, state, applyMiddleware(
+ createLogger({
+ predicate: (getState, action) => action.type !== 'UPDATE_STATS'
+ }),
+ promiseMiddleware(),
+ thunk
+ ));
+};
diff --git a/spikes/graphs-matrix/chartjs/package.json b/spikes/graphs-matrix/chartjs/package.json
new file mode 100644
index 00000000..65122fd3
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/package.json
@@ -0,0 +1,62 @@
+{
+ "name": "chartjs-matrix-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",
+ "chart.js": "^2.3.0",
+ "classnames": "^2.2.5",
+ "component-emitter": "^1.2.1",
+ "css-loader": "^0.25.0",
+ "d3": "^4.3.0",
+ "hapi": "^15.2.0",
+ "hapi-webpack-dev-plugin": "^1.1.4",
+ "inert": "^4.0.2",
+ "lodash.take": "^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",
+ "redux-logger": "^2.7.4",
+ "redux-promise-middleware": "^4.1.0",
+ "redux-thunk": "^2.1.0",
+ "require-dir": "^0.3.1",
+ "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-matrix/chartjs/readme.md b/spikes/graphs-matrix/chartjs/readme.md
new file mode 100644
index 00000000..f874b364
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/readme.md
@@ -0,0 +1,14 @@
+# ChartJS
+
+ - [x] Graphs should maintain aspect ration
+ - [x] Graphs should match Antonas' first draft designs
+ - [x] Should have 3 Graphs on each row
+ - [x] Should be a 3 x 4 matrix of graphs, showing different data
+ - [x] Graphs should not jitter, ideally smoothly move across the x axis
+ - [x] All graphs should be a bar graph
+ - [ ] Animations when a graph comes into view
+
+## notes
+
+ - borderSkipped not working: https://github.com/chartjs/Chart.js/issues/3293
+
diff --git a/spikes/graphs-matrix/chartjs/server/index.js b/spikes/graphs-matrix/chartjs/server/index.js
new file mode 100644
index 00000000..b2a6a529
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/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-matrix/chartjs/server/metric.js b/spikes/graphs-matrix/chartjs/server/metric.js
new file mode 100644
index 00000000..24015f70
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/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-matrix/chartjs/server/plugins.js b/spikes/graphs-matrix/chartjs/server/plugins.js
new file mode 100644
index 00000000..84944329
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/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-matrix/chartjs/server/routes/home.js b/spikes/graphs-matrix/chartjs/server/routes/home.js
new file mode 100644
index 00000000..48ead969
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/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-matrix/chartjs/server/routes/metrics.js b/spikes/graphs-matrix/chartjs/server/routes/metrics.js
new file mode 100644
index 00000000..e6e1c261
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/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-matrix/chartjs/server/routes/static.js b/spikes/graphs-matrix/chartjs/server/routes/static.js
new file mode 100644
index 00000000..5a41f602
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/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-matrix/chartjs/server/routes/version.js b/spikes/graphs-matrix/chartjs/server/routes/version.js
new file mode 100644
index 00000000..987747cb
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/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-matrix/chartjs/static/index.html b/spikes/graphs-matrix/chartjs/static/index.html
new file mode 100644
index 00000000..fd29e69d
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/static/index.html
@@ -0,0 +1,983 @@
+
+
+
+ React Boilerplate
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spikes/graphs-matrix/chartjs/webpack.config.js b/spikes/graphs-matrix/chartjs/webpack.config.js
new file mode 100644
index 00000000..792e8f4d
--- /dev/null
+++ b/spikes/graphs-matrix/chartjs/webpack.config.js
@@ -0,0 +1,65 @@
+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(),
+ new webpack.ProvidePlugin({
+ 'd3': 'd3'
+ })
+ ],
+ 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);
diff --git a/spikes/graphs-matrix/epoch/.babelrc b/spikes/graphs-matrix/epoch/.babelrc
new file mode 100644
index 00000000..82cc857a
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/.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-matrix/epoch/.eslintignore b/spikes/graphs-matrix/epoch/.eslintignore
new file mode 100644
index 00000000..683e721c
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/.eslintignore
@@ -0,0 +1,3 @@
+/node_modules
+coverage
+.nyc_output
diff --git a/spikes/graphs-matrix/epoch/.eslintrc b/spikes/graphs-matrix/epoch/.eslintrc
new file mode 100644
index 00000000..19bd88dc
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/.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-matrix/epoch/.gitignore b/spikes/graphs-matrix/epoch/.gitignore
new file mode 100644
index 00000000..c21b0ba0
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/.gitignore
@@ -0,0 +1,4 @@
+/node_modules
+coverage
+.nyc_output
+npm-debug.log
diff --git a/spikes/graphs-matrix/epoch/client/actions.js b/spikes/graphs-matrix/epoch/client/actions.js
new file mode 100644
index 00000000..4136fe1e
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/client/actions.js
@@ -0,0 +1,64 @@
+const takeRight = require('lodash.takeright');
+
+const actions = {
+ 'UPDATE_STATS': (state, action) => {
+ const data = (state[action.subscription] || []).concat([action.payload]);
+
+ return {
+ ...state,
+ [action.subscription]: takeRight(data, 50)
+ };
+ }
+};
+
+module.exports = (state, action) => {
+ return !actions[action.type] ? state : actions[action.type](state, action);
+};
+
+module.exports.subscribe = (id) => (dispatch, getState) => {
+ const {
+ ws
+ } = getState();
+
+ const p = new Promise((resolve, reject) => {
+ ws.subscribe(`/stats/${id}`, (update, flag) => {
+ dispatch({
+ type: 'UPDATE_STATS',
+ payload: update,
+ subscription: id
+ });
+ }, (err) => {
+ if (err) {
+ return reject(err);
+ }
+
+ resolve();
+ });
+ });
+
+ return dispatch({
+ action: 'SUBSCRIBE',
+ payload: p
+ });
+};
+
+module.exports.unsubscribe = (id) => (dispatch, getState) => {
+ const {
+ ws
+ } = getState();
+
+ const p = new Promise((resolve, reject) => {
+ ws.unsubscribe(`/stats/${id}`, null, (err) => {
+ if (err) {
+ return reject(err);
+ }
+
+ resolve();
+ });
+ });
+
+ return dispatch({
+ action: 'UNSUBSCRIBE',
+ payload: p
+ });
+};
\ No newline at end of file
diff --git a/spikes/graphs-matrix/epoch/client/epoch.js b/spikes/graphs-matrix/epoch/client/epoch.js
new file mode 100644
index 00000000..daac3937
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/client/epoch.js
@@ -0,0 +1,63 @@
+// injects into `window` (ikr)
+require('epoch-charting');
+
+const ReactRedux = require('react-redux');
+const React = require('react');
+
+const {
+ Time: {
+ Bar
+ }
+} = window.Epoch;
+
+const {
+ connect
+} = ReactRedux;
+
+const style = {
+ height: '220px'
+};
+
+module.exports = React.createClass({
+ ref: function(name) {
+ this._refs = this._refs || {};
+
+ return (el) => {
+ this._refs[name] = el;
+ };
+ },
+ fromData: function(data) {
+ return (data || []).map((d) => {
+ return {
+ y: d.cpu,
+ time: d.when
+ };
+ });
+ },
+ componentDidMount: function() {
+ this.chart = new Bar({
+ el: this._refs.component,
+ type: 'time.bar',
+ data: [{
+ label: 'A',
+ values: []
+ }]
+ });
+ },
+ componentWillReceiveProps: function(nextProps) {
+ this.fromData(this.props.data).forEach((r) => this.chart.push([r]));
+ },
+ render: function() {
+ const className = (this.props.median > 50)
+ ? 'red'
+ : 'blue';
+
+ return (
+
+ );
+ }
+});
\ No newline at end of file
diff --git a/spikes/graphs-matrix/epoch/client/index.js b/spikes/graphs-matrix/epoch/client/index.js
new file mode 100644
index 00000000..34d1748c
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/client/index.js
@@ -0,0 +1,37 @@
+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');
+});
+
+const store = Store({
+ ws: client
+});
+
+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-matrix/epoch/client/matrix.js b/spikes/graphs-matrix/epoch/client/matrix.js
new file mode 100644
index 00000000..ae2ca2b8
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/client/matrix.js
@@ -0,0 +1,95 @@
+const React = require('react');
+const buildArray = require('build-array');
+const ReactRedux = require('react-redux');
+const Epoch = require('./epoch');
+const actions = require('./actions');
+
+const {
+ connect
+} = ReactRedux;
+
+const {
+ subscribe,
+ unsubscribe
+} = actions;
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ data: state[ownProps.id]
+ };
+};
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+ return {
+ subscribe: () => {
+ return dispatch(subscribe(ownProps.id));
+ },
+ unsubscribe: () => {
+ return unsubscribe(ownProps.id);
+ }
+ }
+};
+
+const Graph = connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(React.createClass({
+ componentWillMount: function() {
+ this.props.subscribe();
+ },
+ componentWillUnmount: function() {
+ this.props.unsubscribe();
+ },
+ render: function() {
+ const {
+ data = []
+ } = this.props;
+
+ const median = data.reduce((sum, v) => (sum + v.cpu), 0) / data.length;
+
+ const bg = median > 50
+ ? 'rgba(205, 54, 54, 0.3)'
+ : 'rgba(54, 74, 205, 0.3)';
+
+ const shadow = median > 50
+ ? 'inset 0 1px 0 0 rgba(248, 51, 51, 0.5)'
+ : 'inset 0 1px 0 0 rgba(54, 73, 205, 0.5)';
+
+ return (
+
+ );
+ }
+}));
+
+module.exports = ({
+ x,
+ y
+}) => {
+ const m = buildArray(y).map((v, i) => {
+ const m = buildArray(x).map((v, y) => {
+ const id = `${i}${y}`;
+ return (
+
+
+
+ );
+ });
+
+ return (
+
+ {m}
+
+ );
+ });
+
+ return (
+
+ {m}
+
+ );
+};
diff --git a/spikes/graphs-matrix/epoch/client/root.js b/spikes/graphs-matrix/epoch/client/root.js
new file mode 100644
index 00000000..58534619
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/client/root.js
@@ -0,0 +1,24 @@
+const React = require('react');
+const ReactHotLoader = require('react-hot-loader');
+const ReactRedux = require('react-redux');
+const Matrix = require('./matrix');
+
+const {
+ AppContainer
+} = ReactHotLoader;
+
+const {
+ Provider
+} = ReactRedux;
+
+module.exports = ({
+ store
+}) => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/spikes/graphs-matrix/epoch/client/store.js b/spikes/graphs-matrix/epoch/client/store.js
new file mode 100644
index 00000000..452af806
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/client/store.js
@@ -0,0 +1,19 @@
+const createLogger = require('redux-logger');
+const promiseMiddleware = require('redux-promise-middleware').default;
+const thunk = require('redux-thunk').default;
+const redux = require('redux');
+const reducer = require('./actions');
+
+const {
+ createStore,
+ compose,
+ applyMiddleware
+} = redux;
+
+module.exports = (state = Object.freeze({})) => {
+ return createStore(reducer, state, applyMiddleware(
+ createLogger(),
+ promiseMiddleware(),
+ thunk
+ ));
+};
diff --git a/spikes/graphs-matrix/epoch/package.json b/spikes/graphs-matrix/epoch/package.json
new file mode 100644
index 00000000..a3ed5cf4
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/package.json
@@ -0,0 +1,62 @@
+{
+ "name": "epoch-matrix-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",
+ "classnames": "^2.2.5",
+ "component-emitter": "^1.2.1",
+ "css-loader": "^0.25.0",
+ "d3": "^4.3.0",
+ "epoch-charting": "^0.8.4",
+ "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",
+ "redux-logger": "^2.7.4",
+ "redux-promise-middleware": "^4.1.0",
+ "redux-thunk": "^2.1.0",
+ "require-dir": "^0.3.1",
+ "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-matrix/epoch/readme.md b/spikes/graphs-matrix/epoch/readme.md
new file mode 100644
index 00000000..5172d589
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/readme.md
@@ -0,0 +1,15 @@
+# Epoch
+
+ - [x] Graphs should maintain aspect ration
+ - [ ] Graphs should match Antonas' first draft designs
+ - [x] Should have 3 Graphs on each row
+ - [x] Should be a 3 x 4 matrix of graphs, showing different data
+ - [x] Graphs should not jitter, ideally smoothly move across the x axis
+ - [x] All graphs should be a bar graph
+ - [ ] Animations when a graph comes into view
+
+## notes
+
+ - Epoch is not responsive. Even though they maintain aspect ratio, using a responsive grid they get cluttered between each other
+ - With short update intervals, the graphs start using to much cpu and can't handle it
+ - Even looking at the [documentation](https://epochjs.github.io/epoch/styles), it's not obvious how styling works and I wasn't able to make it work.
diff --git a/spikes/graphs-matrix/epoch/server/index.js b/spikes/graphs-matrix/epoch/server/index.js
new file mode 100644
index 00000000..b2a6a529
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/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-matrix/epoch/server/metric.js b/spikes/graphs-matrix/epoch/server/metric.js
new file mode 100644
index 00000000..0d64fad2
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/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
+ });
+ }, 400);
+
+ cdm[id] = {
+ interval,
+ sockets: 1
+ };
+ },
+ off: (id) => {
+ if (!(cdm[id].sockets -= 1)) {
+ clearInterval(cdm[id].interval);
+ }
+ }
+});
diff --git a/spikes/graphs-matrix/epoch/server/plugins.js b/spikes/graphs-matrix/epoch/server/plugins.js
new file mode 100644
index 00000000..84944329
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/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-matrix/epoch/server/routes/home.js b/spikes/graphs-matrix/epoch/server/routes/home.js
new file mode 100644
index 00000000..48ead969
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/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-matrix/epoch/server/routes/metrics.js b/spikes/graphs-matrix/epoch/server/routes/metrics.js
new file mode 100644
index 00000000..e6e1c261
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/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-matrix/epoch/server/routes/static.js b/spikes/graphs-matrix/epoch/server/routes/static.js
new file mode 100644
index 00000000..5a41f602
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/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-matrix/epoch/server/routes/version.js b/spikes/graphs-matrix/epoch/server/routes/version.js
new file mode 100644
index 00000000..987747cb
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/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-matrix/epoch/static/index.html b/spikes/graphs-matrix/epoch/static/index.html
new file mode 100644
index 00000000..fd29e69d
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/static/index.html
@@ -0,0 +1,983 @@
+
+
+
+ React Boilerplate
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spikes/graphs-matrix/epoch/webpack.config.js b/spikes/graphs-matrix/epoch/webpack.config.js
new file mode 100644
index 00000000..792e8f4d
--- /dev/null
+++ b/spikes/graphs-matrix/epoch/webpack.config.js
@@ -0,0 +1,65 @@
+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(),
+ new webpack.ProvidePlugin({
+ 'd3': 'd3'
+ })
+ ],
+ 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);
diff --git a/spikes/graphs-matrix/nvd3/.babelrc b/spikes/graphs-matrix/nvd3/.babelrc
new file mode 100644
index 00000000..82cc857a
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/.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-matrix/nvd3/.eslintignore b/spikes/graphs-matrix/nvd3/.eslintignore
new file mode 100644
index 00000000..683e721c
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/.eslintignore
@@ -0,0 +1,3 @@
+/node_modules
+coverage
+.nyc_output
diff --git a/spikes/graphs-matrix/nvd3/.eslintrc b/spikes/graphs-matrix/nvd3/.eslintrc
new file mode 100644
index 00000000..19bd88dc
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/.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-matrix/nvd3/.gitignore b/spikes/graphs-matrix/nvd3/.gitignore
new file mode 100644
index 00000000..c21b0ba0
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/.gitignore
@@ -0,0 +1,4 @@
+/node_modules
+coverage
+.nyc_output
+npm-debug.log
diff --git a/spikes/graphs-matrix/nvd3/client/actions.js b/spikes/graphs-matrix/nvd3/client/actions.js
new file mode 100644
index 00000000..77266a12
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/client/actions.js
@@ -0,0 +1,64 @@
+const take = require('lodash.take');
+
+const actions = {
+ 'UPDATE_STATS': (state, action) => {
+ const data = [action.payload].concat(state[action.subscription] || []);
+
+ return {
+ ...state,
+ [action.subscription]: take(data, state.windowSize)
+ };
+ }
+};
+
+module.exports = (state, action) => {
+ return !actions[action.type] ? state : actions[action.type](state, action);
+};
+
+module.exports.subscribe = (id) => (dispatch, getState) => {
+ const {
+ ws
+ } = getState();
+
+ const p = new Promise((resolve, reject) => {
+ ws.subscribe(`/stats/${id}`, (update, flag) => {
+ dispatch({
+ type: 'UPDATE_STATS',
+ payload: update,
+ subscription: id
+ });
+ }, (err) => {
+ if (err) {
+ return reject(err);
+ }
+
+ resolve();
+ });
+ });
+
+ return dispatch({
+ action: 'SUBSCRIBE',
+ payload: p
+ });
+};
+
+module.exports.unsubscribe = (id) => (dispatch, getState) => {
+ const {
+ ws
+ } = getState();
+
+ const p = new Promise((resolve, reject) => {
+ ws.unsubscribe(`/stats/${id}`, null, (err) => {
+ if (err) {
+ return reject(err);
+ }
+
+ resolve();
+ });
+ });
+
+ return dispatch({
+ action: 'UNSUBSCRIBE',
+ payload: p
+ });
+};
diff --git a/spikes/graphs-matrix/nvd3/client/chart.js b/spikes/graphs-matrix/nvd3/client/chart.js
new file mode 100644
index 00000000..64ed36b9
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/client/chart.js
@@ -0,0 +1,41 @@
+const NVD3Chart = require('react-nvd3');
+const React = require('react');
+
+module.exports = ({
+ data
+}) => {
+ const datum = [{
+ key: 'test',
+ values: (data || []).map((v, i) => ({
+ label: `${i}`,
+ value: v.cpu
+ }))
+ }];
+
+ const context = {
+ getColor: (i) => {
+ if (i.value > 50) {
+ return 'red';
+ }
+
+ return 'green';
+ }
+ };
+
+ const color = {
+ name: 'getColor',
+ type: 'function'
+ };
+
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/spikes/graphs-matrix/nvd3/client/index.js b/spikes/graphs-matrix/nvd3/client/index.js
new file mode 100644
index 00000000..cc89c799
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/client/index.js
@@ -0,0 +1,38 @@
+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');
+});
+
+const store = Store({
+ ws: client,
+ windowSize: 20
+});
+
+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-matrix/nvd3/client/matrix.js b/spikes/graphs-matrix/nvd3/client/matrix.js
new file mode 100644
index 00000000..af1eb755
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/client/matrix.js
@@ -0,0 +1,104 @@
+const React = require('react');
+const buildArray = require('build-array');
+const ReactRedux = require('react-redux');
+const Chart = require('./chart');
+const actions = require('./actions');
+
+const {
+ connect
+} = ReactRedux;
+
+const {
+ subscribe,
+ unsubscribe
+} = actions;
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ windowSize: state.windowSize,
+ data: state[ownProps.id]
+ };
+};
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+ return {
+ subscribe: () => {
+ return dispatch(subscribe(ownProps.id));
+ },
+ unsubscribe: () => {
+ return unsubscribe(ownProps.id);
+ }
+ }
+};
+
+const Graph = connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(React.createClass({
+ componentWillMount: function() {
+ this.props.subscribe();
+ },
+ componentWillUnmount: function() {
+ this.props.unsubscribe();
+ },
+ render: function() {
+ const {
+ data = [],
+ windowSize
+ } = this.props;
+
+ const _data = buildArray(windowSize).map((v, i) => {
+ return data[i] ? data[i] : {
+ cpu: 0,
+ when: new Date().getTime()
+ };
+ });
+
+ const median = data.reduce((sum, v) => (sum + v.cpu), 0) / data.length;
+
+ const bg = median > 50
+ ? 'rgba(205, 54, 54, 0.3)'
+ : 'rgba(54, 74, 205, 0.3)';
+
+ const border = median > 50
+ ? 'rgba(248, 51, 51, 0.5)'
+ : 'rgba(54, 73, 205, 0.5)';
+
+ return (
+
+ );
+ }
+}));
+
+module.exports = ({
+ x,
+ y
+}) => {
+ const m = buildArray(y).map((v, i) => {
+ const m = buildArray(x).map((v, y) => {
+ const id = `${i}${y}`;
+ return (
+
+
+
+ );
+ });
+
+ return (
+
+ {m}
+
+ );
+ });
+
+ return (
+
+ {m}
+
+ );
+};
diff --git a/spikes/graphs-matrix/nvd3/client/root.js b/spikes/graphs-matrix/nvd3/client/root.js
new file mode 100644
index 00000000..58534619
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/client/root.js
@@ -0,0 +1,24 @@
+const React = require('react');
+const ReactHotLoader = require('react-hot-loader');
+const ReactRedux = require('react-redux');
+const Matrix = require('./matrix');
+
+const {
+ AppContainer
+} = ReactHotLoader;
+
+const {
+ Provider
+} = ReactRedux;
+
+module.exports = ({
+ store
+}) => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/spikes/graphs-matrix/nvd3/client/store.js b/spikes/graphs-matrix/nvd3/client/store.js
new file mode 100644
index 00000000..27f9837a
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/client/store.js
@@ -0,0 +1,21 @@
+const createLogger = require('redux-logger');
+const promiseMiddleware = require('redux-promise-middleware').default;
+const thunk = require('redux-thunk').default;
+const redux = require('redux');
+const reducer = require('./actions');
+
+const {
+ createStore,
+ compose,
+ applyMiddleware
+} = redux;
+
+module.exports = (state = Object.freeze({})) => {
+ return createStore(reducer, state, applyMiddleware(
+ createLogger({
+ predicate: (getState, action) => action.type !== 'UPDATE_STATS'
+ }),
+ promiseMiddleware(),
+ thunk
+ ));
+};
diff --git a/spikes/graphs-matrix/nvd3/package.json b/spikes/graphs-matrix/nvd3/package.json
new file mode 100644
index 00000000..76394687
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "nvd3-matrix-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",
+ "classnames": "^2.2.5",
+ "component-emitter": "^1.2.1",
+ "css-loader": "^0.25.0",
+ "d3": "^4.3.0",
+ "hapi": "^15.2.0",
+ "hapi-webpack-dev-plugin": "^1.1.4",
+ "inert": "^4.0.2",
+ "lodash.take": "^4.1.1",
+ "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-nvd3": "^0.5.7",
+ "react-redux": "^4.4.5",
+ "redux": "^3.6.0",
+ "redux-logger": "^2.7.4",
+ "redux-promise-middleware": "^4.1.0",
+ "redux-thunk": "^2.1.0",
+ "require-dir": "^0.3.1",
+ "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-matrix/nvd3/readme.md b/spikes/graphs-matrix/nvd3/readme.md
new file mode 100644
index 00000000..5172d589
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/readme.md
@@ -0,0 +1,15 @@
+# Epoch
+
+ - [x] Graphs should maintain aspect ration
+ - [ ] Graphs should match Antonas' first draft designs
+ - [x] Should have 3 Graphs on each row
+ - [x] Should be a 3 x 4 matrix of graphs, showing different data
+ - [x] Graphs should not jitter, ideally smoothly move across the x axis
+ - [x] All graphs should be a bar graph
+ - [ ] Animations when a graph comes into view
+
+## notes
+
+ - Epoch is not responsive. Even though they maintain aspect ratio, using a responsive grid they get cluttered between each other
+ - With short update intervals, the graphs start using to much cpu and can't handle it
+ - Even looking at the [documentation](https://epochjs.github.io/epoch/styles), it's not obvious how styling works and I wasn't able to make it work.
diff --git a/spikes/graphs-matrix/nvd3/server/index.js b/spikes/graphs-matrix/nvd3/server/index.js
new file mode 100644
index 00000000..b2a6a529
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/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-matrix/nvd3/server/metric.js b/spikes/graphs-matrix/nvd3/server/metric.js
new file mode 100644
index 00000000..cf3f1394
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/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
+ });
+ }, 1000);
+
+ cdm[id] = {
+ interval,
+ sockets: 1
+ };
+ },
+ off: (id) => {
+ if (!(cdm[id].sockets -= 1)) {
+ clearInterval(cdm[id].interval);
+ }
+ }
+});
diff --git a/spikes/graphs-matrix/nvd3/server/plugins.js b/spikes/graphs-matrix/nvd3/server/plugins.js
new file mode 100644
index 00000000..84944329
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/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-matrix/nvd3/server/routes/home.js b/spikes/graphs-matrix/nvd3/server/routes/home.js
new file mode 100644
index 00000000..48ead969
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/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-matrix/nvd3/server/routes/metrics.js b/spikes/graphs-matrix/nvd3/server/routes/metrics.js
new file mode 100644
index 00000000..e6e1c261
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/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-matrix/nvd3/server/routes/static.js b/spikes/graphs-matrix/nvd3/server/routes/static.js
new file mode 100644
index 00000000..5a41f602
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/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-matrix/nvd3/server/routes/version.js b/spikes/graphs-matrix/nvd3/server/routes/version.js
new file mode 100644
index 00000000..987747cb
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/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-matrix/nvd3/static/index.html b/spikes/graphs-matrix/nvd3/static/index.html
new file mode 100644
index 00000000..fd29e69d
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/static/index.html
@@ -0,0 +1,983 @@
+
+
+
+ React Boilerplate
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spikes/graphs-matrix/nvd3/webpack.config.js b/spikes/graphs-matrix/nvd3/webpack.config.js
new file mode 100644
index 00000000..792e8f4d
--- /dev/null
+++ b/spikes/graphs-matrix/nvd3/webpack.config.js
@@ -0,0 +1,65 @@
+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(),
+ new webpack.ProvidePlugin({
+ 'd3': 'd3'
+ })
+ ],
+ 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);