diff --git a/frontend/interfaces/icepick.js b/frontend/interfaces/icepick.js
index 3c120f77394a4cbe495e47064c84399657738997..89eec22f31d3e40362d3e45e6332abf08fcdc98d 100644
--- a/frontend/interfaces/icepick.js
+++ b/frontend/interfaces/icepick.js
@@ -2,16 +2,16 @@ type Key = string | number;
 type Value = any;
 
 declare module icepick {
-    declare function assoc<O:Object, K:Key, V:Value>(object: O, key: K, value: V): O;
-    declare function dissoc<O:Object, K:Key, V:Value>(object: O, key: K): O;
+    declare function assoc<O:Object|Array<any>, K:Key, V:Value>(object: O, key: K, value: V): O;
+    declare function dissoc<O:Object|Array<any>, K:Key, V:Value>(object: O, key: K): O;
 
-    declare function getIn<O:Object, K:Key, V:Value>(object: ?O, path: Array<K>): ?V;
-    declare function setIn<O:Object, K:Key, V:Value>(object: O, path: Array<K>, value: V): O;
-    declare function assocIn<O:Object, K:Key, V:Value>(object: O, path: Array<K>, value: V): O;
-    declare function updateIn<O:Object, K:Key, V:Value>(object: O, path: Array<K>, callback: ((value: V) => V)): O;
+    declare function getIn<O:Object|Array<any>, K:Key, V:Value>(object: ?O, path: Array<K>): ?V;
+    declare function setIn<O:Object|Array<any>, K:Key, V:Value>(object: O, path: Array<K>, value: V): O;
+    declare function assocIn<O:Object|Array<any>, K:Key, V:Value>(object: O, path: Array<K>, value: V): O;
+    declare function updateIn<O:Object|Array<any>, K:Key, V:Value>(object: O, path: Array<K>, callback: ((value: V) => V)): O;
 
-    declare function merge<O:Object>(object: O, other: O): O;
+    declare function merge<O:Object|Array<any>>(object: O, other: O): O;
 
     // TODO: improve this
-    declare function chain<O:Object>(object: O): any;
+    declare function chain<O:Object|Array<any>>(object: O): any;
 }
diff --git a/frontend/interfaces/underscore.js b/frontend/interfaces/underscore.js
index c6c3210fe8c8aad3c78f660e60759441d95b318d..00fc5ce0ede504644934323c81eeccf8a3775d2f 100644
--- a/frontend/interfaces/underscore.js
+++ b/frontend/interfaces/underscore.js
@@ -8,7 +8,7 @@ declare module "underscore" {
   declare function clone<T>(obj: T): T;
 
   declare function isEqual(a: any, b: any): boolean;
-  declare function range(a: number, b: number): Array<number>;
+  declare function range(a: number, b?: number): Array<number>;
   declare function extend<S, T>(o1: S, o2: T): S & T;
 
   declare function zip<S, T>(a1: S[], a2: T[]): Array<[S, T]>;
@@ -44,13 +44,20 @@ declare module "underscore" {
   declare function min<T>(a: Array<T>|{[key:any]: T}): T;
   declare function max<T>(a: Array<T>|{[key:any]: T}): T;
 
-  declare function uniq<T>(a: T[], isSorted?: boolean, iteratee?: (val: T) => boolean): T[];
+  declare function uniq<T>(a: T[], iteratee?: (val: T) => boolean): T[];
+  declare function uniq<T>(a: T[], isSorted: boolean, iteratee?: (val: T) => boolean): T[];
 
   declare function values<T>(o: {[key: any]: T}): T[];
-  declare function omit<T>(o: {[key: any]: T}, ...properties: string[]): T;
+  declare function omit(o: {[key: any]: any}, ...properties: string[]): {[key: any]: any};
+  declare function omit(o: {[key: any]: any}, predicate: (val: any, key: any, object: {[key: any]: any})=>boolean): {[key: any]: any};
+  declare function pick(o: {[key: any]: any}, ...properties: string[]): {[key: any]: any};
+  declare function pick(o: {[key: any]: any}, predicate: (val: any, key: any, object: {[key: any]: any})=>boolean): {[key: any]: any};
+  declare function pluck(o: Array<{[key: any]: any}>, propertyNames: string): Array<any>;
 
   declare function flatten(a: Array<any>): Array<any>;
 
+  declare function debounce<T: (any) => any>(func: T): T;
+
   // TODO: improve this
   declare function chain<S>(obj: S): any;
 }
diff --git a/frontend/src/metabase/App.jsx b/frontend/src/metabase/App.jsx
index 3c28474c21b0e2599b8232d68c7484da8e1348ea..6241a1b719a2ca21fb9c99c37b316745ba36ff11 100644
--- a/frontend/src/metabase/App.jsx
+++ b/frontend/src/metabase/App.jsx
@@ -1,3 +1,5 @@
+/* @flow weak */
+
 import React, { Component, PropTypes } from "react";
 
 import Navbar from "metabase/nav/containers/Navbar.jsx";
diff --git a/frontend/src/metabase/auth/auth.js b/frontend/src/metabase/auth/auth.js
index 9cf967b8a6f7ef53df0a498f3abe18d9fa510093..fbd5aaec514b3b9fdd187a1c8c473177edc60a87 100644
--- a/frontend/src/metabase/auth/auth.js
+++ b/frontend/src/metabase/auth/auth.js
@@ -9,7 +9,7 @@ import MetabaseAnalytics from "metabase/lib/analytics";
 
 import { clearGoogleAuthCredentials } from "metabase/lib/auth";
 
-import { refreshCurrentUser } from "metabase/user";
+import { refreshCurrentUser } from "metabase/redux/user";
 
 import { SessionApi } from "metabase/services";
 
diff --git a/frontend/src/metabase/debug.js b/frontend/src/metabase/debug.js
index a719d7a29fe0a5a0adcfa1b120f1ef03f7a78453..829b892496f9d9ec0b2301072b95a04f83c48dd5 100644
--- a/frontend/src/metabase/debug.js
+++ b/frontend/src/metabase/debug.js
@@ -1,3 +1,5 @@
+/* @flow weak */
+
 import React from "react";
 import { Route } from "react-router";
 
diff --git a/frontend/src/metabase/icon_paths.js b/frontend/src/metabase/icon_paths.js
index 3fd20af20aa70d51c2ab899c9b350b636fb090a1..1d63f18c9a918c3770ba15235811e4d5308c20f6 100644
--- a/frontend/src/metabase/icon_paths.js
+++ b/frontend/src/metabase/icon_paths.js
@@ -1,3 +1,4 @@
+/* @flow weak */
 
 /*
     Metabase Icon Paths
@@ -238,6 +239,7 @@ export var ICON_PATHS = {
     }
 };
 
+// $FlowFixMe
 ICON_PATHS["illustration-line"] = ICON_PATHS['illustration-area'];
 
 export function loadIcon(name) {
diff --git a/frontend/src/metabase/lib/api.js b/frontend/src/metabase/lib/api.js
index 2361c2d8d99cf0c8d970713a0bfaf7ded725e2bc..53369187fac44790b8b79d009a1e9b01b8b2909e 100644
--- a/frontend/src/metabase/lib/api.js
+++ b/frontend/src/metabase/lib/api.js
@@ -20,8 +20,8 @@ function makeMethod(method: string, hasBody: boolean = false) {
             params = {};
         }
         return function(
-            data: { [key:string]: any },
-            options: { [key:string]: any } = {}
+            data?: { [key:string]: any },
+            options?: { [key:string]: any } = {}
         ): Promise<any> {
             let url = urlTemplate;
             data = { ...data };
diff --git a/frontend/src/metabase/meta/types/Dataset.js b/frontend/src/metabase/meta/types/Dataset.js
index 5ad43b5b6ad1560d4bc3094cf251091c0eb433cc..4f8c9f95e4671f1a389bd451a63ccef66218058e 100644
--- a/frontend/src/metabase/meta/types/Dataset.js
+++ b/frontend/src/metabase/meta/types/Dataset.js
@@ -12,17 +12,19 @@ export type Column = {
     display_name: string,
     base_type: string,
     special_type: ?string
-}
+};
 
 export type ISO8601Times = string;
 export type Value = string|number|ISO8601Times|boolean|null|{};
 export type Row = Value[];
 
+export type DatasetData = {
+    cols: Column[],
+    columns: ColumnName[],
+    rows: Row[]
+};
+
 export type Dataset = {
-    data: {
-        cols: Column[],
-        columns: ColumnName[],
-        rows: Row[]
-    },
+    data: DatasetData,
     json_query: DatasetQuery
-}
+};
diff --git a/frontend/src/metabase/reducers.js b/frontend/src/metabase/reducers.js
index 0cd9ca0a5ee93f9e3aa771e70cfe9829de349406..b8339147f9bcc360653705c1b914aaacd4c5e21f 100644
--- a/frontend/src/metabase/reducers.js
+++ b/frontend/src/metabase/reducers.js
@@ -1,3 +1,4 @@
+/* @flow weak */
 
 import { combineReducers } from 'redux';
 
@@ -36,7 +37,7 @@ import * as setup from "metabase/setup/reducers";
 
 /* user */
 import * as user from "metabase/user/reducers";
-import { currentUser } from "metabase/user";
+import { currentUser } from "metabase/redux/user";
 
 const reducers = {
     // global reducers
diff --git a/frontend/src/metabase/redux/requests.js b/frontend/src/metabase/redux/requests.js
index 4b50b6d0b05da594481d006059a969932bb9ec0d..0458ab62d857e61c9ec8e71ac395be7af40c0b70 100644
--- a/frontend/src/metabase/redux/requests.js
+++ b/frontend/src/metabase/redux/requests.js
@@ -1,3 +1,5 @@
+/* @flow weak */
+
 import { handleActions, createAction } from "metabase/lib/redux";
 import i from "icepick";
 
diff --git a/frontend/src/metabase/redux/settings.js b/frontend/src/metabase/redux/settings.js
index 21f5d3d89668bc462104a39af4c10f10b65fe1b2..2e8e3ed1f00372ddad468595a68348cfd8ee3738 100644
--- a/frontend/src/metabase/redux/settings.js
+++ b/frontend/src/metabase/redux/settings.js
@@ -1,3 +1,5 @@
+/* @flow weak */
+
 import { createThunkAction } from "metabase/lib/redux";
 import MetabaseSettings from "metabase/lib/settings";
 
diff --git a/frontend/src/metabase/redux/undo.js b/frontend/src/metabase/redux/undo.js
index 9d50538ac0986d7cfb3311a31892763c89acdbe4..f1ffb2d649def870ec9b90c33062c7eb81280ecc 100644
--- a/frontend/src/metabase/redux/undo.js
+++ b/frontend/src/metabase/redux/undo.js
@@ -1,3 +1,4 @@
+/* @flow weak */
 
 import { createAction, createThunkAction } from "metabase/lib/redux";
 
diff --git a/frontend/src/metabase/user.js b/frontend/src/metabase/redux/user.js
similarity index 98%
rename from frontend/src/metabase/user.js
rename to frontend/src/metabase/redux/user.js
index 68d3019ecccc1863186b5aceed4e25c6772c80c2..8425eeb378905f6352d74e84477319f21b425f49 100644
--- a/frontend/src/metabase/user.js
+++ b/frontend/src/metabase/redux/user.js
@@ -1,3 +1,5 @@
+/* @flow */
+
 import { createAction } from "redux-actions";
 import { handleActions } from 'redux-actions';
 
diff --git a/frontend/src/metabase/routes.jsx b/frontend/src/metabase/routes.jsx
index 85769988f17a5069c08762dc3e086fdaa418ea06..db5b5e1307c1d5b1ce39a70385eaf25710371fe2 100644
--- a/frontend/src/metabase/routes.jsx
+++ b/frontend/src/metabase/routes.jsx
@@ -1,10 +1,12 @@
+/* @flow weak */
+
 import React, { Component, PropTypes } from "react";
 
 import { Route, Redirect, IndexRedirect, IndexRoute } from 'react-router';
 import { routerActions } from 'react-router-redux';
 import { UserAuthWrapper } from 'redux-auth-wrapper';
 
-import { refreshCurrentUser } from "metabase/user";
+import { refreshCurrentUser } from "metabase/redux/user";
 import MetabaseSettings from "metabase/lib/settings";
 
 import App from "metabase/App.jsx";
diff --git a/frontend/src/metabase/store.js b/frontend/src/metabase/store.js
index fe73cdfc1d9b1fd7649ff8f5dcade1ba5a36ba62..bc8ac82d1912df9a304a51ac9be57ac45bb1d89d 100644
--- a/frontend/src/metabase/store.js
+++ b/frontend/src/metabase/store.js
@@ -1,3 +1,5 @@
+/* @flow weak */
+
 import { combineReducers, applyMiddleware, createStore, compose } from 'redux'
 import { reducer as form } from "redux-form";
 import { routerReducer as routing, routerMiddleware } from 'react-router-redux'
diff --git a/frontend/src/metabase/user/actions.js b/frontend/src/metabase/user/actions.js
index ea2227b4741fc9924a60edd84f5474567da0df86..6c01c55d570be2ba888c7c3ff3193e7dff7e8794 100644
--- a/frontend/src/metabase/user/actions.js
+++ b/frontend/src/metabase/user/actions.js
@@ -4,7 +4,7 @@ import { createThunkAction } from "metabase/lib/redux";
 
 import { UserApi } from "metabase/services";
 
-import { refreshCurrentUser } from "metabase/user";
+import { refreshCurrentUser } from "metabase/redux/user";
 
 // action constants
 export const CHANGE_TAB = 'CHANGE_TAB';
diff --git a/frontend/src/metabase/visualizations/AreaChart.jsx b/frontend/src/metabase/visualizations/AreaChart.jsx
index c9c08e1ebf74eea07f0caaef9cce8bd45062f2f2..b53f4c8cd046d412ec3be0adabb6e2780fba148e 100644
--- a/frontend/src/metabase/visualizations/AreaChart.jsx
+++ b/frontend/src/metabase/visualizations/AreaChart.jsx
@@ -1,3 +1,5 @@
+/* @flow */
+
 import React, { Component, PropTypes } from "react";
 
 import LineAreaBarChart from "./components/LineAreaBarChart.jsx";
diff --git a/frontend/src/metabase/visualizations/BarChart.jsx b/frontend/src/metabase/visualizations/BarChart.jsx
index f267dc73b80e60a685e16bce392d9c99ede36a74..1a546a5c6be8ea235a5339e1331c0abeca9ebe94 100644
--- a/frontend/src/metabase/visualizations/BarChart.jsx
+++ b/frontend/src/metabase/visualizations/BarChart.jsx
@@ -1,3 +1,5 @@
+/* @flow */
+
 import React, { Component, PropTypes } from "react";
 
 import LineAreaBarChart from "./components/LineAreaBarChart.jsx";
diff --git a/frontend/src/metabase/visualizations/Funnel.jsx b/frontend/src/metabase/visualizations/Funnel.jsx
index 7dcbbd2e1a263a0a838d65446964b1db0fb2437b..f3ac709b6de50912f9dfbabd6045e5c27f27abcd 100644
--- a/frontend/src/metabase/visualizations/Funnel.jsx
+++ b/frontend/src/metabase/visualizations/Funnel.jsx
@@ -1,3 +1,5 @@
+/* @flow */
+
 import React, { Component, PropTypes } from "react";
 import ReactDOM from "react-dom";
 
@@ -7,7 +9,9 @@ import { formatValue } from "metabase/lib/formatting";
 import { getSettings } from "metabase/lib/visualization_settings";
 import i from "icepick";
 
-export default class Funnel extends Component {
+import type { VisualizationProps } from "metabase/visualizations";
+
+export default class Funnel extends Component<*, VisualizationProps, *> {
     static displayName = "Funnel";
     static identifier = "funnel";
     static iconName = "funnel";
diff --git a/frontend/src/metabase/visualizations/LineChart.jsx b/frontend/src/metabase/visualizations/LineChart.jsx
index 45406129e96378c1a65c7b14db0eb5436199d53b..98d42bff66049d9688366d6c9f029206868e602a 100644
--- a/frontend/src/metabase/visualizations/LineChart.jsx
+++ b/frontend/src/metabase/visualizations/LineChart.jsx
@@ -1,3 +1,5 @@
+/* @flow */
+
 import React, { Component, PropTypes } from "react";
 
 import LineAreaBarChart from "./components/LineAreaBarChart.jsx";
diff --git a/frontend/src/metabase/visualizations/Map.jsx b/frontend/src/metabase/visualizations/Map.jsx
index 1fa4d557f498a03856c3435494f7e9e1ae77df32..2c6880d7473d4e74dfb204b9f18dc7d068525221 100644
--- a/frontend/src/metabase/visualizations/Map.jsx
+++ b/frontend/src/metabase/visualizations/Map.jsx
@@ -1,3 +1,5 @@
+/* @flow */
+
 import React, { Component, PropTypes } from "react";
 
 import ChoroplethMap from "./components/ChoroplethMap.jsx";
@@ -5,7 +7,9 @@ import PinMap from "./PinMap.jsx";
 
 import { ChartSettingsError } from "metabase/visualizations/lib/errors";
 
-export default class Map extends Component {
+import type { VisualizationProps } from "metabase/visualizations";
+
+export default class Map extends Component<*, VisualizationProps, *> {
     static displayName = "Map";
     static identifier = "map";
     static iconName = "pinmap";
diff --git a/frontend/src/metabase/visualizations/PieChart.jsx b/frontend/src/metabase/visualizations/PieChart.jsx
index 4490dc65694a03c06ddea37dc3844e9a04b79974..53f47e1ec94fd515ef9d4ced058d0eee65da4e54 100644
--- a/frontend/src/metabase/visualizations/PieChart.jsx
+++ b/frontend/src/metabase/visualizations/PieChart.jsx
@@ -1,3 +1,5 @@
+/* @flow */
+
 import React, { Component, PropTypes } from "react";
 import ReactDOM from "react-dom";
 import styles from "./PieChart.css";
@@ -26,7 +28,11 @@ const OTHER_SLICE_MIN_PERCENTAGE = 0.003;
 
 const PERCENT_REGEX = /percent/i;
 
-export default class PieChart extends Component {
+import type { VisualizationProps } from "metabase/visualizations";
+
+type Props = VisualizationProps;
+
+export default class PieChart extends Component<*, Props, *> {
     static displayName = "Pie";
     static identifier = "pie";
     static iconName = "pie";
@@ -66,7 +72,8 @@ export default class PieChart extends Component {
 
         const showPercentInTooltip = !PERCENT_REGEX.test(cols[metricIndex].name) && !PERCENT_REGEX.test(cols[metricIndex].display_name);
 
-        let total = rows.reduce((sum, row) => sum + row[metricIndex], 0);
+        // $FlowFixMe
+        let total: number = rows.reduce((sum, row) => sum + row[metricIndex], 0);
 
         // use standard colors for up to 5 values otherwise use color harmony to help differentiate slices
         let sliceColors = Object.values(rows.length > 5 ? colors.harmony : colors.normal);
diff --git a/frontend/src/metabase/visualizations/PinMap.jsx b/frontend/src/metabase/visualizations/PinMap.jsx
index a43c7025d1d211c2794ba96a2e9db8f8c98ba021..e80bb7846462b54a8b6c079dbc067b7d888cf144 100644
--- a/frontend/src/metabase/visualizations/PinMap.jsx
+++ b/frontend/src/metabase/visualizations/PinMap.jsx
@@ -1,3 +1,5 @@
+/* @flow */
+
 import React, { Component, PropTypes } from "react";
 
 import { hasLatitudeAndLongitudeColumns } from "metabase/lib/schema_metadata";
@@ -11,12 +13,24 @@ import cx from "classnames";
 
 import L from "leaflet";
 
+import type { VisualizationProps } from "metabase/visualizations";
+
+type Props = VisualizationProps;
+
+type State = {
+    lat: ?number,
+    lng: ?number,
+    zoom: ?number,
+    points: L.Point[],
+    bounds: L.Bounds,
+};
+
 const MAP_COMPONENTS_BY_TYPE = {
     "markers": LeafletMarkerPinMap,
     "tiles": LeafletTilePinMap,
 }
 
-export default class PinMap extends Component {
+export default class PinMap extends Component<*, Props, State> {
     static displayName = "Pin Map";
     static identifier = "pin_map";
     static iconName = "pinmap";
@@ -29,24 +43,25 @@ export default class PinMap extends Component {
         if (!hasLatitudeAndLongitudeColumns(cols)) { throw new LatitudeLongitudeError(); }
     }
 
-    constructor(props, context) {
-        super(props, context);
+    state: State;
+
+    constructor(props: Props) {
+        super(props);
         this.state = {
             lat: null,
             lng: null,
             zoom: null,
             ...this._getPoints(props)
         };
-        _.bindAll(this, "onMapZoomChange", "onMapCenterChange", "updateSettings");
     }
 
-    componentWillReceiveProps(newProps) {
+    componentWillReceiveProps(newProps: Props) {
         if (newProps.series[0].data !== this.props.series[0].data) {
             this.setState(this._getPoints(newProps))
         }
     }
 
-    updateSettings() {
+    updateSettings = () => {
         let newSettings = {};
         if (this.state.lat != null) {
             newSettings["map.center_latitude"] = this.state.lat;
@@ -61,15 +76,15 @@ export default class PinMap extends Component {
         this.setState({ lat: null, lng: null, zoom: null });
     }
 
-    onMapCenterChange = (lat, lng) => {
+    onMapCenterChange = (lat: number, lng: number) => {
         this.setState({ lat, lng });
     }
 
-    onMapZoomChange = (zoom) => {
+    onMapZoomChange = (zoom: number) => {
         this.setState({ zoom });
     }
 
-    _getPoints(props) {
+    _getPoints(props: Props) {
         const { settings, series: [{ data: { cols, rows }}] } = props;
         const latitudeIndex = _.findIndex(cols, (col) => col.name === settings["map.latitude_column"]);
         const longitudeIndex = _.findIndex(cols, (col) => col.name === settings["map.longitude_column"]);
@@ -91,7 +106,7 @@ export default class PinMap extends Component {
         const { points, bounds } = this.state;//this._getPoints(this.props);
 
         return (
-            <div className={className + " PinMap relative"} onMouseDownCapture={(e) =>e.stopPropagation() /* prevent dragging */}>
+            <div className={cx(className, "PinMap relative")} onMouseDownCapture={(e) =>e.stopPropagation() /* prevent dragging */}>
                 { Map ?
                     <Map
                         {...this.props}
diff --git a/frontend/src/metabase/visualizations/Progress.jsx b/frontend/src/metabase/visualizations/Progress.jsx
index faf2bd64d48e4eaa0b1632b54072c80e10045c05..abbaaf072af58bee35fc10f9dce4f75a295ca5a4 100644
--- a/frontend/src/metabase/visualizations/Progress.jsx
+++ b/frontend/src/metabase/visualizations/Progress.jsx
@@ -1,3 +1,5 @@
+/* @flow */
+
 import React, { Component, PropTypes } from "react";
 import ReactDOM from "react-dom";
 
@@ -10,7 +12,9 @@ import Color from "color";
 
 const BORDER_RADIUS = 5;
 
-export default class Progress extends Component {
+import type { VisualizationProps } from "metabase/visualizations";
+
+export default class Progress extends Component<*, VisualizationProps, *> {
     static displayName = "Progress";
     static identifier = "progress";
     static iconName = "progress";
@@ -76,7 +80,7 @@ export default class Progress extends Component {
 
     render() {
         const { series: [{ data: { rows } }], settings } = this.props;
-        const value = rows[0][0];
+        const value: number = rows[0][0];
         const goal = settings["progress.goal"] || 0;
 
         const mainColor = settings["progress.color"];
diff --git a/frontend/src/metabase/visualizations/Scalar.jsx b/frontend/src/metabase/visualizations/Scalar.jsx
index 3b08089c20d4eb17c3b59efe753a31cedf55e63b..5feb602986717371999eb5abedfe876f9243fb3e 100644
--- a/frontend/src/metabase/visualizations/Scalar.jsx
+++ b/frontend/src/metabase/visualizations/Scalar.jsx
@@ -1,3 +1,5 @@
+/* @flow */
+
 import React, { Component, PropTypes } from "react";
 import { Link } from "react-router";
 import styles from "./Scalar.css";
@@ -13,7 +15,9 @@ import cx from "classnames";
 import i from "icepick";
 import d3 from "d3";
 
-export default class Scalar extends Component {
+import type { VisualizationProps } from "metabase/visualizations";
+
+export default class Scalar extends Component<*, VisualizationProps, *> {
     static displayName = "Number";
     static identifier = "scalar";
     static iconName = "number";
diff --git a/frontend/src/metabase/visualizations/ScatterPlot.jsx b/frontend/src/metabase/visualizations/ScatterPlot.jsx
index 0f5af3c5857028dcf750249f9dba71e4d689cbdd..f6ea82d56ac58dbb711bc6aab29785a6002a7e2b 100644
--- a/frontend/src/metabase/visualizations/ScatterPlot.jsx
+++ b/frontend/src/metabase/visualizations/ScatterPlot.jsx
@@ -1,3 +1,5 @@
+/* @flow */
+
 import React, { Component, PropTypes } from "react";
 
 import LineAreaBarChart from "./components/LineAreaBarChart.jsx";
diff --git a/frontend/src/metabase/visualizations/Table.jsx b/frontend/src/metabase/visualizations/Table.jsx
index fa9c2708f758be12725500f054505f4fd2ef691a..8eb8be0ed7c4a9b0b582fc3d2fc915de56f1f72e 100644
--- a/frontend/src/metabase/visualizations/Table.jsx
+++ b/frontend/src/metabase/visualizations/Table.jsx
@@ -1,3 +1,5 @@
+/* @flow */
+
 import React, { Component, PropTypes } from "react";
 
 import TableInteractive from "./TableInteractive.jsx";
@@ -6,7 +8,26 @@ import TableSimple from "./TableSimple.jsx";
 import * as DataGrid from "metabase/lib/data_grid";
 import _ from "underscore";
 
-export default class Bar extends Component {
+import type { DatasetData } from "metabase/meta/types/Dataset";
+import type { Card, VisualizationSettings } from "metabase/meta/types/Card";
+
+type Props = {
+    card: Card,
+    data: DatasetData,
+    settings: VisualizationSettings,
+    isDashboard: boolean,
+    cellClickedFn: (number, number) => void,
+    cellIsClickableFn: (number, number) => boolean,
+    setSortFn: (/* TODO */) => void,
+}
+type State = {
+    data: ?DatasetData,
+    columnIndexes: number[]
+}
+
+export default class Bar extends Component<*, Props, State> {
+    state: State;
+
     static displayName = "Table";
     static identifier = "table";
     static iconName = "table";
@@ -21,11 +42,12 @@ export default class Bar extends Component {
         // scalar can always be rendered, nothing needed here
     }
 
-    constructor(props, context) {
-        super(props, context);
+    constructor(props: Props) {
+        super(props);
 
         this.state = {
-            data: null
+            data: null,
+            columnIndexes: []
         };
     }
 
@@ -33,22 +55,22 @@ export default class Bar extends Component {
         this._updateData(this.props);
     }
 
-    componentWillReceiveProps(newProps) {
+    componentWillReceiveProps(newProps: Props) {
         // TODO: remove use of deprecated "card" and "data" props
         if (newProps.data !== this.props.data || !_.isEqual(newProps.settings, this.props.settings)) {
             this._updateData(newProps);
         }
     }
 
-    cellClicked = (rowIndex, columnIndex, ...args) => {
+    cellClicked = (rowIndex: number, columnIndex: number, ...args: any[]) => {
         this.props.cellClickedFn(rowIndex, this.state.columnIndexes[columnIndex], ...args);
     }
 
-    cellIsClickable = (rowIndex, columnIndex, ...args) => {
+    cellIsClickable = (rowIndex: number, columnIndex: number, ...args: any[]) => {
         return this.props.cellIsClickableFn(rowIndex, this.state.columnIndexes[columnIndex], ...args);
     }
 
-    _updateData({ data, settings }) {
+    _updateData({ data, settings }: { data: DatasetData, settings: VisualizationSettings }) {
         if (settings["table.pivot"]) {
             this.setState({
                 data: DataGrid.pivot(data)
diff --git a/frontend/src/metabase/visualizations/TableInteractive.jsx b/frontend/src/metabase/visualizations/TableInteractive.jsx
index 285afc8fec1f63d9dddad7c145b972622fca79e4..43884f285a214e58c345c91054316ec47be79800 100644
--- a/frontend/src/metabase/visualizations/TableInteractive.jsx
+++ b/frontend/src/metabase/visualizations/TableInteractive.jsx
@@ -1,3 +1,5 @@
+/* @flow */
+
 import React, { Component, PropTypes } from "react";
 import ReactDOM from "react-dom";
 
@@ -24,10 +26,47 @@ const ROW_HEIGHT = 35;
 const MIN_COLUMN_WIDTH = ROW_HEIGHT;
 const RESIZE_HANDLE_WIDTH = 5;
 
+import type { Column } from "metabase/meta/types/Dataset";
+import type { VisualizationProps } from "metabase/visualizations";
+
+type Props = VisualizationProps & {
+    width: number,
+    height: number,
+    sort: any,
+    isPivoted: boolean,
+    cellClickedFn: (number, number) => void,
+    cellIsClickableFn: (number, number) => boolean,
+    setSortFn: (/* TODO */) => void,
+}
+type State = {
+    popover: ?{ rowIndex: number, columnIndex: number },
+    columnWidths: number[],
+    contentWidths: ?number[]
+}
+
+type CellRendererProps = {
+    key: string,
+    style: { [key:string]: any },
+    columnIndex: number,
+    rowIndex: number
+}
+
+type GridComponent = Component<void, void, void> & { recomputeGridSize: () => void }
+
 @ExplicitSize
-export default class TableInteractive extends Component {
-    constructor(props, context) {
-        super(props, context);
+export default class TableInteractive extends Component<*, Props, State> {
+    state: State;
+    props: Props;
+
+    columnHasResized: { [key:number]: boolean };
+    columnNeedsResize: { [key:number]: boolean };
+    _div: HTMLElement;
+
+    header: GridComponent;
+    grid: GridComponent;
+
+    constructor(props: Props) {
+        super(props);
 
         this.state = {
             popover: null,
@@ -35,8 +74,6 @@ export default class TableInteractive extends Component {
             contentWidths: null
         };
         this.columnHasResized = {};
-
-        _.bindAll(this, "onClosePopover", "cellRenderer", "tableHeaderRenderer");
     }
 
     static propTypes = {
@@ -61,29 +98,29 @@ export default class TableInteractive extends Component {
         this._div.style.display = "inline-block"
         this._div.style.position = "absolute"
         this._div.style.visibility = "hidden"
-        this._div.style.zIndex = -1
+        this._div.style.zIndex = "-1"
         document.body.appendChild(this._div);
 
         this._measure();
     }
 
     componentWillUnmount() {
-        if (this._div) {
+        if (this._div && this._div.parentNode) {
             this._div.parentNode.removeChild(this._div);
         }
     }
 
-    componentWillReceiveProps(newProps) {
+    componentWillReceiveProps(newProps: Props) {
         if (JSON.stringify(this.props.data && this.props.data.cols) !== JSON.stringify(newProps.data && newProps.data.cols)) {
             this.resetColumnWidths();
         }
     }
 
-    shouldComponentUpdate(nextProps, nextState) {
-        const PROP_KEYS = ["width", "height", "settings", "data"];
+    shouldComponentUpdate(nextProps: Props, nextState: State) {
+        const PROP_KEYS: string[] = ["width", "height", "settings", "data"];
         // compare specific props and state to determine if we should re-render
         return (
-            !_.isEqual(_.pick(this.props, PROP_KEYS), _.pick(nextProps, PROP_KEYS)) ||
+            !_.isEqual(_.pick(this.props, ...PROP_KEYS), _.pick(nextProps, ...PROP_KEYS)) ||
             !_.isEqual(this.state, nextState)
         );
     }
@@ -110,7 +147,7 @@ export default class TableInteractive extends Component {
             this._measureColumn(index)
         );
 
-        let columnWidths = cols.map((col, index) => {
+        let columnWidths: number[] = cols.map((col, index) => {
             if (this.columnNeedsResize) {
                 if (this.columnNeedsResize[index] && !this.columnHasResized[index]) {
                     this.columnHasResized[index] = true;
@@ -128,18 +165,18 @@ export default class TableInteractive extends Component {
         this.setState({ contentWidths, columnWidths }, this.recomputeGridSize);
     }
 
-    _measureColumn(columnIndex) {
+    _measureColumn(columnIndex: number) {
         const { data: { rows } } = this.props;
         let width = MIN_COLUMN_WIDTH;
 
         // measure column header
-        width = Math.max(width, this._measureCell(this.tableHeaderRenderer({ columnIndex })));
+        width = Math.max(width, this._measureCell(this.tableHeaderRenderer({ columnIndex, rowIndex: 0, key: "", style: {} })));
 
         // measure up to 10 non-nil cells
         let remaining = 10;
         for (let rowIndex = 0; rowIndex < rows.length && remaining > 0; rowIndex++) {
             if (rows[rowIndex][columnIndex] != null) {
-                const cellWidth = this._measureCell(this.cellRenderer({ rowIndex, columnIndex }));
+                const cellWidth = this._measureCell(this.cellRenderer({ rowIndex, columnIndex, key: "", style: {} }));
                 width = Math.max(width, cellWidth);
                 remaining--;
             }
@@ -148,7 +185,7 @@ export default class TableInteractive extends Component {
         return width;
     }
 
-    _measureCell(cell) {
+    _measureCell(cell: React.Element<any>) {
         ReactDOM.unstable_renderSubtreeIntoContainer(this, cell, this._div);
 
         // 2px for border?
@@ -170,13 +207,13 @@ export default class TableInteractive extends Component {
         this.setState({ contentWidths: null })
     }, 100)
 
-    onCellResize(columnIndex) {
+    onCellResize(columnIndex: number) {
         this.columnNeedsResize = this.columnNeedsResize || {}
         this.columnNeedsResize[columnIndex] = true;
         this.recomputeColumnSizes();
     }
 
-    onColumnResize(columnIndex, width) {
+    onColumnResize(columnIndex: number, width: number) {
         const { settings } = this.props;
         let columnWidthsSetting = settings["table.column_widths"] ? settings["table.column_widths"].slice() : [];
         columnWidthsSetting[columnIndex] = Math.max(MIN_COLUMN_WIDTH, width);
@@ -188,22 +225,22 @@ export default class TableInteractive extends Component {
         return (this.props.setSortFn !== undefined);
     }
 
-    setSort(column) {
+    setSort(column: Column) {
         // lets completely delegate this to someone else up the stack :)
         this.props.setSortFn(column);
         MetabaseAnalytics.trackEvent('QueryBuilder', 'Set Sort', 'table column');
     }
 
-    cellClicked(rowIndex, columnIndex) {
+    cellClicked(rowIndex: number, columnIndex: number) {
         this.props.cellClickedFn(rowIndex, columnIndex);
     }
 
-    popoverFilterClicked(rowIndex, columnIndex, operator) {
+    popoverFilterClicked(rowIndex: number, columnIndex: number, operator: string) {
         this.props.cellClickedFn(rowIndex, columnIndex, operator);
         this.setState({ popover: null });
     }
 
-    showPopover(rowIndex, columnIndex) {
+    showPopover(rowIndex: number, columnIndex: number) {
         this.setState({
             popover: {
                 rowIndex: rowIndex,
@@ -212,11 +249,11 @@ export default class TableInteractive extends Component {
         });
     }
 
-    onClosePopover() {
+    onClosePopover = () => {
         this.setState({ popover: null });
     }
 
-    cellRenderer({ key, style, rowIndex, columnIndex }) {
+    cellRenderer = ({ key, style, rowIndex, columnIndex }: CellRendererProps) => {
         const { data: { cols, rows }} = this.props;
         const column = cols[columnIndex];
         const cellData = rows[rowIndex][columnIndex];
@@ -247,7 +284,7 @@ export default class TableInteractive extends Component {
                     <Value value={cellData} column={column} onResize={this.onCellResize.bind(this, columnIndex)} />
                     { popover && popover.rowIndex === rowIndex && popover.columnIndex === columnIndex &&
                         <QuickFilterPopover
-                            column={cols[this.state.popover.columnIndex]}
+                            column={cols[popover.columnIndex]}
                             onFilter={this.popoverFilterClicked.bind(this, rowIndex, columnIndex)}
                             onClose={this.onClosePopover}
                         />
@@ -257,7 +294,7 @@ export default class TableInteractive extends Component {
         }
     }
 
-    tableHeaderRenderer({ key, style, columnIndex }) {
+    tableHeaderRenderer = ({ key, style, columnIndex }: CellRendererProps) => {
         const { sort, data: { cols }} = this.props;
         const isSortable = this.isSortable();
         const column = cols[columnIndex];
@@ -309,7 +346,7 @@ export default class TableInteractive extends Component {
         )
     }
 
-    getColumnWidth = ({ index }) => {
+    getColumnWidth = ({ index }: { index: number }) => {
         const { settings } = this.props;
         const { columnWidths } = this.state;
         const columnWidthsSetting = settings["table.column_widths"] || [];
diff --git a/frontend/src/metabase/visualizations/TableSimple.jsx b/frontend/src/metabase/visualizations/TableSimple.jsx
index cb26c420fb679d52b905222d47f4ae814d48331c..9c919ba001f388d0b82cf9150f22778d7011425d 100644
--- a/frontend/src/metabase/visualizations/TableSimple.jsx
+++ b/frontend/src/metabase/visualizations/TableSimple.jsx
@@ -1,3 +1,5 @@
+/* @flow */
+
 import React, { Component, PropTypes } from "react";
 import ReactDOM from "react-dom";
 import styles from "./Table.css";
@@ -12,10 +14,26 @@ import { getFriendlyName } from "metabase/visualizations/lib/utils";
 import cx from "classnames";
 import _ from "underscore";
 
+import type { VisualizationProps } from "metabase/visualizations";
+
+type Props = VisualizationProps & {
+    height: number,
+    className?: string
+}
+
+type State = {
+    page: number,
+    pageSize: number,
+    sortColumn: ?number,
+    sortDescending: boolean
+}
+
 @ExplicitSize
-export default class TableSimple extends Component {
-    constructor(props, context) {
-        super(props, context);
+export default class TableSimple extends Component<*, Props, State> {
+    state: State;
+
+    constructor(props: Props) {
+        super(props);
 
         this.state = {
             page: 0,
@@ -33,7 +51,7 @@ export default class TableSimple extends Component {
         className: ""
     };
 
-    setSort(colIndex) {
+    setSort(colIndex: number) {
         if (this.state.sortColumn === colIndex) {
             this.setState({ sortDescending: !this.state.sortDescending });
         } else {
diff --git a/frontend/src/metabase/visualizations/XKCDChart.css b/frontend/src/metabase/visualizations/XKCDChart.css
deleted file mode 100644
index 7d12a325ef668e25df4b1d55262c498f13d8d48f..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/visualizations/XKCDChart.css
+++ /dev/null
@@ -1,27 +0,0 @@
-
-:local(.XKCDChart) {
-    font-family: "Humor Sans", sans-serif;
-    font-size: 16px;
-    color: #333;
-    text-align: center;
-}
-
-:local(.XKCDChart) text.title {
-    font-size: 20px;
-}
-
-:local(.XKCDChart) path {
-    fill: none;
-    stroke-width: 2.5px;
-    stroke-linecap: round;
-    stroke-linejoin: round;
-}
-
-:local(.XKCDChart) path.axis {
-    stroke: black;
-}
-
-:local(.XKCDChart) path.bgline {
-    stroke: white;
-    stroke-width: 6px;
-}
diff --git a/frontend/src/metabase/visualizations/XKCDChart.jsx b/frontend/src/metabase/visualizations/XKCDChart.jsx
deleted file mode 100644
index a8a61456dc9380662155493385fc9623dd242942..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/visualizations/XKCDChart.jsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import React, { Component, PropTypes } from "react";
-import ReactDOM from "react-dom";
-
-import styles from "./XKCDChart.css";
-
-import { MinColumnsError, MinRowsError } from "metabase/visualizations/lib/errors";
-
-import {
-    getFriendlyName,
-    getAvailableCanvasWidth,
-    getAvailableCanvasHeight
-} from "metabase/visualizations/lib/utils";
-
-import { dimensionIsTimeseries } from "./lib/timeseries";
-
-import xkcdplot from "xkcdplot";
-import "xkcdplot/humor-sans";
-
-import cx from "classnames";
-
-export default class XKCDChart extends Component {
-    static displayName = "XKCD"
-    static identifier = "xkcd";
-    static iconName = "pinmap";
-
-    static noHeader = true;
-
-    static isSensible(cols, rows) {
-        return rows.length > 1 && cols.length > 1;
-    }
-
-    static checkRenderable(cols, rows) {
-        if (cols.length < 2) { throw new MinColumnsError(2, cols.length); }
-        if (rows.length < 1) { throw new MinRowsError(1, rows.length); }
-    }
-
-    componentDidMount() {
-        this.componentDidUpdate();
-    }
-
-    componentDidUpdate() {
-        let { series } = this.props;
-
-        let parent = ReactDOM.findDOMNode(this);
-        while (parent.firstChild) {
-            parent.removeChild(parent.firstChild);
-        }
-
-        let margin = 50;
-
-        // Build the plot.
-        var plot = xkcdplot()
-            .width(Math.min(800, getAvailableCanvasWidth(parent) - margin * 2))
-            .height(Math.min(600, getAvailableCanvasHeight(parent) - margin * 2))
-            .margin(margin)
-            .xlabel(getFriendlyName(series[0].data.cols[0]))
-            .ylabel(getFriendlyName(series[0].data.cols[1]));
-
-        if (series[0].card.name) {
-            plot.title(series[0].card.name);
-        }
-
-        plot(parent);
-
-        let colors = [undefined, "red", "grey", "green", "yellow"];
-        let isTimeseries = dimensionIsTimeseries(series[0].data);
-        series.map((s, index) => {
-            let data = s.data.rows.map(row => ({
-                x: isTimeseries ? new Date(row[0]).getTime() : row[0],
-                y: row[1]
-            }));
-            plot.plot(data, { stroke: colors[index % colors.length] });
-        })
-
-        plot.draw();
-
-        if (series[0].card.id) {
-            let title = parent.querySelector(".title");
-            title.style = "cursor: pointer;";
-            title.addEventListener("click", () => window.location = "/card/" + series[0].card.id);
-        }
-    }
-
-    render() {
-        return (
-            <div className={cx(this.props.className, styles.XKCDChart)} />
-        );
-    }
-}
diff --git a/frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx b/frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx
index 7022b90c84e79ee04e36399c6ed55ade3d057115..3abcb36840acfb78e0ec31115323bba8147813c2 100644
--- a/frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx
+++ b/frontend/src/metabase/visualizations/components/LineAreaBarChart.jsx
@@ -1,3 +1,5 @@
+/* @flow */
+
 import React, { Component, PropTypes } from "react";
 
 import CardRenderer from "./CardRenderer.jsx";
@@ -20,7 +22,11 @@ import crossfilter from "crossfilter";
 import _ from "underscore";
 import cx from "classnames";
 
-export default class LineAreaBarChart extends Component {
+import type { VisualizationProps } from "metabase/visualizations";
+
+export default class LineAreaBarChart extends Component<*, VisualizationProps, *> {
+    static identifier;
+
     static noHeader = true;
     static supportsSeries = true;
 
@@ -81,7 +87,6 @@ export default class LineAreaBarChart extends Component {
 
     static propTypes = {
         series: PropTypes.array.isRequired,
-        onAddSeries: PropTypes.func,
         actionButtons: PropTypes.node,
         isDashboard: PropTypes.bool
     };
diff --git a/frontend/src/metabase/visualizations/components/Visualization.jsx b/frontend/src/metabase/visualizations/components/Visualization.jsx
index c66217a0826d86e9e9278ad13479ac943318548c..e464edc263133a1eb4cd15e102ad06856075fa9e 100644
--- a/frontend/src/metabase/visualizations/components/Visualization.jsx
+++ b/frontend/src/metabase/visualizations/components/Visualization.jsx
@@ -1,6 +1,6 @@
-/* eslint "react/prop-types": "warn" */
+/* @flow weak */
 
-import React, { Component, PropTypes } from "react";
+import React, { Component, PropTypes, Element } from "react";
 
 import ExplicitSize from "metabase/components/ExplicitSize.jsx";
 import LegendHeader from "metabase/visualizations/components/LegendHeader.jsx";
@@ -24,58 +24,77 @@ import cx from "classnames";
 export const ERROR_MESSAGE_GENERIC = "There was a problem displaying this chart.";
 export const ERROR_MESSAGE_PERMISSION = "Sorry, you don't have permission to see this card."
 
-@ExplicitSize
-export default class Visualization extends Component {
-    constructor(props, context) {
-        super(props, context)
+import type { VisualizationSettings } from "metabase/meta/types/Card";
+import type { HoverObject, Series } from "metabase/visualizations";
 
-        this.state = {
-            hovered: null,
-            error: null,
-            warnings: [],
-            yAxisSplit: null,
-        };
+type Props = {
+    series: Series,
 
-        _.bindAll(this, "onRender", "onRenderError", "onHoverChange");
-    }
+    className: string,
 
-    static propTypes = {
-        series: PropTypes.array.isRequired,
+    isDashboard: boolean,
+    isEditing: boolean,
 
-        className: PropTypes.string,
+    actionButtons: Element<any>,
 
-        isDashboard: PropTypes.bool,
-        isEditing: PropTypes.bool,
+    // errors
+    error: string,
+    errorIcon: string,
 
-        actionButtons: PropTypes.node,
+    // slow card warnings
+    isSlow: boolean,
+    expectedDuration: number,
 
-        // errors
-        error: PropTypes.string,
-        errorIcon: PropTypes.string,
+    // injected by ExplicitSize
+    width: number,
+    height: number,
 
-        // slow card warnings
-        isSlow: PropTypes.bool,
-        expectedDuration: PropTypes.number,
+    // settings overrides from settings panel
+    settings: VisualizationSettings,
 
-        // injected by ExplicitSize
-        width: PropTypes.number,
-        height: PropTypes.number,
+    // used for showing content in place of visualization, e.x. dashcard filter mapping
+    replacementContent: Element<any>,
 
-        // settings overrides from settings panel
-        settings: PropTypes.object,
+    // used by TableInteractive
+    setSortFn: (any) => void,
+    cellIsClickableFn: (number, number) => boolean,
+    cellClickedFn: (number, number) => void,
 
-        // used for showing content in place of visualization, e.x. dashcard filter mapping
-        replacementContent: PropTypes.node,
+    // misc
+    onUpdateWarnings: (string[]) => void,
+    onOpenChartSettings: () => void,
+}
 
-        // used by TableInteractive
-        setSortFn: PropTypes.func,
-        cellIsClickableFn: PropTypes.func,
-        cellClickedFn: PropTypes.func,
+type State = {
+    series: ?Series,
+    CardVisualization: ?(Component<*, VisualizationSettings, *> & {
+        checkRenderable: (any, any) => void,
+        noHeader: boolean
+    }),
+
+    hovered: ?HoverObject,
+    error: ?Error,
+    warnings: string[],
+    yAxisSplit: ?number[][],
+}
 
-        // misc
-        onUpdateWarnings: PropTypes.func,
-        onOpenChartSettings: PropTypes.func,
-    };
+@ExplicitSize
+export default class Visualization extends Component<*, Props, State> {
+    state: State;
+    props: Props;
+
+    constructor(props: Props) {
+        super(props);
+
+        this.state = {
+            hovered: null,
+            error: null,
+            warnings: [],
+            yAxisSplit: null,
+            series: null,
+            CardVisualization: null
+        };
+    }
 
     static defaultProps = {
         isDashboard: false,
@@ -103,6 +122,7 @@ export default class Visualization extends Component {
         }
     }
 
+    // $FlowFixMe
     getWarnings(props = this.props, state = this.state) {
         let warnings = state.warnings || [];
         // don't warn about truncated data for table since we show a warning in the row count
@@ -128,7 +148,7 @@ export default class Visualization extends Component {
         });
     }
 
-    onHoverChange(hovered) {
+    onHoverChange = (hovered) => {
         const { yAxisSplit } = this.state;
         if (hovered) {
             // if we have Y axis split info then find the Y axis index (0 = left, 1 = right)
@@ -140,11 +160,11 @@ export default class Visualization extends Component {
         this.setState({ hovered });
     }
 
-    onRender({ yAxisSplit, warnings = [] } = {}) {
+    onRender = ({ yAxisSplit, warnings = [] } = {}) => {
         this.setState({ yAxisSplit, warnings });
     }
 
-    onRenderError(error) {
+    onRenderError = (error) => {
         this.setState({ error })
     }
 
@@ -154,7 +174,7 @@ export default class Visualization extends Component {
         const small = width < 330;
 
         let error = this.props.error || this.state.error;
-        let loading = !(series.length > 0 && _.every(series, (s) => s.data));
+        let loading = !(series && series.length > 0 && _.every(series, (s) => s.data));
         let noResults = false;
 
         // don't try to load settings unless data is loaded
@@ -167,6 +187,7 @@ export default class Visualization extends Component {
             } else {
                 try {
                     if (CardVisualization.checkRenderable) {
+                        // $FlowFixMe
                         CardVisualization.checkRenderable(series[0].data.cols, series[0].data.rows, settings);
                     }
                 } catch (e) {
@@ -209,12 +230,13 @@ export default class Visualization extends Component {
 
         return (
             <div className={cx(className, "flex flex-column")}>
-                { isDashboard && (settings["card.title"] || extra) && (loading || error || !CardVisualization.noHeader) || replacementContent ?
+                { isDashboard && (settings["card.title"] || extra) && (loading || error || !(CardVisualization && CardVisualization.noHeader)) || replacementContent ?
                     <div className="p1 flex-no-shrink">
                         <LegendHeader
                             series={
                                 settings["card.title"] ?
                                     // if we have a card title set, use it
+                                    // $FlowFixMe
                                     setIn(series, [0, "card", "name"], settings["card.title"]) :
                                     // otherwise use the original series
                                     series
@@ -272,12 +294,15 @@ export default class Visualization extends Component {
                         }
                     </div>
                 :
+                    // $FlowFixMe
                     <CardVisualization
                         {...this.props}
                         className="flex-full"
                         series={series}
                         settings={settings}
+                        // $FlowFixMe
                         card={series[0].card} // convienence for single-series visualizations
+                        // $FlowFixMe
                         data={series[0].data} // convienence for single-series visualizations
                         hovered={this.state.hovered}
                         onHoverChange={this.onHoverChange}
diff --git a/frontend/src/metabase/visualizations/index.js b/frontend/src/metabase/visualizations/index.js
index 1e3b9937064dcb62e33125511578ae3855427121..a6ed3c41f2847c012e1b852ff615130743db227a 100644
--- a/frontend/src/metabase/visualizations/index.js
+++ b/frontend/src/metabase/visualizations/index.js
@@ -1,3 +1,6 @@
+/* @flow weak */
+
+import { Component } from "react";
 
 import Scalar     from "./Scalar.jsx";
 import Progress   from "./Progress.jsx";
@@ -12,8 +15,44 @@ import Funnel     from "./Funnel.jsx";
 
 import _ from "underscore";
 
+import type { DatasetData } from "metabase/meta/types/Dataset";
+import type { Card, VisualizationSettings } from "metabase/meta/types/Card";
+
+export type HoverObject = {
+    index?: number,
+    axisIndex?: number
+}
+
+// type Visualization = Component<*, VisualizationProps, *>;
+
+// $FlowFixMe
+export type Series = { card: Card, data: DatasetData }[] & { _raw: Series }
+
+export type VisualizationProps = {
+    series: Series,
+    card: Card,
+    data: DatasetData,
+    settings: VisualizationSettings,
+
+    className?: string,
+    gridSize: ?{
+        width: number,
+        height: number
+    },
+
+    isDashboard: boolean,
+    isEditing: boolean,
+    actionButtons: Node,
+
+    hovered: ?HoverObject,
+    onHoverChange: (?HoverObject) => void,
+
+    onUpdateVisualizationSettings: ({ [key: string]: any }) => void
+}
+
 const visualizations = new Map();
 const aliases = new Map();
+// $FlowFixMe
 visualizations.get = function(key) {
     return Map.prototype.get.call(this, key) || aliases.get(key) || Table;
 }
@@ -32,14 +71,14 @@ export function registerVisualization(visualization) {
     }
 }
 
-export function getVisualizationRaw(series) {
+export function getVisualizationRaw(series: Series) {
     return {
         series: series,
         CardVisualization: visualizations.get(series[0].card.display)
     };
 }
 
-export function getVisualizationTransformed(series) {
+export function getVisualizationTransformed(series: Series) {
     // don't transform if we don't have the data
     if (_.any(series, s => s.data == null)) {
         return getVisualizationRaw(series);
@@ -49,12 +88,16 @@ export function getVisualizationTransformed(series) {
     let CardVisualization, lastSeries;
     do {
         CardVisualization = visualizations.get(series[0].card.display);
+        if (!CardVisualization) {
+            throw new Error("No visualization for " + series[0].card.display);
+        }
         lastSeries = series;
         if (typeof CardVisualization.transformSeries === "function") {
             series = CardVisualization.transformSeries(series);
         }
         if (series !== lastSeries) {
             series = [...series];
+            // $FlowFixMe
             series._raw = lastSeries;
         }
     } while (series !== lastSeries);
@@ -73,9 +116,4 @@ registerVisualization(PieChart);
 registerVisualization(MapViz);
 registerVisualization(Funnel);
 
-import { enableVisualizationEasterEgg } from "./lib/utils";
-import XKCDChart from "./XKCDChart.jsx";
-import LineAreaBarChart from "./components/LineAreaBarChart.jsx";
-enableVisualizationEasterEgg("XKCD", LineAreaBarChart, XKCDChart);
-
 export default visualizations;
diff --git a/frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js b/frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js
index ff4f99e33ee3e01f0499f4fc8ac7b4709ee45316..a72d304ce1d53e2b6deeeb1af9b5f836ceab8819 100644
--- a/frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js
+++ b/frontend/src/metabase/visualizations/lib/LineAreaBarRenderer.js
@@ -1,3 +1,5 @@
+/* @flow weak */
+
 import crossfilter from "crossfilter";
 import d3 from "d3";
 import dc from "dc";
@@ -47,11 +49,11 @@ const VORONOI_MAX_POINTS = 300;
 const UNAGGREGATED_DATA_WARNING = (col) => `"${getFriendlyName(col)}" is an unaggregated field: if it has more than one value at a point on the x-axis, the values will be summed.`
 const NULL_DIMENSION_WARNING = "Data includes missing dimension values.";
 
-function adjustTicksIfNeeded(axis, axisSize, minPixelsPerTick) {
-    let numTicks = axis.ticks();
+function adjustTicksIfNeeded(axis, axisSize: number, minPixelsPerTick: number) {
+    const ticks = axis.ticks();
     // d3.js is dumb and sometimes numTicks is a number like 10 and other times it is an Array like [10]
     // if it's an array then convert to a num
-    numTicks = numTicks.length != null ? numTicks[0] : numTicks;
+    const numTicks: number = Array.isArray(ticks) ? ticks[0] : ticks;
 
     if ((axisSize / numTicks) < minPixelsPerTick) {
         axis.ticks(Math.round(axisSize / minPixelsPerTick));
@@ -192,14 +194,14 @@ function applyChartOrdinalXAxis(chart, settings, series, xValues) {
 
 function applyChartYAxis(chart, settings, series, yExtent, axisName) {
     let axis;
-    if (axisName === "left") {
+    if (axisName !== "right") {
         axis = {
             scale:   (...args) => chart.y(...args),
             axis:    (...args) => chart.yAxis(...args),
             label:   (...args) => chart.yAxisLabel(...args),
             setting: (name) => settings["graph.y_axis." + name]
         };
-    } else if (axisName === "right") {
+    } else {
         axis = {
             scale:   (...args) => chart.rightY(...args),
             axis:    (...args) => chart.rightYAxis(...args),
@@ -441,6 +443,7 @@ function lineAndBarOnRender(chart, settings, onGoalHover, isSplitAxis) {
 
         function dispatchUIEvent(element, eventName) {
             let e = document.createEvent("UIEvents");
+            // $FlowFixMe
             e.initUIEvent(eventName, true, true, window, 1);
             element.dispatchEvent(e);
         }
@@ -713,9 +716,11 @@ export default function lineAreaBar(element, { series, onHoverChange, onRender,
 
     if (settings["line.missing"] === "zero" || settings["line.missing"] === "none") {
         if (isTimeseries) {
+            // $FlowFixMe
+            const { interval, count } = xInterval;
             // replace xValues with
-            xValues = d3.time[xInterval.interval]
-                .range(xDomain[0], moment(xDomain[1]).add(1, "ms"), xInterval.count)
+            xValues = d3.time[interval]
+                .range(xDomain[0], moment(xDomain[1]).add(1, "ms"), count)
                 .map(d => moment(d));
             datas = fillMissingValues(
                 datas,
diff --git a/frontend/src/metabase/visualizations/lib/errors.js b/frontend/src/metabase/visualizations/lib/errors.js
index c7e5e7a46eaf86b9c04135d5d023659a587eb63b..22e3c462a7b0393b47ac0348ba21dd38e7bea6f7 100644
--- a/frontend/src/metabase/visualizations/lib/errors.js
+++ b/frontend/src/metabase/visualizations/lib/errors.js
@@ -1,29 +1,30 @@
+/* @flow */
 
 import { inflect } from "metabase/lib/formatting";
 
-export class MinColumnsError {
-    constructor(minColumns, actualColumns) {
-        this.message = `Doh! The data from your query doesn't fit the chosen display choice. This visualization requires at least ${actualColumns} ${inflect("column", actualColumns)} of data.`;
+export class MinColumnsError extends Error {
+    constructor(minColumns: number, actualColumns: number) {
+        super(`Doh! The data from your query doesn't fit the chosen display choice. This visualization requires at least ${actualColumns} ${inflect("column", actualColumns)} of data.`);
     }
 }
 
-export class MinRowsError {
-    constructor(minRows, actualRows) {
-        this.message = `No dice. We have ${actualRows} data ${inflect("point", actualRows)} to show and that's not enough for this visualization.`;
-        this.minRows = minRows;
-        this.actualRows = actualRows;
+export class MinRowsError extends Error {
+    constructor(minRows: number, actualRows: number) {
+        super(`No dice. We have ${actualRows} data ${inflect("point", actualRows)} to show and that's not enough for this visualization.`);
     }
 }
 
-export class LatitudeLongitudeError {
-    constructor(minRows, actualRows) {
-        this.message = "Bummer. We can't actually do a pin map for this data because we require both a latitude and longitude column.";
+export class LatitudeLongitudeError extends Error {
+    constructor() {
+        super("Bummer. We can't actually do a pin map for this data because we require both a latitude and longitude column.");
     }
 }
 
-export class ChartSettingsError {
-    constructor(message, section, buttonText) {
-        this.message = message || "Please configure this chart in the chart settings";
+export class ChartSettingsError extends Error {
+    section: ?string;
+    buttonText: ?string;
+    constructor(message: string, section?: string, buttonText?: string) {
+        super(message || "Please configure this chart in the chart settings");
         this.section = section;
         this.buttonText = buttonText || "Edit Settings";
     }
diff --git a/frontend/src/metabase/visualizations/lib/mapping.js b/frontend/src/metabase/visualizations/lib/mapping.js
index 52d453bc6a9311013d82736dd4055892ed4fc897..d2f23e2f8a0e1cff80aa75b3c9669e5c70b84b9b 100644
--- a/frontend/src/metabase/visualizations/lib/mapping.js
+++ b/frontend/src/metabase/visualizations/lib/mapping.js
@@ -1,3 +1,4 @@
+/* @flow weak */
 
 import L from "leaflet/dist/leaflet-src.js";
 import d3 from "d3";
diff --git a/frontend/src/metabase/visualizations/lib/numeric.js b/frontend/src/metabase/visualizations/lib/numeric.js
index bb671b949330e95de50d8b354b3ea793776874af..aa6de4a28a742bbce34d6fa5e39af37ff9e21136 100644
--- a/frontend/src/metabase/visualizations/lib/numeric.js
+++ b/frontend/src/metabase/visualizations/lib/numeric.js
@@ -1,3 +1,5 @@
+/* @flow weak */
+
 import { isNumeric } from "metabase/lib/schema_metadata";
 
 export function dimensionIsNumeric({ cols, rows }, i = 0) {
diff --git a/frontend/src/metabase/visualizations/lib/timeseries.js b/frontend/src/metabase/visualizations/lib/timeseries.js
index e76c13ae91e8b1989157c01b5674b33d4838d5fd..9f0a31934013dd204c09eb3f209d506018dba1f4 100644
--- a/frontend/src/metabase/visualizations/lib/timeseries.js
+++ b/frontend/src/metabase/visualizations/lib/timeseries.js
@@ -1,3 +1,5 @@
+/* @flow weak */
+
 import moment from "moment";
 
 import { isDate } from "metabase/lib/schema_metadata";
diff --git a/frontend/src/metabase/visualizations/lib/tooltip.js b/frontend/src/metabase/visualizations/lib/tooltip.js
index 3d470b38420dd0b9b73212b02124f825c2f951f8..15d5d8a8a671e8b64678470e7ffaae7132984947 100644
--- a/frontend/src/metabase/visualizations/lib/tooltip.js
+++ b/frontend/src/metabase/visualizations/lib/tooltip.js
@@ -1,3 +1,5 @@
+/* @flow weak */
+
 function getElementIndex(e) {
     return [...e.classList].map(c => c.match(/^_(\d+)$/)).filter(c => c).map(c => parseInt(c[1], 10))[0];
 }
diff --git a/frontend/src/metabase/visualizations/lib/utils.js b/frontend/src/metabase/visualizations/lib/utils.js
index 87e91dd554327ff3a14d44cde16536abee9df5fe..0080fff76bef2b8443ef908f116f46aae4e625d3 100644
--- a/frontend/src/metabase/visualizations/lib/utils.js
+++ b/frontend/src/metabase/visualizations/lib/utils.js
@@ -1,3 +1,4 @@
+/* @flow weak */
 
 import React from "react";
 import _ from "underscore";
@@ -74,7 +75,7 @@ export function computeSplit(extents) {
             bestCost = splitCost;
         }
     }
-    return best.sort((a,b) => a[0] - b[0]);
+    return best && best.sort((a,b) => a[0] - b[0]);
 }
 
 const FRIENDLY_NAME_MAP = {
@@ -87,7 +88,7 @@ const FRIENDLY_NAME_MAP = {
 
 export function getXValues(datas, chartType) {
     let xValues = _.chain(datas)
-        .map((data) => _.pluck(data, 0))
+        .map((data) => _.pluck(data, "0"))
         .flatten(true)
         .uniq()
         .value();
diff --git a/package.json b/package.json
index f884c5654b91cc06e2f0e31782098c10dac2e67e..3b5f764220ae2778ea7bc4aa1991b877a4308e7a 100644
--- a/package.json
+++ b/package.json
@@ -62,7 +62,6 @@
     "stack-source-map": "^1.0.4",
     "tether": "^1.2.0",
     "underscore": "^1.8.3",
-    "xkcdplot": "^1.1.0",
     "z-index": "0.0.1"
   },
   "devDependencies": {
diff --git a/yarn.lock b/yarn.lock
index f728df13b3f93d6f8d2f85036f406fa6ede80f4d..c40f60caee784dcd153646f33662f9253dd14a6e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2141,7 +2141,7 @@ cycle@1.0.x:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
 
-d3@^3, d3@^3.5.17, d3@^3.5.x:
+d3@^3, d3@^3.5.17:
   version "3.5.17"
   resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8"
 
@@ -6950,12 +6950,6 @@ xdg-basedir@^2.0.0:
   dependencies:
     os-homedir "^1.0.0"
 
-xkcdplot@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/xkcdplot/-/xkcdplot-1.1.0.tgz#de8a9ba73c0f86ec72818bda1b5433f78d364836"
-  dependencies:
-    d3 "^3.5.x"
-
 xml-char-classes@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/xml-char-classes/-/xml-char-classes-1.0.0.tgz#64657848a20ffc5df583a42ad8a277b4512bbc4d"