├── .gitignore ├── README.md ├── build ├── iris.csv └── main.js ├── gulpfile.js ├── index.html ├── package.json ├── react_example.py ├── requirements.txt └── scripts ├── barChart.js ├── iris.csv ├── main.js └── scatterPlot.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.pyc 3 | venv 4 | .eslintrc 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | flask_react_example 2 | ===================== 3 | 4 | A minimal example of an interactive scikit-learn model, using react and d3js. To run in a 5 | virtual environment: 6 | 7 | ``` 8 | $ virtualenv venv 9 | New python executable in venv/bin/python 10 | Installing setuptools, pip, wheel...done. 11 | $ source venv/bin/activate 12 | (venv)$ pip install -r requirements.txt 13 | ... 14 | (venv)$ python react_example.py 15 | * Running on http://127.0.0.1:5001/ (Press CTRL+C to quit) 16 | ``` 17 | then go to [localhost:5001](http://localhost:5001/). 18 | 19 | Just want to see it running? Go over to [here](http://reactdemo.colindcarroll.com/). 20 | -------------------------------------------------------------------------------- /build/iris.csv: -------------------------------------------------------------------------------- 1 | Sepal Length,Sepal Width,Petal Length,Petal Width,Species 2 | 5.1,3.5,1.4,0.2,setosa 3 | 4.9,3.0,1.4,0.2,setosa 4 | 4.7,3.2,1.3,0.2,setosa 5 | 4.6,3.1,1.5,0.2,setosa 6 | 5.0,3.6,1.4,0.2,setosa 7 | 5.4,3.9,1.7,0.4,setosa 8 | 4.6,3.4,1.4,0.3,setosa 9 | 5.0,3.4,1.5,0.2,setosa 10 | 4.4,2.9,1.4,0.2,setosa 11 | 4.9,3.1,1.5,0.1,setosa 12 | 5.4,3.7,1.5,0.2,setosa 13 | 4.8,3.4,1.6,0.2,setosa 14 | 4.8,3.0,1.4,0.1,setosa 15 | 4.3,3.0,1.1,0.1,setosa 16 | 5.8,4.0,1.2,0.2,setosa 17 | 5.7,4.4,1.5,0.4,setosa 18 | 5.4,3.9,1.3,0.4,setosa 19 | 5.1,3.5,1.4,0.3,setosa 20 | 5.7,3.8,1.7,0.3,setosa 21 | 5.1,3.8,1.5,0.3,setosa 22 | 5.4,3.4,1.7,0.2,setosa 23 | 5.1,3.7,1.5,0.4,setosa 24 | 4.6,3.6,1.0,0.2,setosa 25 | 5.1,3.3,1.7,0.5,setosa 26 | 4.8,3.4,1.9,0.2,setosa 27 | 5.0,3.0,1.6,0.2,setosa 28 | 5.0,3.4,1.6,0.4,setosa 29 | 5.2,3.5,1.5,0.2,setosa 30 | 5.2,3.4,1.4,0.2,setosa 31 | 4.7,3.2,1.6,0.2,setosa 32 | 4.8,3.1,1.6,0.2,setosa 33 | 5.4,3.4,1.5,0.4,setosa 34 | 5.2,4.1,1.5,0.1,setosa 35 | 5.5,4.2,1.4,0.2,setosa 36 | 4.9,3.1,1.5,0.1,setosa 37 | 5.0,3.2,1.2,0.2,setosa 38 | 5.5,3.5,1.3,0.2,setosa 39 | 4.9,3.1,1.5,0.1,setosa 40 | 4.4,3.0,1.3,0.2,setosa 41 | 5.1,3.4,1.5,0.2,setosa 42 | 5.0,3.5,1.3,0.3,setosa 43 | 4.5,2.3,1.3,0.3,setosa 44 | 4.4,3.2,1.3,0.2,setosa 45 | 5.0,3.5,1.6,0.6,setosa 46 | 5.1,3.8,1.9,0.4,setosa 47 | 4.8,3.0,1.4,0.3,setosa 48 | 5.1,3.8,1.6,0.2,setosa 49 | 4.6,3.2,1.4,0.2,setosa 50 | 5.3,3.7,1.5,0.2,setosa 51 | 5.0,3.3,1.4,0.2,setosa 52 | 7.0,3.2,4.7,1.4,versicolor 53 | 6.4,3.2,4.5,1.5,versicolor 54 | 6.9,3.1,4.9,1.5,versicolor 55 | 5.5,2.3,4.0,1.3,versicolor 56 | 6.5,2.8,4.6,1.5,versicolor 57 | 5.7,2.8,4.5,1.3,versicolor 58 | 6.3,3.3,4.7,1.6,versicolor 59 | 4.9,2.4,3.3,1.0,versicolor 60 | 6.6,2.9,4.6,1.3,versicolor 61 | 5.2,2.7,3.9,1.4,versicolor 62 | 5.0,2.0,3.5,1.0,versicolor 63 | 5.9,3.0,4.2,1.5,versicolor 64 | 6.0,2.2,4.0,1.0,versicolor 65 | 6.1,2.9,4.7,1.4,versicolor 66 | 5.6,2.9,3.6,1.3,versicolor 67 | 6.7,3.1,4.4,1.4,versicolor 68 | 5.6,3.0,4.5,1.5,versicolor 69 | 5.8,2.7,4.1,1.0,versicolor 70 | 6.2,2.2,4.5,1.5,versicolor 71 | 5.6,2.5,3.9,1.1,versicolor 72 | 5.9,3.2,4.8,1.8,versicolor 73 | 6.1,2.8,4.0,1.3,versicolor 74 | 6.3,2.5,4.9,1.5,versicolor 75 | 6.1,2.8,4.7,1.2,versicolor 76 | 6.4,2.9,4.3,1.3,versicolor 77 | 6.6,3.0,4.4,1.4,versicolor 78 | 6.8,2.8,4.8,1.4,versicolor 79 | 6.7,3.0,5.0,1.7,versicolor 80 | 6.0,2.9,4.5,1.5,versicolor 81 | 5.7,2.6,3.5,1.0,versicolor 82 | 5.5,2.4,3.8,1.1,versicolor 83 | 5.5,2.4,3.7,1.0,versicolor 84 | 5.8,2.7,3.9,1.2,versicolor 85 | 6.0,2.7,5.1,1.6,versicolor 86 | 5.4,3.0,4.5,1.5,versicolor 87 | 6.0,3.4,4.5,1.6,versicolor 88 | 6.7,3.1,4.7,1.5,versicolor 89 | 6.3,2.3,4.4,1.3,versicolor 90 | 5.6,3.0,4.1,1.3,versicolor 91 | 5.5,2.5,4.0,1.3,versicolor 92 | 5.5,2.6,4.4,1.2,versicolor 93 | 6.1,3.0,4.6,1.4,versicolor 94 | 5.8,2.6,4.0,1.2,versicolor 95 | 5.0,2.3,3.3,1.0,versicolor 96 | 5.6,2.7,4.2,1.3,versicolor 97 | 5.7,3.0,4.2,1.2,versicolor 98 | 5.7,2.9,4.2,1.3,versicolor 99 | 6.2,2.9,4.3,1.3,versicolor 100 | 5.1,2.5,3.0,1.1,versicolor 101 | 5.7,2.8,4.1,1.3,versicolor 102 | 6.3,3.3,6.0,2.5,virginica 103 | 5.8,2.7,5.1,1.9,virginica 104 | 7.1,3.0,5.9,2.1,virginica 105 | 6.3,2.9,5.6,1.8,virginica 106 | 6.5,3.0,5.8,2.2,virginica 107 | 7.6,3.0,6.6,2.1,virginica 108 | 4.9,2.5,4.5,1.7,virginica 109 | 7.3,2.9,6.3,1.8,virginica 110 | 6.7,2.5,5.8,1.8,virginica 111 | 7.2,3.6,6.1,2.5,virginica 112 | 6.5,3.2,5.1,2.0,virginica 113 | 6.4,2.7,5.3,1.9,virginica 114 | 6.8,3.0,5.5,2.1,virginica 115 | 5.7,2.5,5.0,2.0,virginica 116 | 5.8,2.8,5.1,2.4,virginica 117 | 6.4,3.2,5.3,2.3,virginica 118 | 6.5,3.0,5.5,1.8,virginica 119 | 7.7,3.8,6.7,2.2,virginica 120 | 7.7,2.6,6.9,2.3,virginica 121 | 6.0,2.2,5.0,1.5,virginica 122 | 6.9,3.2,5.7,2.3,virginica 123 | 5.6,2.8,4.9,2.0,virginica 124 | 7.7,2.8,6.7,2.0,virginica 125 | 6.3,2.7,4.9,1.8,virginica 126 | 6.7,3.3,5.7,2.1,virginica 127 | 7.2,3.2,6.0,1.8,virginica 128 | 6.2,2.8,4.8,1.8,virginica 129 | 6.1,3.0,4.9,1.8,virginica 130 | 6.4,2.8,5.6,2.1,virginica 131 | 7.2,3.0,5.8,1.6,virginica 132 | 7.4,2.8,6.1,1.9,virginica 133 | 7.9,3.8,6.4,2.0,virginica 134 | 6.4,2.8,5.6,2.2,virginica 135 | 6.3,2.8,5.1,1.5,virginica 136 | 6.1,2.6,5.6,1.4,virginica 137 | 7.7,3.0,6.1,2.3,virginica 138 | 6.3,3.4,5.6,2.4,virginica 139 | 6.4,3.1,5.5,1.8,virginica 140 | 6.0,3.0,4.8,1.8,virginica 141 | 6.9,3.1,5.4,2.1,virginica 142 | 6.7,3.1,5.6,2.4,virginica 143 | 6.9,3.1,5.1,2.3,virginica 144 | 5.8,2.7,5.1,1.9,virginica 145 | 6.8,3.2,5.9,2.3,virginica 146 | 6.7,3.3,5.7,2.5,virginica 147 | 6.7,3.0,5.2,2.3,virginica 148 | 6.3,2.5,5.0,1.9,virginica 149 | 6.5,3.0,5.2,2.0,virginica 150 | 6.2,3.4,5.4,2.3,virginica 151 | 5.9,3.0,5.1,1.8,virginica 152 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var source = require('vinyl-source-stream'); 2 | var gulp = require('gulp'); 3 | var gutil = require('gulp-util'); 4 | var browserify = require('browserify'); 5 | var babelify = require('babelify'); 6 | var watchify = require('watchify'); 7 | var notify = require('gulp-notify'); 8 | 9 | var stylus = require('gulp-stylus'); 10 | var autoprefixer = require('gulp-autoprefixer'); 11 | var uglify = require('gulp-uglify'); 12 | var rename = require('gulp-rename'); 13 | var buffer = require('vinyl-buffer'); 14 | 15 | var browserSync = require('browser-sync'); 16 | var reload = browserSync.reload; 17 | var historyApiFallback = require('connect-history-api-fallback') 18 | 19 | 20 | /* 21 | Styles Task 22 | */ 23 | 24 | gulp.task('styles',function() { 25 | // move over fonts 26 | 27 | gulp.src('css/fonts/**.*') 28 | .pipe(gulp.dest('build/css/fonts')) 29 | 30 | // Compiles CSS 31 | gulp.src('css/style.styl') 32 | .pipe(stylus()) 33 | .pipe(autoprefixer()) 34 | .pipe(gulp.dest('./build/css/')) 35 | .pipe(reload({stream:true})) 36 | }); 37 | 38 | /* 39 | Images 40 | */ 41 | gulp.task('images',function(){ 42 | gulp.src('css/images/**') 43 | .pipe(gulp.dest('./build/css/images')) 44 | }); 45 | 46 | /* 47 | Browser Sync 48 | */ 49 | gulp.task('browser-sync', function() { 50 | browserSync({ 51 | // we need to disable clicks and forms for when we test multiple rooms 52 | server : {}, 53 | middleware : [ historyApiFallback() ], 54 | ghostMode: false 55 | }); 56 | }); 57 | 58 | function handleErrors() { 59 | var args = Array.prototype.slice.call(arguments); 60 | notify.onError({ 61 | title: 'Compile Error', 62 | message: '<%= error.message %>' 63 | }).apply(this, args); 64 | this.emit('end'); // Keep gulp from hanging on this task 65 | } 66 | 67 | function buildScript(file, watch) { 68 | var props = { 69 | entries: ['./scripts/' + file], 70 | debug : true, 71 | transform: [babelify.configure({stage : 0 })] 72 | }; 73 | 74 | // watchify() if watch requested, otherwise run browserify() once 75 | var bundler = watch ? watchify(browserify(props)) : browserify(props); 76 | 77 | function rebundle() { 78 | var stream = bundler.bundle(); 79 | return stream 80 | .on('error', handleErrors) 81 | .pipe(source(file)) 82 | .pipe(gulp.dest('./build/')) 83 | // If you also want to uglify it 84 | // .pipe(buffer()) 85 | // .pipe(uglify()) 86 | // .pipe(rename('app.min.js')) 87 | // .pipe(gulp.dest('./build')) 88 | .pipe(reload({stream:true})) 89 | } 90 | 91 | // listen for an update and run rebundle 92 | bundler.on('update', function() { 93 | rebundle(); 94 | gutil.log('Rebundle...'); 95 | }); 96 | 97 | // run it once the first time buildScript is called 98 | return rebundle(); 99 | } 100 | 101 | gulp.task('scripts', function() { 102 | return buildScript('main.js', false); // this will run once because we set watch to false 103 | }); 104 | 105 | // run 'scripts' task first, then watch for future changes 106 | gulp.task('default', ['images','styles','scripts','browser-sync'], function() { 107 | gulp.watch('css/**/*', ['styles']); // gulp watch for stylus changes 108 | return buildScript('main.js', true); // browserify watch for JS changes 109 | }); 110 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sample Flask React App 7 | 8 | 9 | 22 | 23 | 24 | 25 | Fork me on GitHub 26 | 27 |
28 |
29 | 30 | 31 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flask-react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "autobind-decorator": "1.3.2", 13 | "babel": "5.8.23", 14 | "babelify": "6.4.0", 15 | "bootstrap-sass": "^3.3.5", 16 | "browser-sync": "2.9.11", 17 | "browserify": "11.2.0", 18 | "connect-history-api-fallback": "1.1.0", 19 | "d3": "^3.5.6", 20 | "gulp": "3.9.0", 21 | "gulp-autoprefixer": "3.1.0", 22 | "gulp-notify": "2.2.0", 23 | "gulp-rename": "1.2.2", 24 | "gulp-stylus": "2.1.0", 25 | "gulp-uglify": "1.4.2", 26 | "gulp-util": "3.0.7", 27 | "history": "1.12.5", 28 | "install": "0.1.8", 29 | "jquery": "^2.1.4", 30 | "re-base": "1.2.1", 31 | "react": "0.14.0", 32 | "react-addons-css-transition-group": "0.14.0", 33 | "react-catalyst": "0.3.0", 34 | "react-dom": "0.14.0", 35 | "react-mixin": "3.0.0", 36 | "react-router": "1.0.0-rc3", 37 | "vinyl-buffer": "1.0.0", 38 | "vinyl-source-stream": "1.1.0", 39 | "watchify": "3.4.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /react_example.py: -------------------------------------------------------------------------------- 1 | import os 2 | from sklearn import datasets 3 | from sklearn.ensemble import RandomForestClassifier 4 | from flask import Flask, make_response, jsonify, request 5 | 6 | DIR = os.path.dirname(os.path.abspath(__file__)) 7 | app = Flask(__name__, static_folder='build') 8 | 9 | 10 | def get_model(): 11 | iris = datasets.load_iris() 12 | model = RandomForestClassifier(n_estimators=1000).fit(iris.data, iris.target) 13 | labels = list(iris.target_names) 14 | return model, labels 15 | 16 | 17 | MODEL, LABELS = get_model() 18 | 19 | 20 | @app.route('/') 21 | def index(): 22 | return make_response(open(os.path.join(DIR, 'index.html')).read()) 23 | 24 | 25 | @app.route('/api/predict') 26 | def predict(): 27 | def getter(label): 28 | return float(request.args.get(label, 0)) 29 | try: 30 | features = map(getter, ['sepalLength', 'sepalWidth', 'petalLength', 'petalWidth']) 31 | probs = MODEL.predict_proba(features)[0] 32 | except ValueError: 33 | probs = (1. / len(LABELS) for _ in LABELS) 34 | 35 | val = {"data": [{"label": label, "prob": prob} for label, prob in zip(LABELS, probs)]} 36 | return jsonify(val) 37 | 38 | 39 | if __name__ == '__main__': 40 | app.run(port=5001) 41 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.0 2 | itsdangerous==0.24 3 | Jinja2==2.11.3 4 | MarkupSafe==0.23 5 | numpy==1.22.0 6 | scikit-learn==0.16.1 7 | scipy==0.16.1 8 | sklearn==0.0 9 | Werkzeug==2.2.3 10 | wheel==0.38.1 11 | -------------------------------------------------------------------------------- /scripts/barChart.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | var $ = require('jquery'); 3 | var ns = {} 4 | 5 | ns.create = function(el, props){ 6 | var svg = d3.select(el) 7 | .append('svg') 8 | .attr('width', props.width) 9 | .attr('height', props.height) 10 | .attr('class', 'barChart'); 11 | 12 | var h = this._helpers(el, { 13 | width: props.width, 14 | height: props.height 15 | }) 16 | 17 | $.get(this._url(props), function(data){ 18 | svg.selectAll('rect') 19 | .data(data.data, function(d){ return d.label;}) 20 | .enter() 21 | .append('rect') 22 | .attr('width', h.barWidth) 23 | .attr('fill', function(d){return h.c(d.label)}) 24 | .attr('x', function(d, i) { return i * (h.barWidth + h.barPadding) }) 25 | .attr('height', 0) 26 | .attr('y', h.height) 27 | .transition() 28 | .duration(h.duration) 29 | .ease(h.ease) 30 | .attr('height', function(d){return h.height - h.y(d.prob)}) 31 | .attr('y', function(d){return h.y(d.prob)}); 32 | 33 | var text = svg.selectAll('text') 34 | .data(data.data, function(d){ return d.label; }) 35 | 36 | text.enter() 37 | .append('text') 38 | .attr('x', function(d, i) { return i * (h.barWidth + h.barPadding) }) 39 | .attr('dx', h.barWidth/2) 40 | .attr('dy', '1.2em') 41 | .attr('text-anchor', 'middle') 42 | .text(function(d) {return (100 * d.prob).toFixed(1) + '%';}) 43 | .attr('fill', 'white') 44 | .attr('y', h.height) 45 | .transition() 46 | .duration(h.duration) 47 | .ease(h.ease) 48 | .attr('y', function(d){ return h.y(d.prob)}); 49 | 50 | text.enter() 51 | .append('text') 52 | .attr('y', 10 + h.height) 53 | .attr('x', function(d, i) { return i * (h.barWidth + h.barPadding);}) 54 | .attr('dx', h.barWidth/2) 55 | .attr('text-anchor', 'middle') 56 | .attr('fill', 'black') 57 | .text(function(d) { return d.label;}); 58 | }) 59 | } 60 | 61 | ns._helpers = function(el, props){ 62 | var margin = {top: 0, right: 20, bottom: 30, left: 40}, 63 | width = props.width - margin.left - margin.right, 64 | height = props.height - margin.top - margin.bottom, 65 | yScale = d3.scale.linear() 66 | .domain([0, 1]) 67 | .range([height, 0]), 68 | colorScale = d3.scale.category10(), 69 | barWidth = width / 3; 70 | 71 | return { 72 | y: yScale, 73 | c: colorScale, 74 | width: width, 75 | height: height, 76 | barWidth: barWidth, 77 | barPadding: 2, 78 | duration: 300, 79 | ease: 'linear' 80 | } 81 | } 82 | 83 | ns._url = function(props) { 84 | return ('/api/predict?' + 85 | 'sepalLength=' + props.features['Sepal Length'] + '&' + 86 | 'sepalWidth=' + props.features['Sepal Width'] + '&' + 87 | 'petalLength=' + props.features['Petal Length'] + '&' + 88 | 'petalWidth=' + props.features['Petal Width']); 89 | } 90 | 91 | ns.update = function(el, props){ 92 | var h = this._helpers(el, { 93 | width: props.width, 94 | height: props.height 95 | }) 96 | 97 | $.get(this._url(props), function(data){ 98 | var svg = d3.select(el).select('.barChart'); 99 | svg.selectAll('rect') 100 | .data(data.data, function(d){ return d.label;}) 101 | .transition() 102 | .duration(h.duration) 103 | .ease(h.ease) 104 | .attr('height', function(d){return h.height - h.y(d.prob)}) 105 | .attr('y', function(d){return h.y(d.prob)}); 106 | 107 | svg.selectAll('text') 108 | .data(data.data, function(d){ return d.label; }) 109 | .transition() 110 | .duration(h.duration) 111 | .ease(h.ease) 112 | .text(function(d) {return (100 * d.prob).toFixed(1) + '%';}) 113 | .attr('y', function(d){ return h.y(d.prob)}); 114 | }) 115 | } 116 | 117 | export default ns; 118 | -------------------------------------------------------------------------------- /scripts/iris.csv: -------------------------------------------------------------------------------- 1 | Sepal Length,Sepal Width,Petal Length,Petal Width,Species 2 | 5.1,3.5,1.4,0.2,setosa 3 | 4.9,3.0,1.4,0.2,setosa 4 | 4.7,3.2,1.3,0.2,setosa 5 | 4.6,3.1,1.5,0.2,setosa 6 | 5.0,3.6,1.4,0.2,setosa 7 | 5.4,3.9,1.7,0.4,setosa 8 | 4.6,3.4,1.4,0.3,setosa 9 | 5.0,3.4,1.5,0.2,setosa 10 | 4.4,2.9,1.4,0.2,setosa 11 | 4.9,3.1,1.5,0.1,setosa 12 | 5.4,3.7,1.5,0.2,setosa 13 | 4.8,3.4,1.6,0.2,setosa 14 | 4.8,3.0,1.4,0.1,setosa 15 | 4.3,3.0,1.1,0.1,setosa 16 | 5.8,4.0,1.2,0.2,setosa 17 | 5.7,4.4,1.5,0.4,setosa 18 | 5.4,3.9,1.3,0.4,setosa 19 | 5.1,3.5,1.4,0.3,setosa 20 | 5.7,3.8,1.7,0.3,setosa 21 | 5.1,3.8,1.5,0.3,setosa 22 | 5.4,3.4,1.7,0.2,setosa 23 | 5.1,3.7,1.5,0.4,setosa 24 | 4.6,3.6,1.0,0.2,setosa 25 | 5.1,3.3,1.7,0.5,setosa 26 | 4.8,3.4,1.9,0.2,setosa 27 | 5.0,3.0,1.6,0.2,setosa 28 | 5.0,3.4,1.6,0.4,setosa 29 | 5.2,3.5,1.5,0.2,setosa 30 | 5.2,3.4,1.4,0.2,setosa 31 | 4.7,3.2,1.6,0.2,setosa 32 | 4.8,3.1,1.6,0.2,setosa 33 | 5.4,3.4,1.5,0.4,setosa 34 | 5.2,4.1,1.5,0.1,setosa 35 | 5.5,4.2,1.4,0.2,setosa 36 | 4.9,3.1,1.5,0.1,setosa 37 | 5.0,3.2,1.2,0.2,setosa 38 | 5.5,3.5,1.3,0.2,setosa 39 | 4.9,3.1,1.5,0.1,setosa 40 | 4.4,3.0,1.3,0.2,setosa 41 | 5.1,3.4,1.5,0.2,setosa 42 | 5.0,3.5,1.3,0.3,setosa 43 | 4.5,2.3,1.3,0.3,setosa 44 | 4.4,3.2,1.3,0.2,setosa 45 | 5.0,3.5,1.6,0.6,setosa 46 | 5.1,3.8,1.9,0.4,setosa 47 | 4.8,3.0,1.4,0.3,setosa 48 | 5.1,3.8,1.6,0.2,setosa 49 | 4.6,3.2,1.4,0.2,setosa 50 | 5.3,3.7,1.5,0.2,setosa 51 | 5.0,3.3,1.4,0.2,setosa 52 | 7.0,3.2,4.7,1.4,versicolor 53 | 6.4,3.2,4.5,1.5,versicolor 54 | 6.9,3.1,4.9,1.5,versicolor 55 | 5.5,2.3,4.0,1.3,versicolor 56 | 6.5,2.8,4.6,1.5,versicolor 57 | 5.7,2.8,4.5,1.3,versicolor 58 | 6.3,3.3,4.7,1.6,versicolor 59 | 4.9,2.4,3.3,1.0,versicolor 60 | 6.6,2.9,4.6,1.3,versicolor 61 | 5.2,2.7,3.9,1.4,versicolor 62 | 5.0,2.0,3.5,1.0,versicolor 63 | 5.9,3.0,4.2,1.5,versicolor 64 | 6.0,2.2,4.0,1.0,versicolor 65 | 6.1,2.9,4.7,1.4,versicolor 66 | 5.6,2.9,3.6,1.3,versicolor 67 | 6.7,3.1,4.4,1.4,versicolor 68 | 5.6,3.0,4.5,1.5,versicolor 69 | 5.8,2.7,4.1,1.0,versicolor 70 | 6.2,2.2,4.5,1.5,versicolor 71 | 5.6,2.5,3.9,1.1,versicolor 72 | 5.9,3.2,4.8,1.8,versicolor 73 | 6.1,2.8,4.0,1.3,versicolor 74 | 6.3,2.5,4.9,1.5,versicolor 75 | 6.1,2.8,4.7,1.2,versicolor 76 | 6.4,2.9,4.3,1.3,versicolor 77 | 6.6,3.0,4.4,1.4,versicolor 78 | 6.8,2.8,4.8,1.4,versicolor 79 | 6.7,3.0,5.0,1.7,versicolor 80 | 6.0,2.9,4.5,1.5,versicolor 81 | 5.7,2.6,3.5,1.0,versicolor 82 | 5.5,2.4,3.8,1.1,versicolor 83 | 5.5,2.4,3.7,1.0,versicolor 84 | 5.8,2.7,3.9,1.2,versicolor 85 | 6.0,2.7,5.1,1.6,versicolor 86 | 5.4,3.0,4.5,1.5,versicolor 87 | 6.0,3.4,4.5,1.6,versicolor 88 | 6.7,3.1,4.7,1.5,versicolor 89 | 6.3,2.3,4.4,1.3,versicolor 90 | 5.6,3.0,4.1,1.3,versicolor 91 | 5.5,2.5,4.0,1.3,versicolor 92 | 5.5,2.6,4.4,1.2,versicolor 93 | 6.1,3.0,4.6,1.4,versicolor 94 | 5.8,2.6,4.0,1.2,versicolor 95 | 5.0,2.3,3.3,1.0,versicolor 96 | 5.6,2.7,4.2,1.3,versicolor 97 | 5.7,3.0,4.2,1.2,versicolor 98 | 5.7,2.9,4.2,1.3,versicolor 99 | 6.2,2.9,4.3,1.3,versicolor 100 | 5.1,2.5,3.0,1.1,versicolor 101 | 5.7,2.8,4.1,1.3,versicolor 102 | 6.3,3.3,6.0,2.5,virginica 103 | 5.8,2.7,5.1,1.9,virginica 104 | 7.1,3.0,5.9,2.1,virginica 105 | 6.3,2.9,5.6,1.8,virginica 106 | 6.5,3.0,5.8,2.2,virginica 107 | 7.6,3.0,6.6,2.1,virginica 108 | 4.9,2.5,4.5,1.7,virginica 109 | 7.3,2.9,6.3,1.8,virginica 110 | 6.7,2.5,5.8,1.8,virginica 111 | 7.2,3.6,6.1,2.5,virginica 112 | 6.5,3.2,5.1,2.0,virginica 113 | 6.4,2.7,5.3,1.9,virginica 114 | 6.8,3.0,5.5,2.1,virginica 115 | 5.7,2.5,5.0,2.0,virginica 116 | 5.8,2.8,5.1,2.4,virginica 117 | 6.4,3.2,5.3,2.3,virginica 118 | 6.5,3.0,5.5,1.8,virginica 119 | 7.7,3.8,6.7,2.2,virginica 120 | 7.7,2.6,6.9,2.3,virginica 121 | 6.0,2.2,5.0,1.5,virginica 122 | 6.9,3.2,5.7,2.3,virginica 123 | 5.6,2.8,4.9,2.0,virginica 124 | 7.7,2.8,6.7,2.0,virginica 125 | 6.3,2.7,4.9,1.8,virginica 126 | 6.7,3.3,5.7,2.1,virginica 127 | 7.2,3.2,6.0,1.8,virginica 128 | 6.2,2.8,4.8,1.8,virginica 129 | 6.1,3.0,4.9,1.8,virginica 130 | 6.4,2.8,5.6,2.1,virginica 131 | 7.2,3.0,5.8,1.6,virginica 132 | 7.4,2.8,6.1,1.9,virginica 133 | 7.9,3.8,6.4,2.0,virginica 134 | 6.4,2.8,5.6,2.2,virginica 135 | 6.3,2.8,5.1,1.5,virginica 136 | 6.1,2.6,5.6,1.4,virginica 137 | 7.7,3.0,6.1,2.3,virginica 138 | 6.3,3.4,5.6,2.4,virginica 139 | 6.4,3.1,5.5,1.8,virginica 140 | 6.0,3.0,4.8,1.8,virginica 141 | 6.9,3.1,5.4,2.1,virginica 142 | 6.7,3.1,5.6,2.4,virginica 143 | 6.9,3.1,5.1,2.3,virginica 144 | 5.8,2.7,5.1,1.9,virginica 145 | 6.8,3.2,5.9,2.3,virginica 146 | 6.7,3.3,5.7,2.5,virginica 147 | 6.7,3.0,5.2,2.3,virginica 148 | 6.3,2.5,5.0,1.9,virginica 149 | 6.5,3.0,5.2,2.0,virginica 150 | 6.2,3.4,5.4,2.3,virginica 151 | 5.9,3.0,5.1,1.8,virginica 152 | -------------------------------------------------------------------------------- /scripts/main.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactDom = require('react-dom'); 3 | 4 | var Catalyst = require('react-catalyst'); 5 | var ScatterPlot = require('./scatterPlot'); 6 | var Bar = require('./barChart'); 7 | 8 | /* 9 | * App 10 | */ 11 | var App = React.createClass({ 12 | mixins: [Catalyst.LinkedStateMixin], 13 | getInitialState: function(){ 14 | var domain = { 15 | 'Sepal Width': [1.0, 5.0], 16 | 'Sepal Length': [3.5, 8.5], 17 | 'Petal Width': [0.0, 3.5], 18 | 'Petal Length': [0.0, 7.5] 19 | } 20 | var dimensions= Object.keys(domain); 21 | var features = {}; 22 | for(var j=0; j < dimensions.length; j++){ 23 | var key = dimensions[j]; 24 | features[key] = 0.5 * (domain[key][0] + domain[key][1]) ; 25 | } 26 | return { 27 | domain: domain, 28 | features: features 29 | } 30 | }, 31 | render: function(){ 32 | return ( 33 |
34 |
35 |
36 |
37 | 39 |
40 | 41 | 43 |
44 |
45 |
46 |
47 | )} 48 | }); 49 | 50 | /* 51 | * Header 52 | */ 53 | var Header = React.createClass({ 54 | render: function(){ 55 | return( 56 |
57 |
58 |

Interactive Models with Flask and ReactJS

59 |

Illustrating how to display an interactive model using the Python Flask framework and ReactJS. We use the famous iris dataset to train a random forest in scikit-learn, and put up an interactive dashboard giving predictions. 60 |

61 |
62 |
63 | ) 64 | } 65 | }); 66 | 67 | /* 68 | * Features 69 | */ 70 | var Features = React.createClass({ 71 | renderFeature: function(key){ 72 | var domain = this.props.domain[key]; 73 | var linkState = this.props.linkState; 74 | return ( 75 |
76 | 77 |
78 | 81 |
82 |
83 | ) 84 | }, 85 | render: function(){ 86 | return( 87 |
88 |

Features

89 |
90 | {Object.keys(this.props.domain).map(this.renderFeature)} 91 |
92 |
93 | ) 94 | } 95 | }); 96 | 97 | /* 98 | * BarChart 99 | */ 100 | var BarChart = React.createClass({ 101 | componentDidMount: function() { 102 | var el = ReactDom.findDOMNode(this); 103 | Bar.create(el, { 104 | features: this.props.features, 105 | width: '450', 106 | height: '400'}); 107 | }, 108 | componentDidUpdate: function() { 109 | var el = ReactDom.findDOMNode(this); 110 | Bar.update(el, { 111 | features: this.props.features, 112 | width: '450', 113 | height: '400'}); 114 | }, 115 | render: function(){ 116 | return( 117 |
118 |

Class Probabilities

119 |
120 | ) 121 | } 122 | }); 123 | 124 | 125 | /* 126 | * ScatterNav 127 | */ 128 | var ScatterNav = React.createClass({ 129 | render: function(){ 130 | return( 131 |
132 | 135 | 136 | 139 |
140 | ) 141 | } 142 | }); 143 | 144 | /* 145 | * Scatter 146 | */ 147 | var Scatter = React.createClass({ 148 | componentDidMount: function() { 149 | var el = ReactDom.findDOMNode(this); 150 | ScatterPlot.create( 151 | el, { 152 | width: this.props.width, 153 | height: this.props.height, 154 | domain: this.props.domain, 155 | xVal: this.props.xVal, 156 | yVal: this.props.yVal, 157 | legend: this.props.legend, 158 | features: this.props.features 159 | }) 160 | }, 161 | componentDidUpdate: function() { 162 | var el = ReactDom.findDOMNode(this); 163 | ScatterPlot.update( 164 | el, { 165 | width: this.props.width, 166 | height: this.props.height, 167 | domain: this.props.domain, 168 | xVal: this.props.xVal, 169 | yVal: this.props.yVal, 170 | features: this.props.features 171 | }) 172 | }, 173 | render: function(){ 174 | return 175 | } 176 | }) 177 | 178 | ReactDom.render(, document.querySelector('#main')) 179 | -------------------------------------------------------------------------------- /scripts/scatterPlot.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | var ns = {}; 3 | 4 | var plotPosition = function(el, features, xMap, yMap){ 5 | d3.select(el).select('svg') 6 | .selectAll('.position') 7 | .data([features]) 8 | .enter().append('circle') 9 | .attr('class', 'position') 10 | .attr('r', 2.5) 11 | .attr('cx', xMap) 12 | .attr('cy', yMap) 13 | .style('fill', 'black'); 14 | } 15 | 16 | var updatePosition = function(el, features, xMap, yMap){ 17 | d3.select(el).select('svg') 18 | .selectAll('.position') 19 | .data([features]) 20 | .attr('class', 'position') 21 | .attr('r', 2.5) 22 | .attr('cx', xMap) 23 | .attr('cy', yMap) 24 | .style('fill', 'black'); 25 | } 26 | 27 | ns.create = function(el, props) { 28 | var svg = d3.select(el) 29 | .append('svg') 30 | .attr('width', props.width) 31 | .attr('height', props.height) 32 | .attr('class', 'scatterChart'); 33 | 34 | var h = this._helpers(el, { 35 | domain: props.domain, 36 | xVal: props.xVal, 37 | yVal: props.yVal, 38 | width: props.width, 39 | height: props.height 40 | }); 41 | 42 | this._drawAxis(svg, { 43 | scales: h.scales, 44 | width: h.width, 45 | height: h.height, 46 | xVal: props.xVal, 47 | yVal: props.yVal 48 | }); 49 | 50 | this._drawPoints(svg, { 51 | scales: h.scales, 52 | xVal: props.xVal, 53 | yVal: props.yVal, 54 | x: h.x, 55 | y: h.y, 56 | legend: props.legend 57 | }); 58 | 59 | plotPosition(el, props.features, h.x, h.y) 60 | } 61 | 62 | ns.update = function(el, props){ 63 | var h = this._helpers(el, props) 64 | updatePosition(el, props.features, h.x, h.y) 65 | } 66 | 67 | ns._helpers = function(el, props) { 68 | var margin = {top: 20, right: 30, bottom: 30, left: 40}; 69 | 70 | var xVal = props.xVal, 71 | yVal = props.yVal, 72 | domain = { 73 | x: props.domain[xVal], 74 | y: props.domain[yVal]}, 75 | height = props.height - margin.top - margin.bottom, 76 | width = props.width - margin.left - margin.right, 77 | x = d3.scale.linear() 78 | .domain(domain.x) 79 | .range([0, width]), 80 | y = d3.scale.linear() 81 | .domain(domain.y) 82 | .range([height, 0]), 83 | c = d3.scale.category10(), 84 | xMap = function(d){ return x(d[xVal])}, 85 | yMap = function(d){ return y(d[yVal])}; 86 | 87 | return { 88 | scales: { 89 | x: x, 90 | y: y, 91 | c: c}, 92 | x: xMap, 93 | y: yMap, 94 | height: height, 95 | width: width 96 | } 97 | } 98 | 99 | ns._drawAxis = function(svg, props) { 100 | var xAxis = d3.svg.axis().scale(props.scales.x).orient('bottom'), 101 | yAxis = d3.svg.axis().scale(props.scales.y).orient('right'); 102 | 103 | // x-axis 104 | svg.append('g') 105 | .attr('class', 'x axis') 106 | .attr('transform', 'translate(0,' + props.height + ')') 107 | .call(xAxis) 108 | .append('text') 109 | .attr('class', 'label') 110 | .attr('x', props.width - 5) 111 | .attr('y', -2) 112 | .style('text-anchor', 'end') 113 | .text(props.xVal); 114 | 115 | // y-axis 116 | svg.append('g') 117 | .attr('class', 'y axis') 118 | .attr('transform', 'translate(' + props.width + ', 0)') 119 | .call(yAxis) 120 | .append('text') 121 | .attr('class', 'label') 122 | .attr('transform', 'rotate(-90)') 123 | .attr('y', 6) 124 | .attr('dy', '-1.2em') 125 | .style('text-anchor', 'end') 126 | .text(props.yVal); 127 | } 128 | 129 | ns._drawPoints = function(svg, props) { 130 | d3.csv('/build/iris.csv', function(data) { 131 | 132 | data.forEach(function(d) { 133 | d[props.xVal] = +d[props.xVal]; 134 | d[props.yVal] = +d[props.yVal]; 135 | }); 136 | 137 | // draw dots 138 | svg.selectAll('.dot') 139 | .data(data) 140 | .enter().append('circle') 141 | .attr('class', 'dot') 142 | .attr('r', 2.5) 143 | .attr('cx', props.x) 144 | .attr('cy', props.y) 145 | .style('opacity', 0.6) 146 | .style('fill', function(d) { return props.scales.c(d.Species);}); 147 | 148 | if(props.legend){ 149 | var legend = svg.selectAll('.legend') 150 | .data(props.scales.c.domain()) 151 | .enter().append('g') 152 | .attr('class', 'legend') 153 | .attr('transform', function(d, i) { 154 | return 'translate(0,' + i * 13 + ')'; 155 | }); 156 | 157 | legend.append('rect') 158 | .attr('x', 10) 159 | .attr('width', 10) 160 | .attr('height', 10) 161 | .style('fill', props.scales.c); 162 | 163 | legend.append('text') 164 | .attr('x', 24) 165 | .attr('y', 5) 166 | .attr('dy', '.25em') 167 | .style('text-anchor', 'begin') 168 | .text(function(d) { return d;}) 169 | } 170 | }); 171 | } 172 | 173 | export default ns; 174 | --------------------------------------------------------------------------------