From 286304136ad21e3df4d6c7cc2a6dd1c9b6dfc4af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Ramos?= Date: Wed, 9 Nov 2016 13:58:10 +0000 Subject: [PATCH] chartjs matrix implementation --- spikes/graphs-matrix/chartjs/.babelrc | 15 + spikes/graphs-matrix/chartjs/.eslintignore | 3 + spikes/graphs-matrix/chartjs/.eslintrc | 29 + spikes/graphs-matrix/chartjs/.gitignore | 4 + .../graphs-matrix/chartjs/client/actions.js | 64 ++ spikes/graphs-matrix/chartjs/client/chart.js | 48 + spikes/graphs-matrix/chartjs/client/index.js | 37 + spikes/graphs-matrix/chartjs/client/matrix.js | 95 ++ spikes/graphs-matrix/chartjs/client/root.js | 24 + spikes/graphs-matrix/chartjs/client/store.js | 19 + spikes/graphs-matrix/chartjs/package.json | 62 ++ spikes/graphs-matrix/chartjs/readme.md | 15 + spikes/graphs-matrix/chartjs/server/index.js | 29 + spikes/graphs-matrix/chartjs/server/metric.js | 34 + .../graphs-matrix/chartjs/server/plugins.js | 15 + .../chartjs/server/routes/home.js | 11 + .../chartjs/server/routes/metrics.js | 18 + .../chartjs/server/routes/static.js | 15 + .../chartjs/server/routes/version.js | 18 + .../graphs-matrix/chartjs/static/index.html | 983 ++++++++++++++++++ .../graphs-matrix/chartjs/webpack.config.js | 65 ++ spikes/graphs-matrix/epoch/package.json | 2 +- 22 files changed, 1604 insertions(+), 1 deletion(-) create mode 100644 spikes/graphs-matrix/chartjs/.babelrc create mode 100644 spikes/graphs-matrix/chartjs/.eslintignore create mode 100644 spikes/graphs-matrix/chartjs/.eslintrc create mode 100644 spikes/graphs-matrix/chartjs/.gitignore create mode 100644 spikes/graphs-matrix/chartjs/client/actions.js create mode 100644 spikes/graphs-matrix/chartjs/client/chart.js create mode 100644 spikes/graphs-matrix/chartjs/client/index.js create mode 100644 spikes/graphs-matrix/chartjs/client/matrix.js create mode 100644 spikes/graphs-matrix/chartjs/client/root.js create mode 100644 spikes/graphs-matrix/chartjs/client/store.js create mode 100644 spikes/graphs-matrix/chartjs/package.json create mode 100644 spikes/graphs-matrix/chartjs/readme.md create mode 100644 spikes/graphs-matrix/chartjs/server/index.js create mode 100644 spikes/graphs-matrix/chartjs/server/metric.js create mode 100644 spikes/graphs-matrix/chartjs/server/plugins.js create mode 100644 spikes/graphs-matrix/chartjs/server/routes/home.js create mode 100644 spikes/graphs-matrix/chartjs/server/routes/metrics.js create mode 100644 spikes/graphs-matrix/chartjs/server/routes/static.js create mode 100644 spikes/graphs-matrix/chartjs/server/routes/version.js create mode 100644 spikes/graphs-matrix/chartjs/static/index.html create mode 100644 spikes/graphs-matrix/chartjs/webpack.config.js 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..4136fe1e --- /dev/null +++ b/spikes/graphs-matrix/chartjs/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/chartjs/client/chart.js b/spikes/graphs-matrix/chartjs/client/chart.js new file mode 100644 index 00000000..1aaf8c66 --- /dev/null +++ b/spikes/graphs-matrix/chartjs/client/chart.js @@ -0,0 +1,48 @@ +const buildArray = require('build-array'); +const Chart = require('chart.js'); +const React = require('react'); + +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 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 ( + + ); + } +}); diff --git a/spikes/graphs-matrix/chartjs/client/index.js b/spikes/graphs-matrix/chartjs/client/index.js new file mode 100644 index 00000000..34d1748c --- /dev/null +++ b/spikes/graphs-matrix/chartjs/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/chartjs/client/matrix.js b/spikes/graphs-matrix/chartjs/client/matrix.js new file mode 100644 index 00000000..84be991e --- /dev/null +++ b/spikes/graphs-matrix/chartjs/client/matrix.js @@ -0,0 +1,95 @@ +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 { + 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/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..452af806 --- /dev/null +++ b/spikes/graphs-matrix/chartjs/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/chartjs/package.json b/spikes/graphs-matrix/chartjs/package.json new file mode 100644 index 00000000..ed4bba13 --- /dev/null +++ b/spikes/graphs-matrix/chartjs/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", + "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.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/chartjs/readme.md b/spikes/graphs-matrix/chartjs/readme.md new file mode 100644 index 00000000..5172d589 --- /dev/null +++ b/spikes/graphs-matrix/chartjs/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/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..0d64fad2 --- /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 + }); + }, 400); + + 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/package.json b/spikes/graphs-matrix/epoch/package.json index f3c2dfbb..a3ed5cf4 100644 --- a/spikes/graphs-matrix/epoch/package.json +++ b/spikes/graphs-matrix/epoch/package.json @@ -1,5 +1,5 @@ { - "name": "epoch-graphing-spike", + "name": "epoch-matrix-spike", "private": true, "license": "private", "main": "server/index.js",