New time_pivot visualization

(cherry picked from commit 527b2c0328797de73b6bd7b3d4cc2e0a57d1b926)
diff --git a/superset/assets/images/viz_thumbnails/time_pivot.png b/superset/assets/images/viz_thumbnails/time_pivot.png
new file mode 100644
index 0000000..149f3da
--- /dev/null
+++ b/superset/assets/images/viz_thumbnails/time_pivot.png
Binary files differ
diff --git a/superset/assets/javascripts/explore/components/Control.jsx b/superset/assets/javascripts/explore/components/Control.jsx
index e458807..25d69a5 100644
--- a/superset/assets/javascripts/explore/components/Control.jsx
+++ b/superset/assets/javascripts/explore/components/Control.jsx
@@ -13,6 +13,7 @@
   label: PropTypes.string.isRequired,
   choices: PropTypes.arrayOf(PropTypes.array),
   description: PropTypes.string,
+  tooltipOnClick: PropTypes.func,
   places: PropTypes.number,
   validators: PropTypes.array,
   validationErrors: PropTypes.array,
diff --git a/superset/assets/javascripts/explore/components/ControlHeader.jsx b/superset/assets/javascripts/explore/components/ControlHeader.jsx
index d2e0a6e..bc474a6 100644
--- a/superset/assets/javascripts/explore/components/ControlHeader.jsx
+++ b/superset/assets/javascripts/explore/components/ControlHeader.jsx
@@ -13,6 +13,7 @@
   leftNode: PropTypes.node,
   onClick: PropTypes.func,
   hovered: PropTypes.bool,
+  tooltipOnClick: PropTypes.func,
 };
 
 const defaultProps = {
@@ -32,6 +33,7 @@
                 label={t('description')}
                 tooltip={this.props.description}
                 placement="top"
+                onClick={this.props.tooltipOnClick}
               />
               {' '}
             </span>
diff --git a/superset/assets/javascripts/explore/stores/controls.jsx b/superset/assets/javascripts/explore/stores/controls.jsx
index 325c878..4ea4a2e 100644
--- a/superset/assets/javascripts/explore/stores/controls.jsx
+++ b/superset/assets/javascripts/explore/stores/controls.jsx
@@ -432,6 +432,29 @@
     'to find in the [country] column'),
   },
 
+  freq: {
+    type: 'SelectControl',
+    label: t('Frequency'),
+    default: '7D',
+    freeForm: true,
+    clearable: false,
+    choices: [
+      ['AS', '[AS] Year'],
+      ['52W', '[52W] 52 Weeks'],
+      ['W-SUN', '[W-SUN] Week (starting Sunday)'],
+      ['W-MON', '[W-MON] Week (starting Monday)'],
+      ['D', '[D] Day'],
+      ['4W', '[4W] 4 Weeks'],
+    ],
+    description: t(
+      'The frequency over which to pivot time. Free-form "pandas" offset alias ' +
+      'are allowed. Click on the info bubble for more details. '),
+    tooltipOnClick: () => {
+      window.open(
+        'https://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases');
+    },
+  },
+
   groupby: groupByControl,
   dimension: {
     ...groupByControl,
diff --git a/superset/assets/javascripts/explore/stores/visTypes.js b/superset/assets/javascripts/explore/stores/visTypes.js
index ef31003..9ad1141 100644
--- a/superset/assets/javascripts/explore/stores/visTypes.js
+++ b/superset/assets/javascripts/explore/stores/visTypes.js
@@ -195,6 +195,50 @@
     },
   },
 
+  time_pivot: {
+    label: t('Time Series - Period Pivot'),
+    showOnExplore: true,
+    requiresTime: true,
+    controlPanelSections: [
+      {
+        label: t('Query'),
+        expanded: true,
+        controlSetRows: [
+          ['metric', 'freq'],
+        ],
+      },
+      {
+        label: t('Chart Options'),
+        expanded: true,
+        controlSetRows: [
+          ['show_legend', 'line_interpolation'],
+          ['color_picker', null],
+        ],
+      },
+      {
+        label: t('X Axis'),
+        controlSetRows: [
+          ['x_axis_label', 'bottom_margin'],
+          ['x_axis_showminmax', 'x_axis_format'],
+        ],
+      },
+      {
+        label: t('Y Axis'),
+        controlSetRows: [
+          ['y_axis_label', 'left_margin'],
+          ['y_axis_showminmax', 'y_log_scale'],
+          ['y_axis_format', 'y_axis_bounds'],
+        ],
+      },
+    ],
+    controlOverrides: {
+      x_axis_format: {
+        choices: D3_TIME_FORMAT_OPTIONS,
+        default: 'smart_date',
+      },
+    },
+  },
+
   dual_line: {
     label: t('Dual Axis Line Chart'),
     requiresTime: true,
diff --git a/superset/assets/javascripts/modules/colors.js b/superset/assets/javascripts/modules/colors.js
index 03c3bb2..f182e97 100644
--- a/superset/assets/javascripts/modules/colors.js
+++ b/superset/assets/javascripts/modules/colors.js
@@ -1,6 +1,7 @@
 import d3 from 'd3';
 
 export const brandColor = '#00A699';
+export const colorPrimary = { r: 0, g: 122, b: 135, a: 1 };
 
 // Color related utility functions go in this object
 export const bnbColors = [
diff --git a/superset/assets/visualizations/main.js b/superset/assets/visualizations/main.js
index 2afc57b..9976614 100644
--- a/superset/assets/visualizations/main.js
+++ b/superset/assets/visualizations/main.js
@@ -18,6 +18,7 @@
   horizon: require('./horizon.js'),
   iframe: require('./iframe.js'),
   line: require('./nvd3_vis.js'),
+  time_pivot: require('./nvd3_vis.js'),
   mapbox: require('./mapbox.jsx'),
   markup: require('./markup.js'),
   para: require('./parallel_coordinates.js'),
diff --git a/superset/assets/visualizations/nvd3_vis.js b/superset/assets/visualizations/nvd3_vis.js
index bbef132..f1b6c11 100644
--- a/superset/assets/visualizations/nvd3_vis.js
+++ b/superset/assets/visualizations/nvd3_vis.js
@@ -166,6 +166,13 @@
         chart.xAxis.staggerLabels(false);
         break;
 
+      case 'time_pivot':
+        chart = nv.models.lineChart();
+        chart.xScale(d3.time.scale.utc());
+        chart.interpolate(fd.line_interpolation);
+        chart.xAxis.staggerLabels(false);
+        break;
+
       case 'dual_line':
         chart = nv.models.multiChart();
         chart.interpolate('linear');
@@ -337,7 +344,7 @@
       chart.xScale(d3.scale.log());
     }
     const isTimeSeries = [
-      'line', 'dual_line', 'area', 'compare', 'bar'].indexOf(vizType) >= 0;
+      'line', 'dual_line', 'area', 'compare', 'bar', 'time_pivot'].indexOf(vizType) >= 0;
     // if x axis format is a date format, rotate label 90 degrees
     if (isTimeSeries) {
       chart.xAxis.rotateLabels(45);
@@ -375,7 +382,16 @@
     setAxisShowMaxMin(chart.yAxis, fd.y_axis_showminmax);
     setAxisShowMaxMin(chart.y2Axis, fd.y_axis_showminmax);
 
-    if (vizType !== 'bullet') {
+    if (vizType === 'time_pivot') {
+      chart.color((d) => {
+        const c = fd.color_picker;
+        let alpha = 1;
+        if (d.rank > 0) {
+          alpha = d.perc * 0.5;
+        }
+        return `rgba(${c.r}, ${c.g}, ${c.b}, ${alpha})`;
+      });
+    } else if (vizType !== 'bullet') {
       chart.color(d => getColorFromScheme(d[colorKey], fd.color_scheme));
     }
     if ((vizType === 'line' || vizType === 'area') && fd.rich_tooltip) {
diff --git a/superset/viz.py b/superset/viz.py
index 6b369be..c37ef14 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -25,6 +25,7 @@
 from markdown import markdown
 import numpy as np
 import pandas as pd
+from pandas.tseries.frequencies import to_offset
 import simplejson as json
 from six import PY3, string_types
 from six.moves import reduce
@@ -947,14 +948,23 @@
             elif title_suffix and isinstance(series_title, (list, tuple)):
                 series_title = series_title + (title_suffix,)
 
+            values = []
+            for ds in df.index:
+                if ds in ys:
+                    d = {
+                        'x': ds,
+                        'y': ys[ds],
+                    }
+                else:
+                    d = {}
+                values.append(d)
+
             d = {
                 'key': series_title,
-                'classed': classed,
-                'values': [
-                    {'x': ds, 'y': ys[ds] if ds in ys else None}
-                    for ds in df.index
-                ],
+                'values': values,
             }
+            if classed:
+                d['classed'] = classed
             chart_data.append(d)
         return chart_data
 
@@ -1136,6 +1146,48 @@
     verbose_name = _('Time Series - Bar Chart')
 
 
+class NVD3TimePivotViz(NVD3TimeSeriesViz):
+
+    """Overlapping time series"""
+
+    viz_type = 'time_pivot'
+    sort_series = True
+    verbose_name = _('Time Series - Period Pivot')
+
+    def query_obj(self):
+        d = super(NVD3TimePivotViz, self).query_obj()
+        d['metrics'] = [self.form_data.get('metric')]
+        return d
+
+    def get_data(self, df):
+        fd = self.form_data
+        df = self.process_data(df)
+        freq = to_offset(fd.get('freq'))
+        freq.normalize = True
+        df[DTTM_ALIAS] = df.index.map(freq.rollback)
+        df['ranked'] = df[DTTM_ALIAS].rank(method='dense', ascending=False) - 1
+        df.ranked = df.ranked.map(int)
+        df['series'] = '-' + df.ranked.map(str)
+        df['series'] = df['series'].str.replace('-0', 'current')
+        rank_lookup = {
+            row['series']: row['ranked']
+            for row in df.to_dict(orient='records')
+        }
+        max_ts = df[DTTM_ALIAS].max()
+        max_rank = df['ranked'].max()
+        df[DTTM_ALIAS] = df.index + (max_ts - df[DTTM_ALIAS])
+        df = df.pivot_table(
+            index=DTTM_ALIAS,
+            columns='series',
+            values=fd.get('metric'))
+        df = df.fillna(0)
+        chart_data = self.to_series(df)
+        for serie in chart_data:
+            serie['rank'] = rank_lookup[serie['key']]
+            serie['perc'] = 1 - (serie['rank'] / (max_rank + 1))
+        return chart_data
+
+
 class NVD3CompareTimeSeriesViz(NVD3TimeSeriesViz):
 
     """A line chart component where you can compare the % change over time"""