diff --git a/caravel/assets/javascripts/components/VictoryTheme.js b/caravel/assets/javascripts/components/VictoryTheme.js
new file mode 100644
index 0000000000000..aeefe6c1de3ab
--- /dev/null
+++ b/caravel/assets/javascripts/components/VictoryTheme.js
@@ -0,0 +1,173 @@
+const { assign } = Object;
+
+const A11Y_BABU = '#00A699';
+const AXIS_LINE_GRAY = '#484848';
+
+// Colors
+const colors = [
+ '#ffffff',
+ '#f0f0f0',
+ '#d9d9d9',
+ '#bdbdbd',
+ '#969696',
+ '#737373',
+ '#525252',
+ '#252525',
+ '#000000',
+];
+
+const charcoal = '#484848';
+
+// Typography
+const sansSerif = '"Roboto", sans-serif';
+const letterSpacing = 'normal';
+const fontSize = 8;
+
+// Layout
+const baseProps = {
+ width: 450,
+ height: 300,
+ padding: 50,
+ colorScale: colors,
+};
+
+// Labels
+const baseLabelStyles = {
+ fontFamily: sansSerif,
+ fontSize,
+ letterSpacing,
+ padding: 10,
+ fill: charcoal,
+ stroke: 'transparent',
+};
+
+// Strokes
+const strokeLinecap = 'round';
+const strokeLinejoin = 'round';
+
+// Create the theme
+const theme = {
+ area: assign({
+ style: {
+ data: {
+ fill: charcoal,
+ },
+ labels: baseLabelStyles,
+ },
+ }, baseProps),
+ axis: assign({
+ style: {
+ axis: {
+ fill: 'none',
+ stroke: AXIS_LINE_GRAY,
+ strokeWidth: 1,
+ strokeLinecap,
+ strokeLinejoin,
+ },
+ axisLabel: assign({}, baseLabelStyles, {
+ padding: 25,
+ }),
+ grid: {
+ fill: 'none',
+ stroke: 'transparent',
+ },
+ ticks: {
+ fill: 'none',
+ padding: 10,
+ size: 1,
+ stroke: 'transparent',
+ },
+ tickLabels: baseLabelStyles,
+ },
+ }, baseProps),
+ bar: assign({
+ style: {
+ data: {
+ fill: A11Y_BABU,
+ padding: 10,
+ stroke: 'transparent',
+ strokeWidth: 0,
+ width: 8,
+ },
+ labels: baseLabelStyles,
+ },
+ }, baseProps),
+ candlestick: assign({
+ style: {
+ data: {
+ stroke: A11Y_BABU,
+ strokeWidth: 1,
+ },
+ labels: assign({}, baseLabelStyles, {
+ padding: 25,
+ textAnchor: 'end',
+ }),
+ },
+ candleColors: {
+ positive: '#ffffff',
+ negative: charcoal,
+ },
+ }, baseProps),
+ chart: baseProps,
+ errorbar: assign({
+ style: {
+ data: {
+ fill: 'none',
+ stroke: charcoal,
+ strokeWidth: 2,
+ },
+ labels: assign({}, baseLabelStyles, {
+ textAnchor: 'start',
+ }),
+ },
+ }, baseProps),
+ group: assign({
+ colorScale: colors,
+ }, baseProps),
+ line: assign({
+ style: {
+ data: {
+ fill: 'none',
+ stroke: A11Y_BABU,
+ strokeWidth: 2,
+ },
+ labels: assign({}, baseLabelStyles, {
+ textAnchor: 'start',
+ }),
+ },
+ }, baseProps),
+ pie: {
+ style: {
+ data: {
+ padding: 10,
+ stroke: 'none',
+ strokeWidth: 1,
+ },
+ labels: assign({}, baseLabelStyles, {
+ padding: 200,
+ textAnchor: 'middle',
+ }),
+ },
+ colorScale: colors,
+ width: 400,
+ height: 400,
+ padding: 50,
+ },
+ scatter: assign({
+ style: {
+ data: {
+ fill: charcoal,
+ stroke: 'transparent',
+ strokeWidth: 0,
+ },
+ labels: assign({}, baseLabelStyles, {
+ textAnchor: 'middle',
+ }),
+ },
+ }, baseProps),
+ stack: assign({
+ colorScale: colors,
+ }, baseProps),
+};
+
+export default theme;
diff --git a/caravel/assets/javascripts/explorev2/components/ChartContainer.jsx b/caravel/assets/javascripts/explorev2/components/ChartContainer.jsx
index 3666e923a201a..00967f79d8ea2 100644
--- a/caravel/assets/javascripts/explorev2/components/ChartContainer.jsx
+++ b/caravel/assets/javascripts/explorev2/components/ChartContainer.jsx
@@ -1,11 +1,67 @@
-import React from 'react';
+import React, { PropTypes } from 'react';
import { Panel } from 'react-bootstrap';
+import TimeSeriesLineChart from './charts/TimeSeriesLineChart';
+import moment from 'moment';
-const ChartContainer = function () {
- return (
-
- chart goes here
-
- );
+const propTypes = {
+ viz: PropTypes.shape({
+ data: PropTypes.object.isRequired,
+ form_data: PropTypes.shape({
+ slice_name: PropTypes.object.isRequired,
+ }).isRequired,
+ }).isRequired,
+ height: PropTypes.number.isRequired,
};
-export default ChartContainer;
+
+export default class ChartContainer extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ params: this.getParamsFromUrl(),
+ data: props.viz.data,
+ label1: 'Label 1',
+ };
+ }
+
+ getParamsFromUrl() {
+ const hash = window.location.search;
+ const params = hash.split('?')[1].split('&');
+ const newParams = {};
+ params.forEach((p) => {
+ const value = p.split('=')[1].replace(/\+/g, ' ');
+ const key = p.split('=')[0];
+ newParams[key] = value;
+ });
+ return newParams;
+ }
+
+ formatDates(values) {
+ const newValues = values.map(function (val) {
+ return {
+ x: moment(new Date(val.x)).format('MMM D'),
+ y: val.y,
+ };
+ });
+ return newValues;
+ }
+
+ render() {
+ return (
+
+
{this.props.viz.form_data.slice_name}
+ }
+ >
+
+
+
+ );
+ }
+}
+
+ChartContainer.propTypes = propTypes;
diff --git a/caravel/assets/javascripts/explorev2/components/ExploreViewContainer.jsx b/caravel/assets/javascripts/explorev2/components/ExploreViewContainer.jsx
index f6b6b52faae3e..31a11de84c6d0 100644
--- a/caravel/assets/javascripts/explorev2/components/ExploreViewContainer.jsx
+++ b/caravel/assets/javascripts/explorev2/components/ExploreViewContainer.jsx
@@ -1,26 +1,55 @@
-import React from 'react';
+import React, { PropTypes } from 'react';
import ChartContainer from './ChartContainer';
import ControlPanelsContainer from './ControlPanelsContainer';
import QueryAndSaveButtons from './QueryAndSaveButtons';
-const ExploreViewContainer = function () {
- return (
-
-
-
- { console.log('clicked query'); }}
- />
-
-
-
-
-
+const propTypes = {
+ data: PropTypes.shape({
+ viz: PropTypes.object.isRequired,
+ }).isRequired,
+};
+
+export default class ExploreViewContainer extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ height: this.getHeight(),
+ };
+ }
+
+ getHeight() {
+ const navHeight = 90;
+ return `${window.innerHeight - navHeight}px`;
+ }
+
+ render() {
+ return (
+
-
- );
-};
+ );
+ }
+}
-export default ExploreViewContainer;
+ExploreViewContainer.propTypes = propTypes;
diff --git a/caravel/assets/javascripts/explorev2/components/QueryAndSaveBtns.jsx b/caravel/assets/javascripts/explorev2/components/QueryAndSaveBtns.jsx
new file mode 100644
index 0000000000000..1a521393402fc
--- /dev/null
+++ b/caravel/assets/javascripts/explorev2/components/QueryAndSaveBtns.jsx
@@ -0,0 +1,31 @@
+import React, { PropTypes } from 'react';
+import classnames from 'classnames';
+
+const propTypes = {
+ canAdd: PropTypes.string.isRequired,
+ onQuery: PropTypes.func.isRequired,
+};
+
+export default function QueryAndSaveBtns({ canAdd, onQuery }) {
+ const saveClasses = classnames('btn btn-default btn-sm', {
+ 'disabled disabledButton': canAdd !== 'True',
+ });
+
+ return (
+
+
+
+
+ );
+}
+
+QueryAndSaveBtns.propTypes = propTypes;
diff --git a/caravel/assets/javascripts/explorev2/components/charts/Legend.jsx b/caravel/assets/javascripts/explorev2/components/charts/Legend.jsx
new file mode 100644
index 0000000000000..be253a33d22f4
--- /dev/null
+++ b/caravel/assets/javascripts/explorev2/components/charts/Legend.jsx
@@ -0,0 +1,21 @@
+import React, { PropTypes } from 'react';
+import LegendItem from './LegendItem';
+
+const propTypes = {
+ data: PropTypes.array.isRequired,
+ keysToColorsMap: PropTypes.object.isRequired,
+};
+
+export default function Legend({ data, keysToColorsMap }) {
+ const legendEls = data.map((d) => {
+ const color = keysToColorsMap[d.key] ? keysToColorsMap[d.key] : '#000';
+ return
;
+ });
+ return (
+
+ );
+}
+
+Legend.propTypes = propTypes;
diff --git a/caravel/assets/javascripts/explorev2/components/charts/LegendItem.jsx b/caravel/assets/javascripts/explorev2/components/charts/LegendItem.jsx
new file mode 100644
index 0000000000000..b1770857f9769
--- /dev/null
+++ b/caravel/assets/javascripts/explorev2/components/charts/LegendItem.jsx
@@ -0,0 +1,17 @@
+import React, { PropTypes } from 'react';
+
+const propTypes = {
+ label: PropTypes.string.isRequired,
+ color: PropTypes.string.isRequired,
+};
+
+export default function LegendItem({ label, color }) {
+ return (
+
+
+ {label}
+
+ );
+}
+
+LegendItem.propTypes = propTypes;
diff --git a/caravel/assets/javascripts/explorev2/components/charts/TimeSeriesLineChart.jsx b/caravel/assets/javascripts/explorev2/components/charts/TimeSeriesLineChart.jsx
new file mode 100644
index 0000000000000..37ddb0129025d
--- /dev/null
+++ b/caravel/assets/javascripts/explorev2/components/charts/TimeSeriesLineChart.jsx
@@ -0,0 +1,70 @@
+import React, { PropTypes } from 'react';
+import * as V from 'victory';
+import theme from '../../../components/VictoryTheme';
+import moment from 'moment';
+import { schemeCategory20c } from 'd3-scale';
+import Legend from './Legend';
+
+const propTypes = {
+ data: PropTypes.array.isRequired,
+ label1: PropTypes.string.isRequired,
+};
+
+export default class TimeSeriesLineChart extends React.Component {
+ constructor(props) {
+ super(props);
+ this.keysToColorsMap = this.mapKeysToColors(props.data);
+ }
+
+ mapKeysToColors(data) {
+ // todo: what if there are more lines than colors in schemeCategory20c?
+ const keysToColorsMap = {};
+ data.forEach((d, i) => {
+ keysToColorsMap[d.key] = schemeCategory20c[i];
+ });
+ return keysToColorsMap;
+ }
+
+ renderLines() {
+ return this.props.data.map(function (d) {
+ return (
+
+ );
+ });
+ }
+
+ render() {
+ return (
+
+
+ {this.renderLines()}
+
+ d.x)}
+ tickFormat={(x) => moment(new Date(x)).format('YYYY')}
+ fixLabelOverlap
+ />
+
+
+
+ );
+ }
+}
+
+TimeSeriesLineChart.propTypes = propTypes;
diff --git a/caravel/assets/javascripts/explorev2/index.jsx b/caravel/assets/javascripts/explorev2/index.jsx
index 77dce35ea283a..47f1e63ca922c 100644
--- a/caravel/assets/javascripts/explorev2/index.jsx
+++ b/caravel/assets/javascripts/explorev2/index.jsx
@@ -1,7 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import ExploreViewContainer from './components/ExploreViewContainer';
-
import { createStore, applyMiddleware, compose } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
diff --git a/caravel/assets/package.json b/caravel/assets/package.json
index 82cd0025e4a16..3ce6e776566f5 100644
--- a/caravel/assets/package.json
+++ b/caravel/assets/package.json
@@ -48,6 +48,7 @@
"d3": "^3.5.14",
"d3-cloud": "^1.2.1",
"d3-sankey": "^0.2.1",
+ "d3-scale": "^1.0.3",
"d3-tip": "^0.6.7",
"datamaps": "^0.4.4",
"datatables-bootstrap3-plugin": "^0.4.0",
@@ -84,6 +85,7 @@
"style-loader": "^0.13.0",
"supercluster": "https://github.com/georgeke/supercluster/tarball/ac3492737e7ce98e07af679623aad452373bbc40",
"topojson": "^1.6.22",
+ "victory": "^0.12.1",
"viewport-mercator-project": "^2.1.0"
},
"devDependencies": {
diff --git a/caravel/assets/stylesheets/exploreV2/exploreV2.css b/caravel/assets/stylesheets/exploreV2/exploreV2.css
new file mode 100644
index 0000000000000..74de33c7d457b
--- /dev/null
+++ b/caravel/assets/stylesheets/exploreV2/exploreV2.css
@@ -0,0 +1,8 @@
+.table-body {
+ display: table;
+}
+
+.table-cell {
+ float: none;
+ display: table-cell;
+}
diff --git a/caravel/templates/caravel/explorev2.html b/caravel/templates/caravel/explorev2.html
index 57e0c3a4c7fae..a0644be1d36e7 100644
--- a/caravel/templates/caravel/explorev2.html
+++ b/caravel/templates/caravel/explorev2.html
@@ -1,4 +1,5 @@
{% extends "caravel/basic.html" %}
+
{% block body %}