From f837733d858643152304469c899c096e31d2bf24 Mon Sep 17 00:00:00 2001 From: Alanna Scott Date: Wed, 5 Oct 2016 19:41:16 -0700 Subject: [PATCH] [explorev2] chart and controls (#1251) * create structure for new forked explore view (#1099) * create structure for new forked explore view * update component name * add bootstrap data pattern * remove console.log * Created store and reducers (#1108) * Created store and reducers * Added spec * Modifications based on comments * do use bootstrap data for now * don't deal with bootstrap data for now * use victory as a base * import fake line data, add fake panels, make chart fixed * add fetch support * get slice data from json endpoint * render chart with slicejson * update chart and label demo * remove fetch config * remove dummy control panels * should be a func * make TimeSeriesLineChart * add a comment * inner height for height * don't need fetch yet * trailing comma breaks in package json * pass in viz data from props * add style sheet * set height on explore container * add legend * make chart responsive to window resize * can't use head_css in template bc overrides head_css in basic * fix linting * break labelItem into own SFC, make legend SFC * add propTypes and fix linter --- .../javascripts/components/VictoryTheme.js | 173 ++++++++++++++++++ .../explorev2/components/ChartContainer.jsx | 72 +++++++- .../components/ExploreViewContainer.jsx | 69 +++++-- .../explorev2/components/QueryAndSaveBtns.jsx | 31 ++++ .../explorev2/components/charts/Legend.jsx | 21 +++ .../components/charts/LegendItem.jsx | 17 ++ .../components/charts/TimeSeriesLineChart.jsx | 70 +++++++ .../assets/javascripts/explorev2/index.jsx | 1 - caravel/assets/package.json | 2 + .../stylesheets/exploreV2/exploreV2.css | 8 + caravel/templates/caravel/explorev2.html | 1 + 11 files changed, 436 insertions(+), 29 deletions(-) create mode 100644 caravel/assets/javascripts/components/VictoryTheme.js create mode 100644 caravel/assets/javascripts/explorev2/components/QueryAndSaveBtns.jsx create mode 100644 caravel/assets/javascripts/explorev2/components/charts/Legend.jsx create mode 100644 caravel/assets/javascripts/explorev2/components/charts/LegendItem.jsx create mode 100644 caravel/assets/javascripts/explorev2/components/charts/TimeSeriesLineChart.jsx create mode 100644 caravel/assets/stylesheets/exploreV2/exploreV2.css diff --git a/caravel/assets/javascripts/components/VictoryTheme.js b/caravel/assets/javascripts/components/VictoryTheme.js new file mode 100644 index 0000000000..aeefe6c1de --- /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 3666e923a2..00967f79d8 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 f6b6b52faa..31a11de84c 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 ExploreViewContainer; +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 ( +
+
+
+ {}} + /> +

+ +
+
+ +
+
+
+ ); + } +} + +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 0000000000..1a52139340 --- /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 0000000000..be253a33d2 --- /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 ( +
    + {legendEls} +
+ ); +} + +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 0000000000..b1770857f9 --- /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 0000000000..37ddb01290 --- /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 77dce35ea2..47f1e63ca9 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 82cd0025e4..3ce6e77656 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 0000000000..74de33c7d4 --- /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 57e0c3a4c7..a0644be1d3 100644 --- a/caravel/templates/caravel/explorev2.html +++ b/caravel/templates/caravel/explorev2.html @@ -1,4 +1,5 @@ {% extends "caravel/basic.html" %} + {% block body %}