CrunchBase UI: Hook up the data to the backend

This changes the indexer to only index what we need since without
chunked maps there is too much data to transfer and decode.

The graph is also changed to a line graph since using a bar chart
for this is not very intuitive.
This commit is contained in:
Erik Arvidsson
2015-11-25 16:17:42 -05:00
parent 13c7eeeb8c
commit b72d7cb323
23 changed files with 610 additions and 263 deletions
+6
View File
@@ -22,6 +22,12 @@ before_script:
- npm run build
- npm test
- popd
- pushd clients/crunchbase/ui
- npm install
- ./link.sh
- npm run build
- npm test
- popd
script:
- export GODEBUG=invalidptr=0
- go test ./...
+40 -3
View File
@@ -68,13 +68,32 @@ func main() {
c <- entry{qk, roundRaiseDef}
}
// Compute a cutoff date which is later used to only include rounds after this date to reduce the amount of data.
now := time.Now()
currentYear := time.Date(now.Year(), time.January, 1, 0, 0, 0, 0, time.UTC)
lastQ := lastQuarter(now)
var timeCutoff time.Time
if currentYear.Before(lastQ) {
timeCutoff = currentYear
} else {
timeCutoff = lastQ
}
go func() {
v.IterAllP(64, func(permalink string, r RefOfCompany) {
company := r.TargetValue(ds)
categoryList := company.CategoryList()
regionKey := NewKey(ds).SetRegion(company.Region())
// Skip region for now to reduce size of data.
// regionKey := NewKey(ds).SetRegion(company.Region())
company.Rounds().IterAll(func(r RefOfRound) {
round := r.TargetValue(ds)
// HACK: Only include rounds that are newer than the cutoff date.
if time.Unix(round.FundedAt(), 0).Before(timeCutoff) {
return
}
roundRaiseDef := RoundRaiseDef{
Raised: round.RaisedAmountUsd(),
Details: r.TargetRef(),
@@ -84,7 +103,9 @@ func main() {
c <- entry{key, roundRaiseDef}
})
c <- entry{regionKey, roundRaiseDef}
// Skip region for now to reduce size of data.
// c <- entry{regionKey, roundRaiseDef}
addTimeRounds(round.FundedAt(), roundRaiseDef)
roundType := classifyRoundType(round)
@@ -99,7 +120,7 @@ func main() {
for e := range c {
key := e.key
roundRaiseDef := e.roundRaiseDef
keyRef := key.Ref()
keyRef := types.WriteValue(key, ds)
setDef := mapOfSets[keyRef]
if setDef == nil {
setDef = SetOfRoundRaiseDef{}
@@ -164,3 +185,19 @@ func timeToQuarter(t time.Time) QuarterEnum {
}
panic("unreachable")
}
func lastQuarter(t time.Time) time.Time {
var m time.Month
switch t.Month() {
case time.January, time.February, time.March:
m = time.January
case time.April, time.May, time.June:
m = time.April
case time.July, time.August, time.September:
m = time.July
case time.October, time.November, time.December:
m = time.October
}
currentQuarter := time.Date(time.Now().Year(), m, 1, 0, 0, 0, 0, time.UTC)
return currentQuarter.AddDate(0, -3, 0)
}
+3
View File
@@ -19,5 +19,8 @@ func main() {
if _, present := os.LookupEnv("NOMS_SERVER"); !present {
os.Setenv("NOMS_SERVER", "http://localhost:8000")
}
if _, present := os.LookupEnv("NOMS_DATASET_ID"); !present {
os.Setenv("NOMS_DATASET_ID", "crunchbase/index")
}
runner.ForceRun("npm", "run", "build")
}
+1 -1
View File
@@ -19,7 +19,7 @@
<p>The nerdosphere dataset is imported montly from the <a href="http://data.crunchbase.com/v3/page/crunchbase-data-exports">CrunchBase Excel Data Export</a>, which is in turned based on crowdsourced data from <a href="http://www.crunchbase.com/">CrunchBase</a>.
<p>This graph shows the distribution of venture capital funding rounds by funding series. The x-axis is round size by decile, and the y-axis is the average round size within that decile.
<p>This graph shows the distribution of venture capital funding rounds by funding series. The x-axis shows the rounds in increasing order. The y-axis is the amound of USD raised in that round.
<h2>Get the data</h2>
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
pushd ../../../js
npm install
npm run build
./link.sh
popd
npm link noms
+1 -1
View File
@@ -31,7 +31,7 @@
"watchify": "^3.6"
},
"scripts": {
"start": "cp node_modules/nvd3/build/nv.d3.css nv.d3.css; watchify -o out.js -v -d src/main.js",
"start": "cp node_modules/nvd3/build/nv.d3.css nv.d3.css; NOMS_SERVER=http://localhost:8000 NOMS_DATASET_ID=crunchbase/index watchify -o out.js -v -d node_modules/babel-regenerator-runtime/runtime.js src/main.js",
"build": "./.npm_build_helper.sh",
"test": "rm -f out.js && eslint src/ && flow src/"
},
+7
View File
@@ -0,0 +1,7 @@
// @flow
export function invariant(exp: any, message: string = 'Invariant violated') {
if (!exp) {
throw new Error(message);
}
}
+60
View File
@@ -0,0 +1,60 @@
// @flow
import d3 from './d3.js';
import nv from 'nvd3';
import React from 'react';
import ReactDOM from 'react-dom';
import type {DataArray} from './data.js';
type Props = {
data: DataArray,
color?: Array<string>
};
type State = {
chart: ?Object
};
type DefaultProps = {};
export default class Chart extends React.Component<DefaultProps, Props, State> {
componentDidMount() {
nv.addGraph(() => {
let chart = nv.models.lineChart();
chart.options({
clipEdge: true,
color: this.props.color,
interpolate: 'basis',
isArea: true,
showControls: false,
showLegend: false,
useInteractiveGuideline: true
});
chart.yScale(d3.scale.log());
let d3Format = d3.format('.2s');
let format = v => '$' + d3Format(v).toUpperCase().replace('G', 'B');
chart.yAxis.tickValues([1e4, 1e5, 1e6, 1e7, 1e8, 1e9]);
chart.yAxis.tickFormat(format);
chart.yDomain([1e3, 1e9]);
chart.xAxis.tickFormat(v => (v * 100 | 0) + '%');
let div = ReactDOM.findDOMNode(this);
d3.select(div.firstChild).datum(this.props.data).call(chart);
nv.utils.windowResize(() => chart.update());
this.setState({chart});
});
}
componentDidUpdate() {
let chart = this.state.chart;
if (chart) {
let svg = ReactDOM.findDOMNode(this);
d3.select(svg).datum(this.props.data).call(chart);
}
}
render(): React.Element {
return <svg/>;
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
/* @flow */
// @flow
/* $FlowIssue - Flow fails to find the d3 module? */
import d3 from 'd3';
+256 -27
View File
@@ -1,33 +1,262 @@
/* @flow */
// @flow
export type DataPoint = {x: number, y: number};
export type DataEntry = {values: Array<DataPoint>, key: string, color?: string};
import {HttpStore, readValue, Struct, makeType, Ref, registerPackage} from 'noms';
import {invariant} from './assert.js';
import type {ChunkStore, Package} from 'noms';
type RoundTypeEnum = 0 | 1 | 2;
const Seed = 0;
const SeriesA = 1;
const SeriesB = 2;
type QuarterEnum = 0 | 1 | 2 | 3;
type KeyParam = {
Year: number,
Quarter?: QuarterEnum
} | {
Category: string
} | {
RoundType: RoundTypeEnum
};
type TimeOption = {
Year: number,
Quarter?: QuarterEnum
};
type DataPoint = {x: number, y: number};
type DataEntry = {values: Array<DataPoint>, key: string, color?: string};
export type DataArray = Array<DataEntry>;
export default function getAllData() : DataArray {
let seed = [];
let seriesA = [];
let seriesB = [];
for (let i = 1; i <= 10; i++) {
seed.push({x: i, y: i});
seriesA.push({x: i, y: i});
seriesB.push({x: i, y: i});
export default class DataManager {
_store: HttpStore;
_datasetId: string;
_keyClass: any;
_quarterClass: any;
_index: Map<string, Ref>;
_datasetP: ?Promise<Map<Ref, Ref>>;
_packageP: ?Promise<Package>;
_categorySetP: ?Promise<Set<Struct>>;
_timeSetP: ?Promise<Set<Struct>>;
_seedSetP: ?Promise<Set<Struct>>;
_seriesASetP: ?Promise<Set<Struct>>;
_seriesBSetP: ?Promise<Set<Struct>>;
_data: ?DataArray;
_time: ?TimeOption;
_category: string;
constructor(store: ChunkStore, datasetId: string) {
this._datasetId = datasetId;
this._store = store;
this._keyClass = null;
this._quarterClass = null;
this._datasetP = null;
this._packageP = null;
this._index = new Map();
this._timeSetP = null;
this._categorySetP = null;
this._seedSetP = null;
this._seriesASetP = null;
this._seriesBSetP = null;
this._data = null;
this._time = null;
this._category = '';
}
return [
{
values: seed,
key: 'Seed',
color: '#011f4b'
},
{
values: seriesA,
key: 'A',
color: '#03396c'
},
{
values: seriesB,
key: 'B',
color: '#005b96'
async _getDataset(): Promise<Map<Ref, Ref>> {
if (this._datasetP) {
return this._datasetP;
}
];
return this._datasetP = getDataset(this._datasetId, this._store);
}
async _getPackage(): Promise<Package> {
if (this._packageP) {
return this._packageP;
}
let ds = await this._getDataset();
this._packageP = getKeyPackage(ds, this._store);
this._index = convertMap(ds);
return this._packageP;
}
async _getKeyClass(): Promise<any> {
if (this._keyClass) return this._keyClass;
let pkg = await this._getPackage();
return this._keyClass = getStructClass(pkg, 'Key');
}
async _getQuarterClass(): Promise<any> {
if (this._quarterClass) return this._quarterClass;
let pkg = await this._getPackage();
return this._quarterClass = getStructClass(pkg, 'Quarter');
}
_setTime(time: TimeOption) {
let t = this._time;
if (!t || t.Year !== time.Year || t.Quarter !== time.Quarter) {
this._time = time;
this._timeSetP = this._getSetOfRounds(time);
this._data = null;
}
}
_setCategory(category: string) {
if (this._category !== category) {
this._category = category;
this._categorySetP = this._getSetOfRounds({Category: category});
this._data = null;
}
}
_createRounds() {
this._seedSetP = this._getSetOfRounds({RoundType: Seed});
this._seriesASetP = this._getSetOfRounds({RoundType: SeriesA});
this._seriesBSetP = this._getSetOfRounds({RoundType: SeriesB});
}
async getData(time: TimeOption, category: string): any {
if (!this._seedSetP) {
this._createRounds();
}
this._setTime(time);
this._setCategory(category);
if (this._data) return this._data;
invariant(this._seedSetP && this._seriesASetP && this._seriesBSetP &&
this._timeSetP && this._categorySetP);
let [seedSet, seriesASet, seriesBSet, timeSet, categorySet] =
await Promise.all([this._seedSetP, this._seriesASetP, this._seriesBSetP,
this._timeSetP, this._categorySetP]);
let baseSet = intersectRounds(timeSet, categorySet);
return this._data = [
{
values: percentiles(intersectRounds(baseSet, seedSet)),
key: 'Seed'
},
{
values: percentiles(intersectRounds(baseSet, seriesASet)),
key: 'A'
},
{
values: percentiles(intersectRounds(baseSet, seriesBSet)),
key: 'B'
}
];
}
async _getKeyRef(p: KeyParam): Promise<Ref> {
const Key = await this._getKeyClass();
let k: typeof Key;
if (p.Quarter !== undefined) {
let Quarter = await this._getQuarterClass();
k = new Key({Quarter: new Quarter(p)});
} else {
k = new Key(p);
}
return k.ref;
}
async _getSetOfRounds(p: KeyParam): Promise<Set<Struct>> {
let s = (await this._getKeyRef(p)).toString();
let v = this._index.get(s);
if (v === undefined) {
return new Set();
}
let set = readValue(v, this._store);
return set || new Set();
}
}
/**
* Loads the first key in the index and gets the package from the type.
*/
async function getKeyPackage(index: Map<Ref, Ref>, store: ChunkStore):
Promise<Package> {
let ref: Ref;
for (let v of index.keys()) {
ref = v;
break;
}
invariant(ref instanceof Ref);
let key = await readValue(ref, store);
let pkg = await readValue(key.type.packageRef, store);
registerPackage(pkg);
return pkg;
}
function getStructClass(pkg, name): typeof Struct {
let keyIndex = pkg.types.findIndex(t => t.name === name);
let type = makeType(pkg.ref, keyIndex);
let typeDef = pkg.types[keyIndex];
return class extends Struct {
constructor(data) {
super(type, typeDef, data);
}
};
}
async function getDataset(id: string, httpStore: ChunkStore): any {
let rootRef = await httpStore.getRoot();
let datasets = await readValue(rootRef, httpStore);
let commitRef = datasets.get(id);
let commit = await readValue(commitRef, httpStore);
return commit.get('value');
}
function convertMap<T>(map: Map<Ref, T>): Map<string, T> {
let m = new Map();
map.forEach((v, k) => {
m.set(k.toString(), v);
});
return m;
}
function intersectRounds(a: Set<Struct>, b: Set<Struct>): Set<Struct> {
let sa = new Set();
a.forEach(v => {
sa.add(v.ref.toString());
});
let s = new Set();
b.forEach(v => {
if (sa.has(v.ref.toString())) {
s.add(v);
}
});
return s;
}
function percentiles(s: Set<Struct>): Array<{x: number, y: number}> {
let arr: Array<number> = [];
for (let round of s) {
let v = round.get('Raised');
if (v > 0) {
arr.push(v);
}
}
arr.sort((a, b) => a - b);
let len = arr.length;
if (len === 0) {
return [];
}
if (len === 1) {
return [{x: 0, y: arr[0]}, {x: 1, y: arr[0]}];
}
return arr.map((y, i) => {
let x = i / (len - 1);
return {x, y};
});
}
-25
View File
@@ -1,25 +0,0 @@
/* @flow */
import React from 'react';
type Props<T> = {
item: T,
delegate: LabelDelegate<T>
};
export type LabelDelegate<T> = {
getLabel: (item: T) => string,
isSelected: (item: T) => boolean,
getColor: (item: T) => string,
onChange: (item: T) => void
};
export default function Label(props: Props) : React.Element {
let {delegate, item} = props;
return <label><input type="checkbox"
checked={delegate.isSelected(item)}
onChange={() => delegate.onChange(item)}/>
<span style={{color: delegate.getColor(item)}}/>
<span>{delegate.getLabel(item)}</span>
</label>;
}
-25
View File
@@ -1,25 +0,0 @@
/* @flow */
'use strict';
import React from 'react';
import Label from './label.js';
import type {LabelDelegate} from './label.js';
type Props<T> = {
delegate: LabelDelegate<T>,
items: Array<T>,
title?: string
};
export default function LabelList<T>(props: Props<T>) : React.Element {
let delegate = props.delegate;
let labels = props.items.map(item => {
return <Label key={delegate.getLabel(item)} item={item}
delegate={delegate}/>;
});
return <div className='selection-list'>
{props.title ? <h3>{props.title}</h3> : null}
{labels}
</div>;
}
+43
View File
@@ -0,0 +1,43 @@
// @flow
import React from 'react';
type Props<T> = {
delegate: ListDelegate<T>,
items: Array<T>,
title?: string
};
export default function List<T>(props: Props<T>) : React.Element {
let delegate = props.delegate;
let labels = props.items.map(item => {
return <Label key={delegate.getLabel(item)} item={item}
delegate={delegate}/>;
});
return <div className='selection-list'>
{props.title ? <h3>{props.title}</h3> : null}
{labels}
</div>;
}
type LabelProps<T> = {
item: T,
delegate: ListDelegate<T>
};
export type ListDelegate<T> = {
getLabel: (item: T) => string,
isSelected: (item: T) => boolean,
getColor?: (item: T) => string,
onChange: (item: T) => void
};
function Label(props: LabelProps) : React.Element {
let {delegate, item} = props;
return <label><input type="checkbox"
checked={delegate.isSelected(item)}
onChange={() => delegate.onChange(item)}/>
<span style={{color: delegate.getColor && delegate.getColor(item)}}/>
<span>{delegate.getLabel(item)}</span>
</label>;
}
+139 -35
View File
@@ -1,78 +1,182 @@
/* @flow */
// @flow
import getAllData from './data.js';
import RadioList from './radio_list.js';
import {HttpStore} from 'noms';
import Chart from './chart.js';
import DataManager from './data.js';
import List from './list.js';
import React from 'react';
import ReactDOM from 'react-dom';
import SeriesList from './series_list.js';
import StackedBarChart from './stacked_bar_chart.js';
import type {DataArray, DataEntry} from './data.js';
import type {DataArray} from './data.js';
import type {ListDelegate} from './list.js';
type LabelAndKey = {
label: string,
key: Object
};
type DefaultProps = {};
type Props = {
data: DataArray,
timeItems: Array<string>,
categories: Array<string>
series: Array<string>,
timeItems: Array<LabelAndKey>,
categories: Array<string>,
color: Array<string>
};
type State = {
selectedSeries: Set<DataEntry>,
selectedTimeItem: string,
selectedCategoryItem: string
selectedSeries: Set<string>,
selectedTimeItem: LabelAndKey,
selectedCategoryItem: string,
data: DataArray
};
const nomsServer: string = process.env.NOMS_SERVER;
if (!nomsServer) {
throw new Error('NOMS_SERVER not set');
}
const datasetId: string = process.env.NOMS_DATASET_ID;
if (!datasetId) {
throw new Error('NOMS_DATASET_ID not set');
}
class Main extends React.Component<DefaultProps, Props, State> {
_dataManager: DataManager;
constructor(props: Props) {
super(props);
let selectedTimeItem = props.timeItems[0];
let selectedCategoryItem = props.categories[0];
this._dataManager = new DataManager(new HttpStore(nomsServer), datasetId);
this.state = {
selectedSeries: new Set(props.data),
selectedTimeItem: props.timeItems[0],
selectedCategoryItem: props.categories[0]
selectedSeries: new Set(this.props.series),
selectedTimeItem,
selectedCategoryItem,
data: []
};
}
_filteredData() : DataArray {
return this.props.data.filter(o => this.state.selectedSeries.has(o));
shouldComponentUpdate(nextProps: Props, nextState: State) : boolean {
return nextProps !== this.props ||
nextState.selectedSeries !== this.state.selectedSeries ||
nextState.selectedTimeItem !== this.state.selectedTimeItem ||
nextState.selectedCategoryItem !== this.state.selectedCategoryItem ||
nextState.data !== this.state.data;
}
_selectedSeriesChanged(s: Set<DataEntry>) {
_filteredData(): DataArray {
return this.state.data.filter(o => this.state.selectedSeries.has(o.key));
}
_selectedSeriesChanged(s: Set<string>) {
this.setState({selectedSeries: s});
}
_selectedTimeChanged(s: string) {
this.setState({selectedTimeItem: s});
_selectedTimeChanged(item: LabelAndKey) {
this.setState({selectedTimeItem: item});
}
_selectedCategoryChanged(s: string) {
this.setState({selectedCategoryItem: s});
}
render() : React.Element {
render(): React.Element {
let s = this.state;
let dm = this._dataManager;
dm.getData(s.selectedTimeItem.key, s.selectedCategoryItem).then(data => {
this.setState({data});
}).catch(ex => {
console.error(ex); // eslint-disable-line
});
let seriesDelegate: ListDelegate<string> = {
getLabel(item: string): string {
return item;
},
isSelected: (item: string) => {
return this.state.selectedSeries.has(item);
},
getColor: (item: string) => {
return this.props.color[this.props.series.indexOf(item)];
},
onChange: (item: string) => {
let selectedSeries = new Set(this.state.selectedSeries);
if (selectedSeries.has(item)) {
selectedSeries.delete(item);
} else {
selectedSeries.add(item);
}
this.setState({selectedSeries});
}
};
let timeDelegate: ListDelegate<LabelAndKey> = {
getLabel(item: LabelAndKey): string {
return item.label;
},
isSelected: (item: LabelAndKey) => {
return item === this.state.selectedTimeItem;
},
onChange: (item: LabelAndKey) => {
this._selectedTimeChanged(item);
}
};
let categoryDelegate: ListDelegate<string> = {
getLabel(item: string): string {
return item;
},
isSelected: (item: string) => {
return item === this.state.selectedCategoryItem;
},
onChange: (item: string) => {
this._selectedCategoryChanged(item);
}
};
return <div className='app'>
<div>
<StackedBarChart className='app-chart' data={this._filteredData()}/>
<Chart className='app-chart' data={this._filteredData()}
color={this.props.color}/>
<div className='app-controls'>
<SeriesList title='Series' data={this.props.data}
selected={this.state.selectedSeries}
onChange={s => this._selectedSeriesChanged(s)}/>
<RadioList title='Time' items={this.props.timeItems}
selected={this.state.selectedTimeItem}
onChange={s => this._selectedTimeChanged(s)}/>
<RadioList title='Categories' items={this.props.categories}
selected={this.state.selectedCategoryItem}
onChange={s => this._selectedCategoryChanged(s)}/>
<List title='Series' items={this.props.series}
delegate={seriesDelegate}/>
<List title='Time' items={this.props.timeItems}
delegate={timeDelegate}/>
<List title='Categories' items={this.props.categories}
delegate={categoryDelegate}/>
</div>
</div>
</div>;
}
}
const data = getAllData();
const timeItems = ['Current Quarter', 'Current Year'];
const categories = ['Finance', 'Tech', 'Bio', 'Education'];
const series = ['Seed', 'A', 'B'];
let d = new Date();
let year = d.getFullYear();
d.setMonth(d.getMonth() - 3);
let qYear = d.getFullYear();
let quarter = (d.getMonth() + 1) / 4 | 0;
const timeItems = [
{label: 'Current Year', key: {Year: year}},
{label: 'Last Quarter', key: {Year: qYear, Quarter: quarter}}
];
const categories = [
'Biotechnology',
'Finance',
'Games',
'Software'
];
const color = ['#011f4b', '#03396c', '#005b96'];
window.addEventListener('load', () => {
ReactDOM.render(
<Main data={data} timeItems={timeItems} categories={categories}/>,
<Main series={series} timeItems={timeItems} categories={categories}
color={color}/>,
document.querySelector('#app'));
});
-31
View File
@@ -1,31 +0,0 @@
/* @flow */
import React from 'react';
import LabelList from './label_list.js';
import type {LabelDelegate} from './label.js';
type Props = {
title: string,
items: Array<string>,
selected: string,
onChange: (selection: string) => void
};
export default function RadioList(props: Props) : React.Element {
let delegate: LabelDelegate<string> = {
getLabel(item: string) : string {
return item;
},
isSelected(item: string) : boolean {
return item === props.selected;
},
getColor() : string {
return '';
},
onChange(item: string) : void {
props.onChange(item);
}
};
return <LabelList title={props.title} items={props.items} delegate={delegate}/>;
}
-36
View File
@@ -1,36 +0,0 @@
/* @flow */
import React from 'react';
import type {DataArray} from './data.js';
import LabelList from './label_list.js';
import type {DataEntry} from './data.js';
import type {LabelDelegate} from './label.js';
type Props = {
title: string,
data: DataArray,
selected: Set<DataEntry>,
onChange: (selection: Set<DataEntry>) => void
};
export default function SeriesList(props: Props) : React.Element {
let {selected} = props;
let delegate: LabelDelegate<DataEntry> = {
getLabel(item: DataEntry) : string {
return item.key;
},
isSelected(item: DataEntry) : boolean {
return selected.has(item);
},
getColor(item: DataEntry) : string {
return item.color || '';
},
onChange(item: DataEntry) : void {
selected = new Set(selected);
selected.has(item) ? selected.delete(item) : selected.add(item);
props.onChange(selected);
}
};
return <LabelList title={props.title} items={props.data} delegate={delegate}/>;
}
@@ -1,47 +0,0 @@
/* @flow */
import d3 from './d3.js';
import nv from 'nvd3';
import React from 'react'; //eslint-disable-line no-unused-lets
import ReactDOM from 'react-dom';
import type {DataArray} from './data.js';
type Props = {
data: DataArray
};
type State = {
chart: ?Object
};
type DefaultProps = {};
export default class StackedBarChart extends React.Component<DefaultProps, Props, State> {
componentDidMount() {
nv.addGraph(() => {
let chart = nv.models.multiBarChart();
chart.options({
stacked: true,
showControls: false,
showLegend: false
});
let div = ReactDOM.findDOMNode(this);
d3.select(div.firstChild).datum(this.props.data).call(chart);
nv.utils.windowResize(() => chart.update());
this.setState({chart});
});
}
componentDidUpdate() {
let chart = this.state.chart;
if (chart) {
let svg = ReactDOM.findDOMNode(this);
d3.select(svg).datum(this.props.data).call(chart);
}
}
render() : React.Element {
return <svg/>;
}
}
+7 -3
View File
@@ -1,7 +1,7 @@
body {
background: white;
color: black;
font-family: 'helvetica', sans-serif;
font-family: helvetica, sans-serif;
margin: 2em;
}
@@ -53,7 +53,7 @@ a:visited {
}
.selection-list {
font: normal 12px Arial;
font-size: 12px;
margin-top: 10px;
}
@@ -99,7 +99,7 @@ a:visited {
.app {
display: flex;
flex-direction: column;
font-family: Arial, sans-serif;
font-family: 'Open Sans', sans-serif;
margin-top: 1em;
}
@@ -120,3 +120,7 @@ a:visited {
display: flex;
flex: 1;
}
.app .nvd3 text {
font-family: 'Open Sans', sans-serif;
}
+2
View File
@@ -3,6 +3,7 @@
.*/node_modules/babylon/.*
.*/node_modules/fbjs/.*
.*/node_modules/react/.*
.*/node_modules/d3/.*
[include]
@@ -11,3 +12,4 @@
[options]
unsafe.enable_getters_and_setters=true
munge_underscores=true
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
+2 -1
View File
@@ -348,7 +348,8 @@ class JsonArrayReader {
let s: { [key: string]: any } = Object.create(null);
for (let field of desc.fields) {
for (let i = 0; i < desc.fields.length; i++) {
let field = desc.fields[i];
if (field.optional) {
let b = this.readBool();
if (b) {
+3 -2
View File
@@ -201,7 +201,7 @@ class JsonArrayWriter {
break;
}
default:
throw new Error('Not implemented');
throw new Error(`Not implemented: ${t.kind} ${v}`);
}
}
@@ -296,7 +296,8 @@ class JsonArrayWriter {
writeStruct(s: Struct, type: Type, typeDef: Type, pkg: Package) {
let desc = typeDef.desc;
invariant(desc instanceof StructDesc);
for (let field of desc.fields) {
for (let i = 0; i < desc.fields.length; i++) {
let field = desc.fields[i];
let fieldValue = s.get(field.name);
if (field.optional) {
if (fieldValue !== undefined) {
+27 -22
View File
@@ -1,26 +1,31 @@
/* @flow */
import Chunk from './chunk.js';
import type {ChunkStore} from './chunk_store.js';
import CompoundList from './compound_list.js';
import HttpStore from './http_store.js';
import MemoryStore from './memory_store.js';
import Ref from './ref.js';
import Struct from './struct.js';
import {encodeNomsValue} from './encode.js';
import {readValue} from './decode.js';
import {Type} from './type.js';
// @flow
export {encodeNomsValue} from './encode.js';
export {readValue} from './decode.js';
export {default as Chunk} from './chunk.js';
export {default as CompoundList} from './compound_list.js';
export {default as HttpStore} from './http_store.js';
export {default as MemoryStore} from './memory_store.js';
export {default as Ref} from './ref.js';
export {default as Struct} from './struct.js';
export {lookupPackage, Package, readPackage, registerPackage} from './package.js';
export {
Chunk,
CompoundList,
encodeNomsValue,
HttpStore,
MemoryStore,
readValue,
Ref,
Struct,
Type
};
CompoundDesc,
EnumDesc,
Field,
makeCompoundType,
makeEnumType,
makePrimitiveType,
makeStructType,
makeType,
makeUnresolvedType,
PrimitiveDesc,
StructDesc,
Type,
typeType,
packageType,
UnresolvedDesc
} from './type.js';
import type {ChunkStore} from './chunk_store.js';
export type {ChunkStore};
+5 -3
View File
@@ -107,17 +107,19 @@ function validate(s: Struct): ?Field {
// TODO: Validate field values match field types.
let data = s._data;
let dataCount = Object.keys(data).length;
s.desc.fields.forEach(field => {
for (let i = 0; i < s.desc.fields.length; i++) {
let field = s.desc.fields[i];
if (data[field.name] !== undefined) {
dataCount--;
} else {
invariant(field.optional);
}
});
}
if (s.desc.union.length > 0) {
invariant(dataCount === 1);
for (let field of s.desc.union) {
for (let i = 0; i < s.desc.union.length; i++) {
let field = s.desc.union[i];
if (data[field.name] !== undefined) {
return field;
}