diff --git a/spikes/architecture/react-d3/.babelrc b/spikes/architecture/react-d3/.babelrc
new file mode 100644
index 00000000..82cc857a
--- /dev/null
+++ b/spikes/architecture/react-d3/.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/architecture/react-d3/.eslintignore b/spikes/architecture/react-d3/.eslintignore
new file mode 100644
index 00000000..683e721c
--- /dev/null
+++ b/spikes/architecture/react-d3/.eslintignore
@@ -0,0 +1,3 @@
+/node_modules
+coverage
+.nyc_output
diff --git a/spikes/architecture/react-d3/.eslintrc b/spikes/architecture/react-d3/.eslintrc
new file mode 100644
index 00000000..a1e892dc
--- /dev/null
+++ b/spikes/architecture/react-d3/.eslintrc
@@ -0,0 +1,30 @@
+{
+ "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
+ }],
+ "operator-linebreak": 0
+ }
+}
diff --git a/spikes/architecture/react-d3/.gitignore b/spikes/architecture/react-d3/.gitignore
new file mode 100644
index 00000000..c21b0ba0
--- /dev/null
+++ b/spikes/architecture/react-d3/.gitignore
@@ -0,0 +1,4 @@
+/node_modules
+coverage
+.nyc_output
+npm-debug.log
diff --git a/spikes/architecture/react-d3/client/graph.js b/spikes/architecture/react-d3/client/graph.js
new file mode 100644
index 00000000..ec911f23
--- /dev/null
+++ b/spikes/architecture/react-d3/client/graph.js
@@ -0,0 +1,26 @@
+const ReactRedux = require('react-redux');
+const React = require('react');
+const Links = require('./links');
+const Nodes = require('./nodes');
+
+const {
+ connect
+} = ReactRedux;
+
+const Component = (props) =>
+ ;
+
+const mapStateToProps = ({
+ data
+}) => {
+ return {
+ data
+ };
+};
+
+module.exports = connect(
+ mapStateToProps
+)(Component);
diff --git a/spikes/architecture/react-d3/client/index.js b/spikes/architecture/react-d3/client/index.js
new file mode 100644
index 00000000..317b0fd5
--- /dev/null
+++ b/spikes/architecture/react-d3/client/index.js
@@ -0,0 +1,18 @@
+const ReactDOM = require('react-dom');
+const React = require('react');
+const store = require('./store')();
+
+const render = () => {
+ const Root = require('./root');
+
+ ReactDOM.render(
+ ,
+ document.getElementById('root')
+ );
+};
+
+render();
+
+if (module.hot) {
+ module.hot.accept('./root', render);
+}
diff --git a/spikes/architecture/react-d3/client/links.js b/spikes/architecture/react-d3/client/links.js
new file mode 100644
index 00000000..c629c3ab
--- /dev/null
+++ b/spikes/architecture/react-d3/client/links.js
@@ -0,0 +1,10 @@
+const React = require('react');
+
+const renderLines = (props) => {
+ return () => ;
+};
+
+module.exports = (props) =>
+
+ { props.data.links.map(renderLines()) }
+ ;
diff --git a/spikes/architecture/react-d3/client/nodes.js b/spikes/architecture/react-d3/client/nodes.js
new file mode 100644
index 00000000..1260025c
--- /dev/null
+++ b/spikes/architecture/react-d3/client/nodes.js
@@ -0,0 +1,75 @@
+const React = require('react');
+
+function rightRoundedRect(x, y, width, height, radius) {
+ return 'M' + x + ',' + y // Move to (absolute)
+ + 'h ' + (width - radius) // Horizontal line to (relative)
+ + 'a ' + radius + ',' + radius + ' 0 0 1 ' + radius + ',' + radius // Relative arc
+ + 'v ' + (height - 2 * radius) // Vertical line to (relative)
+ + 'a ' + radius + ',' + radius + ' 0 0 1 ' + -radius + ',' + radius // Relative arch
+ + 'h ' + (radius - width) // Horizontal lint to (relative)
+ + 'z '; // path back to start
+}
+
+function leftRoundedRect(x, y, width, height, radius) {
+ return 'M' + (x + width) + ',' + y // Move to (absolute) start at top-right
+ + 'v ' + height // Vertical line to (relative)
+ + 'h ' + (radius - width) // Horizontal line to (relative)
+ + 'a ' + radius + ',' + radius + ' 0 0 1 ' + -radius + ',' + -radius // Relative arc
+ + 'v ' + -(height - 2 * radius) // Vertical line to (relative)
+ + 'a ' + radius + ',' + radius + ' 0 0 1 ' + radius + ',' + -radius // Relative arch
+ + 'z '; // path back to start
+}
+
+const InfoBoxContainer = () =>
+
+
+
+ ;
+
+const InfoBoxAlert = () =>
+
+
+ {'!'}
+ ;
+
+const InfoBoxText = (props) =>
+ {props.id};
+
+module.exports = (props) => (
+
+ { props.data.nodes.map(node =>
+
+
+
+
+
+ )
+ }
+
+);
diff --git a/spikes/architecture/react-d3/client/root.js b/spikes/architecture/react-d3/client/root.js
new file mode 100644
index 00000000..2af05235
--- /dev/null
+++ b/spikes/architecture/react-d3/client/root.js
@@ -0,0 +1,21 @@
+const React = require('react');
+const ReactHotLoader = require('react-hot-loader');
+const ReactRedux = require('react-redux');
+const Graph = require('./graph');
+
+const {
+ AppContainer
+} = ReactHotLoader;
+
+const {
+ Provider
+} = ReactRedux;
+
+module.exports = ({
+ store
+}) =>
+
+
+
+
+ ;
diff --git a/spikes/architecture/react-d3/client/services.json b/spikes/architecture/react-d3/client/services.json
new file mode 100644
index 00000000..cc8be654
--- /dev/null
+++ b/spikes/architecture/react-d3/client/services.json
@@ -0,0 +1,15 @@
+{
+ "nodes": [
+ {"id": "Nginx", "group": 1},
+ {"id": "Wordpress", "group": 1},
+ {"id": "Memcached", "group": 1},
+ {"id": "Percona", "group": 1},
+ {"id": "NFS", "group": 1}
+ ],
+ "links": [
+ {"source": "Nginx", "target": "Wordpress", "value": 1},
+ {"source": "Wordpress", "target": "Memcached", "value": 8},
+ {"source": "Wordpress", "target": "NFS", "value": 8},
+ {"source": "Wordpress", "target": "Percona", "value": 8}
+ ]
+}
diff --git a/spikes/architecture/react-d3/client/store.js b/spikes/architecture/react-d3/client/store.js
new file mode 100644
index 00000000..8e5612d5
--- /dev/null
+++ b/spikes/architecture/react-d3/client/store.js
@@ -0,0 +1,17 @@
+const redux = require('redux');
+const services = require('./services');
+
+const {
+ createStore
+} = redux;
+
+const reducer = (state, action) => {
+ return {
+ ...state,
+ data: services
+ };
+};
+
+module.exports = (state = Object.freeze({})) => {
+ return createStore(reducer, state);
+};
diff --git a/spikes/architecture/react-d3/package.json b/spikes/architecture/react-d3/package.json
new file mode 100644
index 00000000..6e83ff22
--- /dev/null
+++ b/spikes/architecture/react-d3/package.json
@@ -0,0 +1,56 @@
+{
+ "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",
+ "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/architecture/react-d3/readme.md b/spikes/architecture/react-d3/readme.md
new file mode 100644
index 00000000..fe0718a4
--- /dev/null
+++ b/spikes/architecture/react-d3/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/architecture/react-d3/server/index.js b/spikes/architecture/react-d3/server/index.js
new file mode 100644
index 00000000..b2a6a529
--- /dev/null
+++ b/spikes/architecture/react-d3/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/architecture/react-d3/server/plugins.js b/spikes/architecture/react-d3/server/plugins.js
new file mode 100644
index 00000000..84944329
--- /dev/null
+++ b/spikes/architecture/react-d3/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/architecture/react-d3/server/routes/home.js b/spikes/architecture/react-d3/server/routes/home.js
new file mode 100644
index 00000000..48ead969
--- /dev/null
+++ b/spikes/architecture/react-d3/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/architecture/react-d3/server/routes/static.js b/spikes/architecture/react-d3/server/routes/static.js
new file mode 100644
index 00000000..5a41f602
--- /dev/null
+++ b/spikes/architecture/react-d3/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/architecture/react-d3/server/routes/version.js b/spikes/architecture/react-d3/server/routes/version.js
new file mode 100644
index 00000000..987747cb
--- /dev/null
+++ b/spikes/architecture/react-d3/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/architecture/react-d3/static/index.html b/spikes/architecture/react-d3/static/index.html
new file mode 100644
index 00000000..422fc652
--- /dev/null
+++ b/spikes/architecture/react-d3/static/index.html
@@ -0,0 +1,11 @@
+
+
+
+ D3 React Boilerplate
+
+
+
+
+
+
+
diff --git a/spikes/architecture/react-d3/webpack.config.js b/spikes/architecture/react-d3/webpack.config.js
new file mode 100644
index 00000000..7295526a
--- /dev/null
+++ b/spikes/architecture/react-d3/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);