Merge pull request #590 from rafael-atticlabs/pitchmapJS2

Update pitchmap to js2
This commit is contained in:
Rafael Weinstein
2015-11-08 19:12:20 -08:00
10 changed files with 255 additions and 135 deletions

View File

@@ -16,6 +16,12 @@ before_script:
- npm run build
- npm test
- popd
- pushd clients/pitchmap/ui
- npm install
- ./link.sh
- npm run build
- npm test
- popd
script:
- export GODEBUG=invalidptr=0
- go test ./...

BIN
clients/pitchmap/index/index Executable file

Binary file not shown.

View File

@@ -0,0 +1,11 @@
{
"optional": ["asyncToGenerator"],
"blacklist": [
"es6.blockScoping",
"es6.constants",
"es6.forOf",
"es6.properties.computed",
"es6.properties.shorthand",
"es6.templateLiterals"
]
}

View File

@@ -0,0 +1,35 @@
{
"parser": "babel-eslint",
"rules": {
"array-bracket-spacing": [2, "never"],
"camelcase": 2,
"eqeqeq": 2,
"indent": [2, 2, {"SwitchCase": 1}],
"linebreak-style": [2, "unix"],
"no-multi-spaces": 2,
"no-new-wrappers": 2,
"no-throw-literal": 2,
"no-var": 2,
"object-curly-spacing": [2, "never"],
"quotes": [2, "single"],
"radix": 2,
"semi": 2,
"space-after-keywords": 2,
"space-before-function-paren": [2, "never"],
"space-infix-ops": 2,
"space-in-parens": [2, "never"]
},
"env": {
"es6": true,
"node": true,
"browser": true
},
"extends": "eslint:recommended",
"ecmaFeatures": {
"jsx": true,
"experimentalObjectRestSpread": true
},
"plugins": [
"react"
]
}

View File

@@ -0,0 +1,11 @@
[ignore]
.*/node_modules/react/.*
.*/node_modules/fbjs/flow/.*
[include]
[libs]
[options]
unsafe.enable_getters_and_setters=true
munge_underscores=true

View File

@@ -0,0 +1,117 @@
/* @flow */
'use strict';
import React from 'react'; //eslint-disable-line no-unused-lets
import {readValue, HttpStore, Ref, Struct} from 'noms';
const IMAGE_WIDTH_PX = 286;
const IMAGE_HEIGHT_PX = 324;
const BASE_PX = 72;
const BASE_FEET = 1 + 5 / 12;
const ORIGIN_X_PIXELS = IMAGE_WIDTH_PX / 2;
const ORIGIN_Z_PIXELS = IMAGE_HEIGHT_PX - 41;
function feetToPixels(f: number): number {
// TODO: Find more accurate image/dimensions.
return 0.8 * f * BASE_PX / BASE_FEET;
}
type Props = {
pitchListRef: Ref,
httpStore: HttpStore
};
type State = {
loaded: boolean,
pitchList: ?Array<Struct>
};
export default class HeatMap extends React.Component {
static defaultProps: {};
props: Props;
state: State;
constructor(props: Props) {
super(props);
this.state = {
loaded: false,
pitchList: null
};
}
setState(state: State) {
super.setState(state);
}
async loadIfNeeded(): Promise<void> {
if (this.state.loaded) {
return;
}
let pitchList = await readValue(this.props.pitchListRef, this.props.httpStore);
if (Array.isArray(pitchList)) {
this.setState({
loaded: true,
pitchList: pitchList
});
} else {
throw new Error('Unexpected type of pitchList');
}
}
render(): React.Element {
this.loadIfNeeded();
let points = this.getPoints();
let fillStyle = {
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0
};
return <div style={ {
position: 'relative',
overflow: 'hidden',
width: IMAGE_WIDTH_PX,
height: IMAGE_HEIGHT_PX
} }>
<img src="background.jpg" style={fillStyle}/>
<div style={fillStyle}>
{points}
</div>
</div>;
}
getPoints(): Array<any> {
if (!this.state.loaded) {
return [];
}
if (!this.state.pitchList) {
throw new Error('pitchList not loaded');
}
return this.state.pitchList.map(p => {
let w = 2;
let h = 2;
let x = - w / 2 + ORIGIN_X_PIXELS + feetToPixels(p.get('X'));
let y = - h / 2 + ORIGIN_Z_PIXELS - feetToPixels(p.get('Z'));
return <div style={
{
position: 'absolute',
left: x,
top: y,
background: 'rgba(0,255,0,0.4)',
width: w,
height: h,
boxShadow: '0px 0px 16px 16px rgba(0,255,0,0.4)',
borderRadius: '50%'
}
}/>;
});
}
}

View File

@@ -1,4 +1,5 @@
pushd ../../../js
pushd ../../../js2
npm install
npm run build
./link.sh
popd

View File

@@ -1,54 +1,81 @@
/* @flow */
'use strict';
var noms = require('noms');
var React = require('react');
var Map = require('./map.js');
import HeatMap from './heat_map.js';
import React from 'react'; //eslint-disable-line no-unused-lets
import ReactDOM from 'react-dom';
import {readValue, HttpStore, Ref} from 'noms';
noms.getRoot()
.then(ref => {
var pRoot = noms.readValue(ref, noms.getChunk);
return noms.getDataset(pRoot, 'mlb/heatmap');
})
.then(getPitchers)
.then(renderPitchersList);
let httpStore: HttpStore;
let renderNode: ?HTMLElement;
function getPitchers(datasetRootRef) {
return datasetRootRef.deref()
.then(datasetRoot => datasetRoot.get('value').deref());
}
window.addEventListener('load', async () => {
renderNode = document.getElementById('heatmap');
httpStore = new HttpStore('http://localhost:8000');
let rootRef = await httpStore.getRoot();
let datasets = await readValue(rootRef, httpStore);
let commitRef = datasets.get('mlb/heatmap');
let commit = await readValue(commitRef, httpStore);
let pitchersMap = commit.get('value');
renderPitchersMap(pitchersMap);
});
var PitcherList = React.createClass({
getInitialState() {
var pitchers = this.props.data.map((v, key) => key).toArray();
type Props = {
pitchersMap: Map<string, Ref>
};
type State = {
currentPitcher: string,
pitchers: Array<string>
};
class PitcherList extends React.Component {
static defaultProps: {};
props: Props;
state: State;
constructor(props) {
super(props);
let pitchers = [];
this.props.pitchersMap.forEach((ref, pitcher) => {
pitchers.push(pitcher);
});
pitchers.sort();
return {
this.state = {
currentPitcher: pitchers[0],
pitchers: pitchers
};
},
}
onChangePitcher(e) {
this.setState({ currentPitcher: e.target.value });
},
setState(state: State) {
super.setState(state);
}
render() {
var currentPitcher = this.state.currentPitcher;
var locations = this.props.data.get(currentPitcher);
let currentPitcher = this.state.currentPitcher;
let pitchListRef = this.props.pitchersMap.get(currentPitcher);
let onChangePitcher = e => {
this.setState({
currentPitcher: e.target.value,
pitchers: this.state.pitchers
});
};
return <div>
<select onChange={this.onChangePitcher} defaultValue={currentPitcher}>{
this.state.pitchers.map((pitcher) => {
var isCurrent = currentPitcher === pitcher;
return <option key={pitcher} value={pitcher}>{pitcher}</option>
<select onChange={onChangePitcher} defaultValue={currentPitcher}>{
this.state.pitchers.map(pitcher => {
return <option key={pitcher} value={pitcher}>{pitcher}</option>;
})
}</select>
<Map key={currentPitcher} points={locations}/>
</div>
<HeatMap key={currentPitcher} pitchListRef={pitchListRef} httpStore={httpStore}/>
</div>;
}
});
function renderPitchersList(list) {
var target = document.getElementById('heatmap');
React.render(<PitcherList data={list}/>, target);
}
function renderPitchersMap(map: Map) {
ReactDOM.render(<PitcherList pitchersMap={map}/>, renderNode);
}

View File

@@ -1,93 +0,0 @@
'use strict';
var React = require('react');
const IMAGE_WIDTH_PX = 286;
const IMAGE_HEIGHT_PX = 324;
const BASE_PX = 72;
const BASE_FEET = 1 + 5 / 12;
const FEETS_TO_PIXELS = BASE_PX / BASE_FEET;
const ORIGIN_X_PIXELS = IMAGE_WIDTH_PX / 2;
const ORIGIN_Z_PIXELS = IMAGE_HEIGHT_PX - 41;
function feetToPixels(f) {
// TODO: Find more accurate image/dimensions.
return 0.8 * f * BASE_PX / BASE_FEET;
}
var Map = React.createClass({
propTypes: {
points: React.PropTypes.object.isRequired,
},
loadIfNeeded() {
if (this.state.loaded) {
return;
}
this.props.points.deref().then((list) => {
return Promise.all(list.map(p => p.deref()))
}).then((points) => {
this.setState({
points: points,
loaded: true
});
});
},
getInitialState() {
return {
loaded: false,
points: null
};
},
render() {
this.loadIfNeeded();
var points = this.getPoints();
var fillStyle = {
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0,
};
return <div style={ {
position: 'relative',
overflow: 'hidden',
width: IMAGE_WIDTH_PX,
height: IMAGE_HEIGHT_PX,
} }>
<img src="background.jpg" style={fillStyle}/>
<div style={fillStyle}>
{points}
</div>
</div>;
},
getPoints: function() {
if (!this.state.loaded) {
return [];
}
return this.state.points.map((p) => {
var w = 2;
var h = 2;
var x = - w / 2 + ORIGIN_X_PIXELS + feetToPixels(p.get('X'));
var y = - h / 2 + ORIGIN_Z_PIXELS - feetToPixels(p.get('Z'));
return <div style={ {
position: 'absolute',
left: x,
top: y,
background: 'rgba(0,255,0,0.4)',
width: w,
height: h,
boxShadow: '0px 0px 16px 16px rgba(0,255,0,0.4)',
borderRadius: '50%',
} }/>;
});
},
});
module.exports = Map;

View File

@@ -1,17 +1,22 @@
{
"name": "noms-pitchmap-ui",
"dependencies": {
"react": "^0.12.0",
"immutable": "^3.7.4"
"react": "^0.14.1",
"react-dom": "^0.14.1"
},
"devDependencies": {
"babel": "^5.6.23",
"babel-eslint": "^4.1.3",
"babelify": "^6.1.3",
"browserify": "^6.2.0",
"uglify-js": "~2.4.15",
"watchify": "^2.1.1"
"browserify": "^12.0.1",
"eslint": "^1.7.3",
"eslint-plugin-react": "^3.6.3",
"flow-bin": "^0.18.1",
"watchify": "^3.6"
},
"scripts": {
"start": "watchify -o out.js -t babelify -v -d main.js",
"build": "NODE_ENV=production browserify main.js -t babelify | uglifyjs -cm > out.js"
"build": "NODE_ENV=production browserify main.js -t babelify > out.js",
"test": "rm -f out.js && eslint *.js && flow"
}
}