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);