mistercrunch closed pull request #3518: Full Annotation Framework
URL: https://github.com/apache/incubator-superset/pull/3518
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/superset/assets/backendSync.json b/superset/assets/backendSync.json
index 71e7130328..ba91b47cde 100644
--- a/superset/assets/backendSync.json
+++ b/superset/assets/backendSync.json
@@ -1,175 +1,15 @@
 {
   "controls": {
     "datasource": {
-      "type": "SelectControl",
+      "type": "DatasourceControl",
       "label": "Datasource",
-      "isLoading": true,
-      "clearable": false,
       "default": null,
-      "description": ""
+      "description": null
     },
     "viz_type": {
-      "type": "SelectControl",
+      "type": "VizTypeControl",
       "label": "Visualization Type",
-      "clearable": false,
       "default": "table",
-      "choices": [
-        [
-          "dist_bar",
-          "Distribution - Bar Chart",
-          "/static/assets/images/viz_thumbnails/dist_bar.png"
-        ],
-        [
-          "pie",
-          "Pie Chart",
-          "/static/assets/images/viz_thumbnails/pie.png"
-        ],
-        [
-          "line",
-          "Time Series - Line Chart",
-          "/static/assets/images/viz_thumbnails/line.png"
-        ],
-        [
-          "dual_line",
-          "Time Series - Dual Axis Line Chart",
-          "/static/assets/images/viz_thumbnails/dual_line.png"
-        ],
-        [
-          "bar",
-          "Time Series - Bar Chart",
-          "/static/assets/images/viz_thumbnails/bar.png"
-        ],
-        [
-          "compare",
-          "Time Series - Percent Change",
-          "/static/assets/images/viz_thumbnails/compare.png"
-        ],
-        [
-          "area",
-          "Time Series - Stacked",
-          "/static/assets/images/viz_thumbnails/area.png"
-        ],
-        [
-          "table",
-          "Table View",
-          "/static/assets/images/viz_thumbnails/table.png"
-        ],
-        [
-          "markup",
-          "Markup",
-          "/static/assets/images/viz_thumbnails/markup.png"
-        ],
-        [
-          "pivot_table",
-          "Pivot Table",
-          "/static/assets/images/viz_thumbnails/pivot_table.png"
-        ],
-        [
-          "separator",
-          "Separator",
-          "/static/assets/images/viz_thumbnails/separator.png"
-        ],
-        [
-          "word_cloud",
-          "Word Cloud",
-          "/static/assets/images/viz_thumbnails/word_cloud.png"
-        ],
-        [
-          "treemap",
-          "Treemap",
-          "/static/assets/images/viz_thumbnails/treemap.png"
-        ],
-        [
-          "cal_heatmap",
-          "Calendar Heatmap",
-          "/static/assets/images/viz_thumbnails/cal_heatmap.png"
-        ],
-        [
-          "box_plot",
-          "Box Plot",
-          "/static/assets/images/viz_thumbnails/box_plot.png"
-        ],
-        [
-          "bubble",
-          "Bubble Chart",
-          "/static/assets/images/viz_thumbnails/bubble.png"
-        ],
-        [
-          "bullet",
-          "Bullet Chart",
-          "/static/assets/images/viz_thumbnails/bullet.png"
-        ],
-        [
-          "big_number",
-          "Big Number with Trendline",
-          "/static/assets/images/viz_thumbnails/big_number.png"
-        ],
-        [
-          "big_number_total",
-          "Big Number",
-          "/static/assets/images/viz_thumbnails/big_number_total.png"
-        ],
-        [
-          "histogram",
-          "Histogram",
-          "/static/assets/images/viz_thumbnails/histogram.png"
-        ],
-        [
-          "sunburst",
-          "Sunburst",
-          "/static/assets/images/viz_thumbnails/sunburst.png"
-        ],
-        [
-          "sankey",
-          "Sankey",
-          "/static/assets/images/viz_thumbnails/sankey.png"
-        ],
-        [
-          "directed_force",
-          "Directed Force Layout",
-          "/static/assets/images/viz_thumbnails/directed_force.png"
-        ],
-        [
-          "country_map",
-          "Country Map",
-          "/static/assets/images/viz_thumbnails/country_map.png"
-        ],
-        [
-          "world_map",
-          "World Map",
-          "/static/assets/images/viz_thumbnails/world_map.png"
-        ],
-        [
-          "filter_box",
-          "Filter Box",
-          "/static/assets/images/viz_thumbnails/filter_box.png"
-        ],
-        [
-          "iframe",
-          "iFrame",
-          "/static/assets/images/viz_thumbnails/iframe.png"
-        ],
-        [
-          "para",
-          "Parallel Coordinates",
-          "/static/assets/images/viz_thumbnails/para.png"
-        ],
-        [
-          "heatmap",
-          "Heatmap",
-          "/static/assets/images/viz_thumbnails/heatmap.png"
-        ],
-        [
-          "horizon",
-          "Horizon",
-          "/static/assets/images/viz_thumbnails/horizon.png"
-        ],
-        [
-          "mapbox",
-          "Mapbox",
-          "/static/assets/images/viz_thumbnails/mapbox.png"
-        ]
-      ],
       "description": "The type of visualization to display"
     },
     "metrics": {
@@ -179,8 +19,26 @@
       "validators": [
         null
       ],
+      "valueKey": "metric_name",
       "description": "One or many metrics to display"
     },
+    "percent_metrics": {
+      "type": "SelectControl",
+      "multi": true,
+      "label": "Percentage Metrics",
+      "valueKey": "metric_name",
+      "description": "Metrics for which percentage of total are to be 
displayed"
+    },
+    "y_axis_bounds": {
+      "type": "BoundsControl",
+      "label": "Y Axis Bounds",
+      "renderTrigger": true,
+      "default": [
+        null,
+        null
+      ],
+      "description": "Bounds for the Y axis. When left empty, the bounds are 
dynamically defined based on the min/max of the data. Note that this feature 
will only expand the axis range. It won't narrow the data's extent."
+    },
     "order_by_cols": {
       "type": "SelectControl",
       "multi": true,
@@ -188,18 +46,38 @@
       "default": [],
       "description": "One or many metrics to display"
     },
+    "color_picker": {
+      "label": "Fixed Color",
+      "description": "Use this to define a static color for all circles",
+      "type": "ColorPickerControl",
+      "default": {
+        "r": 0,
+        "g": 122,
+        "b": 135,
+        "a": 1
+      },
+      "renderTrigger": true
+    },
     "metric": {
       "type": "SelectControl",
       "label": "Metric",
       "clearable": false,
-      "description": "Choose the metric"
+      "description": "Choose the metric",
+      "validators": [
+        null
+      ],
+      "valueKey": "metric_name"
     },
     "metric_2": {
       "type": "SelectControl",
       "label": "Right Axis Metric",
-      "choices": [],
-      "default": [],
-      "description": "Choose a metric for right axis"
+      "default": null,
+      "validators": [
+        null
+      ],
+      "clearable": true,
+      "description": "Choose a metric for right axis",
+      "valueKey": "metric_name"
     },
     "stacked_style": {
       "type": "SelectControl",
@@ -221,8 +99,56 @@
       "default": "stack",
       "description": ""
     },
-    "linear_color_scheme": {
+    "sort_x_axis": {
       "type": "SelectControl",
+      "label": "Sort X Axis",
+      "choices": [
+        [
+          "alpha_asc",
+          "Axis ascending"
+        ],
+        [
+          "alpha_desc",
+          "Axis descending"
+        ],
+        [
+          "value_asc",
+          "sum(value) ascending"
+        ],
+        [
+          "value_desc",
+          "sum(value) descending"
+        ]
+      ],
+      "clearable": false,
+      "default": "alpha_asc"
+    },
+    "sort_y_axis": {
+      "type": "SelectControl",
+      "label": "Sort Y Axis",
+      "choices": [
+        [
+          "alpha_asc",
+          "Axis ascending"
+        ],
+        [
+          "alpha_desc",
+          "Axis descending"
+        ],
+        [
+          "value_asc",
+          "sum(value) ascending"
+        ],
+        [
+          "value_desc",
+          "sum(value) descending"
+        ]
+      ],
+      "clearable": false,
+      "default": "alpha_asc"
+    },
+    "linear_color_scheme": {
+      "type": "ColorSchemeControl",
       "label": "Linear Color Scheme",
       "choices": [
         [
@@ -240,10 +166,54 @@
         [
           "black_white",
           "black/white"
+        ],
+        [
+          "dark_blue",
+          "light/dark blue"
+        ],
+        [
+          "pink_grey",
+          "pink/white/grey"
         ]
       ],
       "default": "blue_white_yellow",
-      "description": ""
+      "clearable": false,
+      "description": "",
+      "renderTrigger": true,
+      "schemes": {
+        "blue_white_yellow": [
+          "#00d1c1",
+          "white",
+          "#ffb400"
+        ],
+        "fire": [
+          "white",
+          "yellow",
+          "red",
+          "black"
+        ],
+        "white_black": [
+          "white",
+          "black"
+        ],
+        "black_white": [
+          "black",
+          "white"
+        ],
+        "dark_blue": [
+          "#EBF5F8",
+          "#6BB1CC",
+          "#357E9B",
+          "#1B4150",
+          "#092935"
+        ],
+        "pink_grey": [
+          "#E70B81",
+          "#FAFAFA",
+          "#666666"
+        ]
+      },
+      "isLinear": true
     },
     "normalize_across": {
       "type": "SelectControl",
@@ -288,6 +258,7 @@
     "canvas_image_rendering": {
       "type": "SelectControl",
       "label": "Rendering",
+      "renderTrigger": true,
       "choices": [
         [
           "pixelated",
@@ -723,6 +694,13 @@
       "description": "Whether to include the time granularity as defined in 
the time section",
       "default": false
     },
+    "show_perc": {
+      "type": "CheckboxControl",
+      "label": "Show percentage",
+      "renderTrigger": true,
+      "description": "Whether to include the percentage in the tooltip",
+      "default": true
+    },
     "bar_stacked": {
       "type": "CheckboxControl",
       "label": "Stacked Bars",
@@ -730,6 +708,13 @@
       "default": false,
       "description": null
     },
+    "pivot_margins": {
+      "type": "CheckboxControl",
+      "label": "Show totals",
+      "renderTrigger": false,
+      "default": true,
+      "description": "Display total row/column"
+    },
     "show_markers": {
       "type": "CheckboxControl",
       "label": "Show Markers",
@@ -785,29 +770,21 @@
     },
     "select_country": {
       "type": "SelectControl",
-      "label": "Country Name Type",
+      "label": "Country Name",
       "default": "France",
       "choices": [
-        [
-          "Algeria",
-          "Algeria"
-        ],
         [
           "Belgium",
           "Belgium"
         ],
         [
-          "Brasil",
-          "Brasil"
+          "Brazil",
+          "Brazil"
         ],
         [
           "China",
           "China"
         ],
-        [
-          "Germany",
-          "Germany"
-        ],
         [
           "Egypt",
           "Egypt"
@@ -816,6 +793,10 @@
           "France",
           "France"
         ],
+        [
+          "Germany",
+          "Germany"
+        ],
         [
           "Italy",
           "Italy"
@@ -825,8 +806,8 @@
           "Morocco"
         ],
         [
-          "Nederlanden",
-          "Nederlanden"
+          "Netherlands",
+          "Netherlands"
         ],
         [
           "Russia",
@@ -844,6 +825,10 @@
           "Uk",
           "Uk"
         ],
+        [
+          "Ukraine",
+          "Ukraine"
+        ],
         [
           "Usa",
           "Usa"
@@ -875,19 +860,66 @@
       ],
       "description": "The country code standard that Superset should expect to 
find in the [country] column"
     },
+    "freq": {
+      "type": "SelectControl",
+      "label": "Frequency",
+      "default": "W-MON",
+      "freeForm": true,
+      "clearable": false,
+      "choices": [
+        [
+          "AS",
+          "Year (freq=AS)"
+        ],
+        [
+          "52W-MON",
+          "52 weeks starting Monday (freq=52W-MON)"
+        ],
+        [
+          "W-SUN",
+          "1 week starting Sunday (freq=W-SUN)"
+        ],
+        [
+          "W-MON",
+          "1 week starting Monday (freq=W-MON)"
+        ],
+        [
+          "D",
+          "Day (freq=D)"
+        ],
+        [
+          "4W-MON",
+          "4 weeks (freq=4W-MON)"
+        ]
+      ],
+      "description": "The periodicity over which to pivot time. Users can 
provide\n      \"Pandas\" offset alias.\n      Click on the info bubble for 
more details on accepted \"freq\" expressions."
+    },
     "groupby": {
       "type": "SelectControl",
       "multi": true,
       "label": "Group by",
       "default": [],
-      "description": "One or many controls to group by"
+      "includeTime": false,
+      "description": "One or many controls to group by",
+      "valueKey": "column_name"
+    },
+    "dimension": {
+      "type": "SelectControl",
+      "multi": false,
+      "label": "Dimension",
+      "default": null,
+      "includeTime": false,
+      "description": "Select a dimension",
+      "valueKey": "column_name"
     },
     "columns": {
       "type": "SelectControl",
       "multi": true,
       "label": "Columns",
       "default": [],
-      "description": "One or many controls to pivot as columns"
+      "includeTime": false,
+      "description": "One or many controls to pivot as columns",
+      "valueKey": "column_name"
     },
     "all_columns": {
       "type": "SelectControl",
@@ -896,6 +928,24 @@
       "default": [],
       "description": "Columns to display"
     },
+    "longitude": {
+      "type": "SelectControl",
+      "label": "Longitude",
+      "default": 1,
+      "validators": [
+        null
+      ],
+      "description": "Select the longitude column"
+    },
+    "latitude": {
+      "type": "SelectControl",
+      "label": "Latitude",
+      "default": 1,
+      "validators": [
+        null
+      ],
+      "description": "Select the latitude column"
+    },
     "all_columns_x": {
       "type": "SelectControl",
       "label": "X",
@@ -960,7 +1010,46 @@
         ]
       ],
       "default": "auto",
-      "description": "Bottom marging, in pixels, allowing for more room for 
axis labels"
+      "renderTrigger": true,
+      "description": "Bottom margin, in pixels, allowing for more room for 
axis labels"
+    },
+    "left_margin": {
+      "type": "SelectControl",
+      "freeForm": true,
+      "label": "Left Margin",
+      "choices": [
+        [
+          "auto",
+          "auto"
+        ],
+        [
+          50,
+          "50"
+        ],
+        [
+          75,
+          "75"
+        ],
+        [
+          100,
+          "100"
+        ],
+        [
+          125,
+          "125"
+        ],
+        [
+          150,
+          "150"
+        ],
+        [
+          200,
+          "200"
+        ]
+      ],
+      "default": "auto",
+      "renderTrigger": true,
+      "description": "Left margin, in pixels, allowing for more room for axis 
labels"
     },
     "granularity": {
       "type": "SelectControl",
@@ -1172,7 +1261,9 @@
     "granularity_sqla": {
       "type": "SelectControl",
       "label": "Time Column",
-      "description": "The time column for the visualization. Note that you can 
define arbitrary expression that return a DATETIME column in the table or. Also 
note that the filter below is applied against this column or expression"
+      "description": "The time column for the visualization. Note that you can 
define arbitrary expression that return a DATETIME column in the table. Also 
note that the filter below is applied against this column or expression",
+      "clearable": false,
+      "valueKey": "column_name"
     },
     "time_grain_sqla": {
       "type": "SelectControl",
@@ -1263,77 +1354,16 @@
       "description": "Pandas resample fill method"
     },
     "since": {
-      "type": "SelectControl",
+      "type": "DateFilterControl",
       "freeForm": true,
       "label": "Since",
-      "default": "7 days ago",
-      "choices": [
-        [
-          "1 hour ago",
-          "1 hour ago"
-        ],
-        [
-          "12 hours ago",
-          "12 hours ago"
-        ],
-        [
-          "1 day ago",
-          "1 day ago"
-        ],
-        [
-          "7 days ago",
-          "7 days ago"
-        ],
-        [
-          "28 days ago",
-          "28 days ago"
-        ],
-        [
-          "90 days ago",
-          "90 days ago"
-        ],
-        [
-          "1 year ago",
-          "1 year ago"
-        ],
-        [
-          "100 year ago",
-          "100 year ago"
-        ]
-      ],
-      "description": "Timestamp from filter. This supports free form typing 
and natural language as in `1 day ago`, `28 days` or `3 years`"
+      "default": "7 days ago"
     },
     "until": {
-      "type": "SelectControl",
+      "type": "DateFilterControl",
       "freeForm": true,
       "label": "Until",
-      "default": "now",
-      "choices": [
-        [
-          "now",
-          "now"
-        ],
-        [
-          "1 day ago",
-          "1 day ago"
-        ],
-        [
-          "7 days ago",
-          "7 days ago"
-        ],
-        [
-          "28 days ago",
-          "28 days ago"
-        ],
-        [
-          "90 days ago",
-          "90 days ago"
-        ],
-        [
-          "1 year ago",
-          "1 year ago"
-        ]
-      ]
+      "default": "now"
     },
     "max_bubble_size": {
       "type": "SelectControl",
@@ -1441,6 +1471,9 @@
       "type": "SelectControl",
       "freeForm": true,
       "label": "Row limit",
+      "validators": [
+        null
+      ],
       "default": null,
       "choices": [
         [
@@ -1485,6 +1518,9 @@
       "type": "SelectControl",
       "freeForm": true,
       "label": "Series limit",
+      "validators": [
+        null
+      ],
       "choices": [
         [
           0,
@@ -1524,6 +1560,12 @@
       "default": null,
       "description": "Metric used to define the top series"
     },
+    "order_desc": {
+      "type": "CheckboxControl",
+      "label": "Sort Descending",
+      "default": true,
+      "description": "Whether to sort descending or ascending"
+    },
     "rolling_type": {
       "type": "SelectControl",
       "label": "Rolling",
@@ -1552,12 +1594,33 @@
       ],
       "description": "Defines a rolling window function to apply, works along 
with the [Periods] text box"
     },
+    "multiplier": {
+      "type": "TextControl",
+      "label": "Multiplier",
+      "isFloat": true,
+      "default": 1,
+      "description": "Factor to multiply the metric by"
+    },
     "rolling_periods": {
       "type": "TextControl",
       "label": "Periods",
       "isInt": true,
       "description": "Defines the size of the rolling window function, 
relative to the time granularity selected"
     },
+    "grid_size": {
+      "type": "TextControl",
+      "label": "Grid Size",
+      "renderTrigger": true,
+      "default": 20,
+      "isInt": true,
+      "description": "Defines the grid size in pixels"
+    },
+    "min_periods": {
+      "type": "TextControl",
+      "label": "Min Periods",
+      "isInt": true,
+      "description": "The minimum number of rolling periods required to show a 
value. For instance if you do a cumulative sum on 7 days you may want your 
\"Min Period\" to be 7, so that all data points shown are the total of 7 
periods. This will hide the \"ramp up\" taking place over the first 7 periods"
+    },
     "series": {
       "type": "SelectControl",
       "label": "Series",
@@ -1568,24 +1631,39 @@
       "type": "SelectControl",
       "label": "Entity",
       "default": null,
-      "description": "This define the element to be plotted on the chart"
+      "validators": [
+        null
+      ],
+      "description": "This defines the element to be plotted on the chart"
     },
     "x": {
       "type": "SelectControl",
       "label": "X Axis",
+      "description": "Metric assigned to the [X] axis",
       "default": null,
-      "description": "Metric assigned to the [X] axis"
+      "validators": [
+        null
+      ],
+      "valueKey": "metric_name"
     },
     "y": {
       "type": "SelectControl",
       "label": "Y Axis",
       "default": null,
-      "description": "Metric assigned to the [Y] axis"
+      "validators": [
+        null
+      ],
+      "description": "Metric assigned to the [Y] axis",
+      "valueKey": "metric_name"
     },
     "size": {
       "type": "SelectControl",
       "label": "Bubble Size",
-      "default": null
+      "default": null,
+      "validators": [
+        null
+      ],
+      "valueKey": "metric_name"
     },
     "url": {
       "type": "TextControl",
@@ -1632,7 +1710,11 @@
       "type": "SelectControl",
       "freeForm": true,
       "label": "Table Timestamp Format",
-      "default": "smart_date",
+      "default": "%Y-%m-%d %H:%M:%S",
+      "validators": [
+        null
+      ],
+      "clearable": false,
       "choices": [
         [
           "smart_date",
@@ -1746,7 +1828,41 @@
     "x_axis_format": {
       "type": "SelectControl",
       "freeForm": true,
-      "label": "X axis format",
+      "label": "X Axis Format",
+      "renderTrigger": true,
+      "default": ".3s",
+      "choices": [
+        [
+          ".3s",
+          ".3s | 12.3k"
+        ],
+        [
+          ".3%",
+          ".3% | 1234543.210%"
+        ],
+        [
+          ".4r",
+          ".4r | 12350"
+        ],
+        [
+          ".3f",
+          ".3f | 12345.432"
+        ],
+        [
+          "+,",
+          "+, | +12,345.4321"
+        ],
+        [
+          "$,.2f",
+          "$,.2f | $12,345.43"
+        ]
+      ],
+      "description": "D3 format syntax: https://github.com/d3/d3-format";
+    },
+    "x_axis_time_format": {
+      "type": "SelectControl",
+      "freeForm": true,
+      "label": "X Axis Format",
       "renderTrigger": true,
       "default": "smart_date",
       "choices": [
@@ -1776,7 +1892,7 @@
     "y_axis_format": {
       "type": "SelectControl",
       "freeForm": true,
-      "label": "Y axis format",
+      "label": "Y Axis Format",
       "renderTrigger": true,
       "default": ".3s",
       "choices": [
@@ -1810,7 +1926,7 @@
     "y_axis_2_format": {
       "type": "SelectControl",
       "freeForm": true,
-      "label": "Right axis format",
+      "label": "Right Axis Format",
       "default": ".3s",
       "choices": [
         [
@@ -1840,9 +1956,40 @@
       ],
       "description": "D3 format syntax: https://github.com/d3/d3-format";
     },
+    "date_time_format": {
+      "type": "SelectControl",
+      "freeForm": true,
+      "label": "Date Time Format",
+      "renderTrigger": true,
+      "default": "smart_date",
+      "choices": [
+        [
+          "smart_date",
+          "Adaptative formating"
+        ],
+        [
+          "%m/%d/%Y",
+          "%m/%d/%Y | 01/14/2019"
+        ],
+        [
+          "%Y-%m-%d",
+          "%Y-%m-%d | 2019-01-14"
+        ],
+        [
+          "%Y-%m-%d %H:%M:%S",
+          "%Y-%m-%d %H:%M:%S | 2019-01-14 01:32:10"
+        ],
+        [
+          "%H:%M:%S",
+          "%H:%M:%S | 01:32:10"
+        ]
+      ],
+      "description": "D3 format syntax: https://github.com/d3/d3-format";
+    },
     "markup_type": {
       "type": "SelectControl",
       "label": "Markup Type",
+      "clearable": false,
       "choices": [
         [
           "markdown",
@@ -1854,6 +2001,9 @@
         ]
       ],
       "default": "markdown",
+      "validators": [
+        null
+      ],
       "description": "Pick your favorite markup language"
     },
     "rotation": {
@@ -1925,6 +2075,14 @@
         [
           "percent",
           "Percentage"
+        ],
+        [
+          "key_value",
+          "Category and Value"
+        ],
+        [
+          "key_percent",
+          "Category and Percentage"
         ]
       ],
       "description": "What should be shown on the label?"
@@ -1993,6 +2151,13 @@
       "default": true,
       "description": "Whether to apply filters as they change, or wait 
forusers to hit an [Apply] button"
     },
+    "extruded": {
+      "type": "CheckboxControl",
+      "label": "Extruded",
+      "renderTrigger": true,
+      "default": true,
+      "description": "Whether to make the grid 3D"
+    },
     "show_brush": {
       "type": "CheckboxControl",
       "label": "Range Filter",
@@ -2006,6 +2171,30 @@
       "default": false,
       "description": "Whether to include a time filter"
     },
+    "show_sqla_time_granularity": {
+      "type": "CheckboxControl",
+      "label": "Show SQL Granularity Dropdown",
+      "default": false,
+      "description": "Check to include SQL Granularity dropdown"
+    },
+    "show_sqla_time_column": {
+      "type": "CheckboxControl",
+      "label": "Show SQL Time Column",
+      "default": false,
+      "description": "Check to include Time Column dropdown"
+    },
+    "show_druid_time_granularity": {
+      "type": "CheckboxControl",
+      "label": "Show Druid Granularity Dropdown",
+      "default": false,
+      "description": "Check to include Druid Granularity dropdown"
+    },
+    "show_druid_time_origin": {
+      "type": "CheckboxControl",
+      "label": "Show Druid Time Origin",
+      "default": false,
+      "description": "Check to include Time Origin dropdown"
+    },
     "show_datatable": {
       "type": "CheckboxControl",
       "label": "Data Table",
@@ -2039,6 +2228,13 @@
       "default": true,
       "description": "Whether to display the legend (toggles)"
     },
+    "show_values": {
+      "type": "CheckboxControl",
+      "label": "Show Values",
+      "renderTrigger": true,
+      "default": false,
+      "description": "Whether to display the numerical values within the cells"
+    },
     "x_axis_showminmax": {
       "type": "CheckboxControl",
       "label": "X bounds",
@@ -2046,19 +2242,19 @@
       "default": true,
       "description": "Whether to display the min and max values of the X axis"
     },
-    "rich_tooltip": {
+    "y_axis_showminmax": {
       "type": "CheckboxControl",
-      "label": "Rich Tooltip",
+      "label": "Y bounds",
       "renderTrigger": true,
       "default": true,
-      "description": "The rich tooltip shows a list of all series for that 
point in time"
+      "description": "Whether to display the min and max values of the Y axis"
     },
-    "y_axis_zero": {
+    "rich_tooltip": {
       "type": "CheckboxControl",
-      "label": "Y Axis Zero",
-      "default": false,
+      "label": "Rich Tooltip",
       "renderTrigger": true,
-      "description": "Force the Y axis to start at 0 instead of the minimum 
value"
+      "default": true,
+      "description": "The rich tooltip shows a list of all series for that 
point in time"
     },
     "y_log_scale": {
       "type": "CheckboxControl",
@@ -2074,16 +2270,25 @@
       "renderTrigger": true,
       "description": "Use a log scale for the X axis"
     },
+    "log_scale": {
+      "type": "CheckboxControl",
+      "label": "Log Scale",
+      "default": false,
+      "renderTrigger": true,
+      "description": "Use a log scale"
+    },
     "donut": {
       "type": "CheckboxControl",
       "label": "Donut",
       "default": false,
+      "renderTrigger": true,
       "description": "Do you want a donut or a pie?"
     },
     "labels_outside": {
       "type": "CheckboxControl",
       "label": "Put labels outside",
       "default": true,
+      "renderTrigger": true,
       "description": "Put the labels outside the pie?"
     },
     "contribution": {
@@ -2140,6 +2345,7 @@
     "mapbox_style": {
       "type": "SelectControl",
       "label": "Map Style",
+      "renderTrigger": true,
       "choices": [
         [
           "mapbox://styles/mapbox/streets-v9",
@@ -2214,6 +2420,11 @@
       ],
       "description": "The radius (in pixels) the algorithm uses to define a 
cluster. Choose 0 to turn off clustering, but beware that a large number of 
points (>1000) will cause lag."
     },
+    "point_radius_fixed": {
+      "type": "FixedOrMetricControl",
+      "label": "Point Size",
+      "description": "Fixed point radius"
+    },
     "point_radius": {
       "type": "SelectControl",
       "label": "Point Radius",
@@ -2240,6 +2451,39 @@
       ],
       "description": "The unit of measure for the specified point radius"
     },
+    "point_unit": {
+      "type": "SelectControl",
+      "label": "Point Unit",
+      "default": "square_m",
+      "clearable": false,
+      "choices": [
+        [
+          "square_m",
+          "Square meters"
+        ],
+        [
+          "square_km",
+          "Square kilometers"
+        ],
+        [
+          "square_miles",
+          "Square miles"
+        ],
+        [
+          "radius_m",
+          "Radius in meters"
+        ],
+        [
+          "radius_km",
+          "Radius in kilometers"
+        ],
+        [
+          "radius_miles",
+          "Radius in miles"
+        ]
+      ],
+      "description": "The unit of measure for the specified point radius"
+    },
     "global_opacity": {
       "type": "TextControl",
       "label": "Opacity",
@@ -2247,6 +2491,19 @@
       "isFloat": true,
       "description": "Opacity of all clusters, points, and labels. Between 0 
and 1."
     },
+    "viewport": {
+      "type": "ViewportControl",
+      "label": "Viewport",
+      "renderTrigger": true,
+      "description": "Parameters related to the view and perspective on the 
map",
+      "default": {
+        "longitude": 6.85236157047845,
+        "latitude": 31.222656842808707,
+        "zoom": 1,
+        "bearing": 0,
+        "pitch": 0
+      }
+    },
     "viewport_zoom": {
       "type": "TextControl",
       "label": "Zoom",
@@ -2310,6 +2567,17 @@
       ],
       "description": "The color for points and clusters in RGB"
     },
+    "color": {
+      "type": "ColorPickerControl",
+      "label": "Color",
+      "default": {
+        "r": 0,
+        "g": 122,
+        "b": 135,
+        "a": 1
+      },
+      "description": "Pick a color"
+    },
     "ranges": {
       "type": "TextControl",
       "label": "Ranges",
@@ -2352,6 +2620,13 @@
       "default": [],
       "description": ""
     },
+    "annotation_layers": {
+      "type": "AnnotationLayerControl",
+      "label": "",
+      "default": [],
+      "description": "Annotation Layers",
+      "renderTrigger": true
+    },
     "having_filters": {
       "type": "FilterControl",
       "label": "",
@@ -2369,6 +2644,334 @@
       "label": "Cache Timeout (seconds)",
       "hidden": true,
       "description": "The number of seconds before expiring the cache"
+    },
+    "order_by_entity": {
+      "type": "CheckboxControl",
+      "label": "Order by entity id",
+      "description": "Important! Select this if the table is not already 
sorted by entity id, else there is no guarantee that all events for each entity 
are returned.",
+      "default": true
+    },
+    "min_leaf_node_event_count": {
+      "type": "SelectControl",
+      "freeForm": false,
+      "label": "Minimum leaf node event count",
+      "default": 1,
+      "choices": [
+        [
+          1,
+          "1"
+        ],
+        [
+          2,
+          "2"
+        ],
+        [
+          3,
+          "3"
+        ],
+        [
+          4,
+          "4"
+        ],
+        [
+          5,
+          "5"
+        ],
+        [
+          6,
+          "6"
+        ],
+        [
+          7,
+          "7"
+        ],
+        [
+          8,
+          "8"
+        ],
+        [
+          9,
+          "9"
+        ],
+        [
+          10,
+          "10"
+        ]
+      ],
+      "description": "Leaf nodes that represent fewer than this number of 
events will be initially hidden in the visualization"
+    },
+    "color_scheme": {
+      "type": "ColorSchemeControl",
+      "label": "Color Scheme",
+      "default": "bnbColors",
+      "renderTrigger": true,
+      "choices": [
+        [
+          "bnbColors",
+          "bnbColors"
+        ],
+        [
+          "d3Category10",
+          "d3Category10"
+        ],
+        [
+          "d3Category20",
+          "d3Category20"
+        ],
+        [
+          "d3Category20b",
+          "d3Category20b"
+        ],
+        [
+          "d3Category20c",
+          "d3Category20c"
+        ],
+        [
+          "googleCategory10c",
+          "googleCategory10c"
+        ],
+        [
+          "googleCategory20c",
+          "googleCategory20c"
+        ]
+      ],
+      "description": "The color scheme for rendering chart",
+      "schemes": {
+        "bnbColors": [
+          "#ff5a5f",
+          "#7b0051",
+          "#007A87",
+          "#00d1c1",
+          "#8ce071",
+          "#ffb400",
+          "#b4a76c",
+          "#ff8083",
+          "#cc0086",
+          "#00a1b3",
+          "#00ffeb",
+          "#bbedab",
+          "#ffd266",
+          "#cbc29a",
+          "#ff3339",
+          "#ff1ab1",
+          "#005c66",
+          "#00b3a5",
+          "#55d12e",
+          "#b37e00",
+          "#988b4e"
+        ],
+        "d3Category10": [
+          "#1f77b4",
+          "#ff7f0e",
+          "#2ca02c",
+          "#d62728",
+          "#9467bd",
+          "#8c564b",
+          "#e377c2",
+          "#7f7f7f",
+          "#bcbd22",
+          "#17becf"
+        ],
+        "d3Category20": [
+          "#1f77b4",
+          "#aec7e8",
+          "#ff7f0e",
+          "#ffbb78",
+          "#2ca02c",
+          "#98df8a",
+          "#d62728",
+          "#ff9896",
+          "#9467bd",
+          "#c5b0d5",
+          "#8c564b",
+          "#c49c94",
+          "#e377c2",
+          "#f7b6d2",
+          "#7f7f7f",
+          "#c7c7c7",
+          "#bcbd22",
+          "#dbdb8d",
+          "#17becf",
+          "#9edae5"
+        ],
+        "d3Category20b": [
+          "#393b79",
+          "#5254a3",
+          "#6b6ecf",
+          "#9c9ede",
+          "#637939",
+          "#8ca252",
+          "#b5cf6b",
+          "#cedb9c",
+          "#8c6d31",
+          "#bd9e39",
+          "#e7ba52",
+          "#e7cb94",
+          "#843c39",
+          "#ad494a",
+          "#d6616b",
+          "#e7969c",
+          "#7b4173",
+          "#a55194",
+          "#ce6dbd",
+          "#de9ed6"
+        ],
+        "d3Category20c": [
+          "#3182bd",
+          "#6baed6",
+          "#9ecae1",
+          "#c6dbef",
+          "#e6550d",
+          "#fd8d3c",
+          "#fdae6b",
+          "#fdd0a2",
+          "#31a354",
+          "#74c476",
+          "#a1d99b",
+          "#c7e9c0",
+          "#756bb1",
+          "#9e9ac8",
+          "#bcbddc",
+          "#dadaeb",
+          "#636363",
+          "#969696",
+          "#bdbdbd",
+          "#d9d9d9"
+        ],
+        "googleCategory10c": [
+          "#3366cc",
+          "#dc3912",
+          "#ff9900",
+          "#109618",
+          "#990099",
+          "#0099c6",
+          "#dd4477",
+          "#66aa00",
+          "#b82e2e",
+          "#316395"
+        ],
+        "googleCategory20c": [
+          "#3366cc",
+          "#dc3912",
+          "#ff9900",
+          "#109618",
+          "#990099",
+          "#0099c6",
+          "#dd4477",
+          "#66aa00",
+          "#b82e2e",
+          "#316395",
+          "#994499",
+          "#22aa99",
+          "#aaaa11",
+          "#6633cc",
+          "#e67300",
+          "#8b0707",
+          "#651067",
+          "#329262",
+          "#5574a6",
+          "#3b3eac"
+        ]
+      }
+    },
+    "significance_level": {
+      "type": "TextControl",
+      "label": "Significance Level",
+      "default": 0.05,
+      "description": "Threshold alpha level for determining significance"
+    },
+    "pvalue_precision": {
+      "type": "TextControl",
+      "label": "p-value precision",
+      "default": 6,
+      "description": "Number of decimal places with which to display p-values"
+    },
+    "liftvalue_precision": {
+      "type": "TextControl",
+      "label": "Lift percent precision",
+      "default": 4,
+      "description": "Number of decimal places with which to display lift 
values"
+    },
+    "column_collection": {
+      "type": "CollectionControl",
+      "label": "Time Series Columns",
+      "validators": [
+        null
+      ],
+      "controlName": "TimeSeriesColumnControl"
+    },
+    "time_series_option": {
+      "type": "SelectControl",
+      "label": "Options",
+      "validators": [
+        null
+      ],
+      "default": "not_time",
+      "valueKey": "value",
+      "options": [
+        {
+          "label": "Not Time Series",
+          "value": "not_time",
+          "description": "Ignore time"
+        },
+        {
+          "label": "Time Series",
+          "value": "time_series",
+          "description": "Standard time series"
+        },
+        {
+          "label": "Aggregate Mean",
+          "value": "agg_mean",
+          "description": "Mean of values over specified period"
+        },
+        {
+          "label": "Aggregate Sum",
+          "value": "agg_sum",
+          "description": "Sum of values over specified period"
+        },
+        {
+          "label": "Difference",
+          "value": "point_diff",
+          "description": "Metric change in value from `since` to `until`"
+        },
+        {
+          "label": "Percent Change",
+          "value": "point_percent",
+          "description": "Metric percent change in value from `since` to 
`until`"
+        },
+        {
+          "label": "Factor",
+          "value": "point_factor",
+          "description": "Metric factor change from `since` to `until`"
+        },
+        {
+          "label": "Advanced Analytics",
+          "value": "adv_anal",
+          "description": "Use the Advanced Analytics options below"
+        }
+      ],
+      "description": "Settings for time series"
+    },
+    "equal_date_size": {
+      "type": "CheckboxControl",
+      "label": "Equal Date Sizes",
+      "default": true,
+      "renderTrigger": true,
+      "description": "Check to force date partitions to have the same height"
+    },
+    "partition_limit": {
+      "type": "TextControl",
+      "label": "Partition Limit",
+      "isInt": true,
+      "default": "5",
+      "description": "The maximum number of subdivisions of each group; lower 
values are pruned first"
+    },
+    "partition_threshold": {
+      "type": "TextControl",
+      "label": "Partition Threshold",
+      "isFloat": true,
+      "default": "0.05",
+      "description": "Partitions whose height to parent height proportions are 
below this value are pruned"
     }
   }
 }
\ No newline at end of file
diff --git a/superset/assets/javascripts/chart/Chart.jsx 
b/superset/assets/javascripts/chart/Chart.jsx
index a4e3dd2abd..3dd0355ab0 100644
--- a/superset/assets/javascripts/chart/Chart.jsx
+++ b/superset/assets/javascripts/chart/Chart.jsx
@@ -10,6 +10,7 @@ import StackTraceMessage from 
'../components/StackTraceMessage';
 import visMap from '../../visualizations/main';
 
 const propTypes = {
+  annotationData: PropTypes.object,
   actions: PropTypes.object,
   chartKey: PropTypes.string.isRequired,
   containerId: PropTypes.string.isRequired,
@@ -47,8 +48,8 @@ const defaultProps = {
 class Chart extends React.PureComponent {
   constructor(props) {
     super(props);
-
     // these properties are used by visualizations
+    this.annotationData = props.annotationData;
     this.containerId = props.containerId;
     this.selector = `#${this.containerId}`;
     this.formData = props.formData;
@@ -71,6 +72,7 @@ class Chart extends React.PureComponent {
   }
 
   componentWillReceiveProps(nextProps) {
+    this.annotationData = nextProps.annotationData;
     this.containerId = nextProps.containerId;
     this.selector = `#${this.containerId}`;
     this.formData = nextProps.formData;
@@ -82,6 +84,7 @@ class Chart extends React.PureComponent {
         this.props.queryResponse &&
         this.props.chartStatus === 'success' &&
         !this.props.queryResponse.error && (
+        prevProps.annotationData !== this.props.annotationData ||
         prevProps.queryResponse !== this.props.queryResponse ||
         prevProps.height !== this.props.height ||
         prevProps.width !== this.props.width ||
diff --git a/superset/assets/javascripts/chart/ChartContainer.jsx 
b/superset/assets/javascripts/chart/ChartContainer.jsx
index d517677ec4..b731412fc5 100644
--- a/superset/assets/javascripts/chart/ChartContainer.jsx
+++ b/superset/assets/javascripts/chart/ChartContainer.jsx
@@ -7,6 +7,7 @@ import Chart from './Chart';
 function mapStateToProps({ charts }, ownProps) {
   const chart = charts[ownProps.chartKey];
   return {
+    annotationData: chart.annotationData,
     chartAlert: chart.chartAlert,
     chartStatus: chart.chartStatus,
     chartUpdateEndTime: chart.chartUpdateEndTime,
diff --git a/superset/assets/javascripts/chart/chartAction.js 
b/superset/assets/javascripts/chart/chartAction.js
index 17205a41a3..a6341ddb6a 100644
--- a/superset/assets/javascripts/chart/chartAction.js
+++ b/superset/assets/javascripts/chart/chartAction.js
@@ -1,5 +1,5 @@
-import { getExploreUrl } from '../explore/exploreUtils';
-import { t } from '../locales';
+import { getExploreUrl, getAnnotationJsonUrl } from '../explore/exploreUtils';
+import { requiresQuery, ANNOTATION_SOURCE_TYPES } from 
'../modules/AnnotationTypes';
 
 const $ = window.$ = require('jquery');
 
@@ -41,6 +41,57 @@ export function removeChart(key) {
   return { type: REMOVE_CHART, key };
 }
 
+export const ANNOTATION_QUERY_SUCCESS = 'ANNOTATION_QUERY_SUCCESS';
+export function annotationQuerySuccess(annotation, queryResponse, key) {
+  return { type: ANNOTATION_QUERY_SUCCESS, annotation, queryResponse, key };
+}
+
+export const ANNOTATION_QUERY_STARTED = 'ANNOTATION_QUERY_STARTED';
+export function annotationQueryStarted(annotation, queryRequest, key) {
+  return { type: ANNOTATION_QUERY_STARTED, annotation, queryRequest, key };
+}
+
+export const ANNOTATION_QUERY_FAILED = 'ANNOTATION_QUERY_FAILED';
+export function annotationQueryFailed(annotation, queryResponse, key) {
+  return { type: ANNOTATION_QUERY_FAILED, annotation, queryResponse, key };
+}
+
+export function runAnnotationQuery(annotation, timeout = 60, formData = null, 
key) {
+  return function (dispatch, getState) {
+    const sliceKey = key || Object.keys(getState().charts)[0];
+    const fd = formData || getState().charts[sliceKey].latestQueryFormData;
+
+    if (!requiresQuery(annotation.sourceType)) {
+      return Promise.resolve();
+    }
+
+    const sliceFormData = Object.keys(annotation.overrides)
+      .reduce((d, k) => ({
+        ...d,
+        [k]: annotation.overrides[k] || fd[k],
+      }), {});
+    const isNative = annotation.sourceType === ANNOTATION_SOURCE_TYPES.NATIVE;
+    const url = getAnnotationJsonUrl(annotation.value, sliceFormData, 
isNative);
+    const queryRequest = $.ajax({
+      url,
+      dataType: 'json',
+      timeout: timeout * 1000,
+    });
+    dispatch(annotationQueryStarted(annotation, queryRequest, sliceKey));
+    return queryRequest
+      .then(queryResponse => dispatch(annotationQuerySuccess(annotation, 
queryResponse, sliceKey)))
+      .catch((err) => {
+        if (err.statusText === 'timeout') {
+          dispatch(annotationQueryFailed(annotation, { error: 'Query Timeout' 
}, sliceKey));
+        } else if ((err.responseJSON.error || '').toLowerCase().startsWith('no 
data')) {
+          dispatch(annotationQuerySuccess(annotation, err, sliceKey));
+        } else if (err.statusText !== 'abort') {
+          dispatch(annotationQueryFailed(annotation, err.responseJSON, 
sliceKey));
+        }
+      });
+  };
+}
+
 export const TRIGGER_QUERY = 'TRIGGER_QUERY';
 export function triggerQuery(value = true, key) {
   return { type: TRIGGER_QUERY, value, key };
@@ -60,32 +111,23 @@ export function runQuery(formData, force = false, timeout 
= 60, key) {
       url,
       dataType: 'json',
       timeout: timeout * 1000,
-      success: (queryResponse =>
-        dispatch(chartUpdateSucceeded(queryResponse, key))
-      ),
-      error: ((xhr) => {
-        if (xhr.statusText === 'timeout') {
-          dispatch(chartUpdateTimeout(xhr.statusText, timeout, key));
-        } else {
-          let error = '';
-          if (!xhr.responseText) {
-            const status = xhr.status;
-            if (status === 0) {
-              // This may happen when the worker in gunicorn times out
-              error += (
-                t('The server could not be reached. You may want to ' +
-                  'verify your connection and try again.'));
-            } else {
-              error += (t('An unknown error occurred. (Status: %s )', status));
-            }
-          }
-          const errorResponse = Object.assign({}, xhr.responseJSON, error);
-          dispatch(chartUpdateFailed(errorResponse, key));
-        }
-      }),
     });
 
-    dispatch(chartUpdateStarted(queryRequest, key));
-    dispatch(triggerQuery(false, key));
+    const queryPromise = 
Promise.resolve(dispatch(chartUpdateStarted(queryRequest, key)))
+      .then(() => queryRequest)
+      .then(queryResponse => dispatch(chartUpdateSucceeded(queryResponse, 
key)))
+      .catch((err) => {
+        if (err.statusText === 'timeout') {
+          dispatch(chartUpdateTimeout(err.statusText, timeout, key));
+        } else if (err.statusText !== 'abort') {
+          dispatch(chartUpdateFailed(err.responseJSON, key));
+        }
+      });
+    const annotationLayers = formData.annotation_layers || [];
+    return Promise.all([
+      queryPromise,
+      dispatch(triggerQuery(false, key)),
+      ...annotationLayers.map(x => dispatch(runAnnotationQuery(x, timeout, 
formData, key))),
+    ]);
   };
 }
diff --git a/superset/assets/javascripts/chart/chartReducer.js 
b/superset/assets/javascripts/chart/chartReducer.js
index ade8c5bf68..3cc9e5e6df 100644
--- a/superset/assets/javascripts/chart/chartReducer.js
+++ b/superset/assets/javascripts/chart/chartReducer.js
@@ -65,12 +65,12 @@ export default function chartReducer(charts = {}, action) {
       return { ...state,
         chartStatus: 'failed',
         chartAlert: (
-        `<strong>${t('Query timeout')}</strong> - ` +
-        t(`visualization queries are set to timeout at ${action.timeout} 
seconds. `) +
-        t('Perhaps your data has grown, your database is under unusual load, ' 
+
-          'or you are simply querying a data source that is too large ' +
-          'to be processed within the timeout range. ' +
-          'If that is the case, we recommend that you summarize your data 
further.')),
+            `<strong>${t('Query timeout')}</strong> - ` +
+            t(`visualization queries are set to timeout at ${action.timeout} 
seconds. `) +
+            t('Perhaps your data has grown, your database is under unusual 
load, ' +
+                'or you are simply querying a data source that is too large ' +
+                'to be processed within the timeout range. ' +
+                'If that is the case, we recommend that you summarize your 
data further.')),
       };
     },
     [actions.CHART_UPDATE_FAILED](state) {
@@ -87,6 +87,53 @@ export default function chartReducer(charts = {}, action) {
     [actions.RENDER_TRIGGERED](state) {
       return { ...state, lastRendered: action.value };
     },
+    [actions.ANNOTATION_QUERY_STARTED](state) {
+      if (state.annotationQuery &&
+        state.annotationQuery[action.annotation.name]) {
+        state.annotationQuery[action.annotation.name].abort();
+      }
+      const annotationQuery = {
+        ...state.annotationQuery,
+        [action.annotation.name]: action.queryRequest,
+      };
+      return {
+        ...state,
+        annotationQuery,
+      };
+    },
+    [actions.ANNOTATION_QUERY_SUCCESS](state) {
+      const annotationData = {
+        ...state.annotationData,
+        [action.annotation.name]: action.queryResponse.data,
+      };
+      const annotationError = { ...state.annotationError };
+      delete annotationError[action.annotation.name];
+      const annotationQuery = { ...state.annotationQuery };
+      delete annotationQuery[action.annotation.name];
+      return {
+        ...state,
+        annotationData,
+        annotationError,
+        annotationQuery,
+      };
+    },
+    [actions.ANNOTATION_QUERY_FAILED](state) {
+      const annotationData = { ...state.annotationData };
+      delete annotationData[action.annotation.name];
+      const annotationError = {
+        ...state.annotationError,
+        [action.annotation.name]: action.queryResponse ?
+          action.queryResponse.error : t('Network error.'),
+      };
+      const annotationQuery = { ...state.annotationQuery };
+      delete annotationQuery[action.annotation.name];
+      return {
+        ...state,
+        annotationData,
+        annotationError,
+        annotationQuery,
+      };
+    },
   };
 
   /* eslint-disable no-param-reassign */
diff --git a/superset/assets/javascripts/dashboard/components/GridCell.jsx 
b/superset/assets/javascripts/dashboard/components/GridCell.jsx
index 854aea01fe..4f7213d3b0 100644
--- a/superset/assets/javascripts/dashboard/components/GridCell.jsx
+++ b/superset/assets/javascripts/dashboard/components/GridCell.jsx
@@ -31,6 +31,7 @@ const propTypes = {
   clearFilter: PropTypes.func,
   removeFilter: PropTypes.func,
   editMode: PropTypes.bool,
+  annotationQuery: PropTypes.object,
 };
 
 const defaultProps = {
@@ -84,7 +85,7 @@ class GridCell extends React.PureComponent {
     const {
       exploreChartUrl, exportCSVUrl, isExpanded, isLoading, isCached, 
cachedDttm,
       removeSlice, updateSliceName, toggleExpandSlice, forceRefresh,
-      chartKey, slice, datasource, formData, timeout,
+      chartKey, slice, datasource, formData, timeout, annotationQuery,
     } = this.props;
     return (
       <div
@@ -104,6 +105,7 @@ class GridCell extends React.PureComponent {
             toggleExpandSlice={toggleExpandSlice}
             forceRefresh={forceRefresh}
             editMode={this.props.editMode}
+            annotationQuery={annotationQuery}
           />
         </div>
         <div
diff --git a/superset/assets/javascripts/dashboard/components/GridLayout.jsx 
b/superset/assets/javascripts/dashboard/components/GridLayout.jsx
index dad66e1adb..b5dd49a4fc 100644
--- a/superset/assets/javascripts/dashboard/components/GridLayout.jsx
+++ b/superset/assets/javascripts/dashboard/components/GridLayout.jsx
@@ -164,6 +164,8 @@ class GridLayout extends React.Component {
             clearFilter={this.props.clearFilter}
             removeFilter={this.props.removeFilter}
             editMode={this.props.editMode}
+            annotationQuery={currentChart.annotationQuery}
+            annotationError={currentChart.annotationError}
           />
         </div>);
     });
diff --git a/superset/assets/javascripts/dashboard/components/SliceHeader.jsx 
b/superset/assets/javascripts/dashboard/components/SliceHeader.jsx
index 36107fedf1..1f4b475d88 100644
--- a/superset/assets/javascripts/dashboard/components/SliceHeader.jsx
+++ b/superset/assets/javascripts/dashboard/components/SliceHeader.jsx
@@ -19,6 +19,8 @@ const propTypes = {
   toggleExpandSlice: PropTypes.func,
   forceRefresh: PropTypes.func,
   editMode: PropTypes.bool,
+  annotationQuery: PropTypes.object,
+  annotationError: PropTypes.object,
 };
 
 const defaultProps = {
@@ -50,6 +52,8 @@ class SliceHeader extends React.PureComponent {
     const refreshTooltip = isCached ?
       t('Served from data cached %s . Click to force refresh.', cachedWhen) :
       t('Force refresh data');
+    const annoationsLoading = t('Annotation layers are still loading.');
+    const annoationsError = t('One ore more annotation layers failed 
loading.');
 
     return (
       <div className="row chart-header">
@@ -61,6 +65,24 @@ class SliceHeader extends React.PureComponent {
               onSaveTitle={this.onSaveTitle}
               noPermitTooltip={'You don\'t have the rights to alter this 
dashboard.'}
             />
+            {!!Object.values(this.props.annotationQuery || {}).length &&
+              <TooltipWrapper
+                label="annotations-loading"
+                placement="top"
+                tooltip={annoationsLoading}
+              >
+                <i className="fa fa-refresh warning" />
+              </TooltipWrapper>
+            }
+            {!!Object.values(this.props.annotationError || {}).length &&
+              <TooltipWrapper
+                label="annoation-errors"
+                placement="top"
+                tooltip={annoationsError}
+              >
+                <i className="fa fa-exclamation-circle danger" />
+              </TooltipWrapper>
+            }
           </div>
           <div className="chart-controls">
             <div id={'controls_' + slice.slice_id} className="pull-right">
diff --git 
a/superset/assets/javascripts/explore/components/controls/AnnotationLayer.jsx 
b/superset/assets/javascripts/explore/components/controls/AnnotationLayer.jsx
new file mode 100644
index 0000000000..aa34fb04f7
--- /dev/null
+++ 
b/superset/assets/javascripts/explore/components/controls/AnnotationLayer.jsx
@@ -0,0 +1,602 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { CompactPicker } from 'react-color';
+import { Button } from 'react-bootstrap';
+
+import $ from 'jquery';
+import mathjs from 'mathjs';
+
+import SelectControl from './SelectControl';
+import TextControl from './TextControl';
+import CheckboxControl from './CheckboxControl';
+
+import AnnotationTypes, {
+  DEFAULT_ANNOTATION_TYPE,
+  ANNOTATION_SOURCE_TYPES,
+  getAnnotationSourceTypeLabels,
+  getAnnotationTypeLabel,
+  getSupportedSourceTypes,
+  getSupportedAnnotationTypes,
+  requiresQuery,
+} from '../../../modules/AnnotationTypes';
+
+import { ALL_COLOR_SCHEMES } from '../../../modules/colors';
+import PopoverSection from '../../../components/PopoverSection';
+import ControlHeader from '../ControlHeader';
+import { nonEmpty } from '../../validators';
+import vizTypes from '../../stores/visTypes';
+
+const AUTOMATIC_COLOR = '';
+
+const propTypes = {
+  name: PropTypes.string,
+  annotationType: PropTypes.string,
+  sourceType: PropTypes.string,
+  color: PropTypes.string,
+  opacity: PropTypes.string,
+  style: PropTypes.string,
+  width: PropTypes.number,
+  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+  overrides: PropTypes.object,
+  show: PropTypes.bool,
+  titleColumn: PropTypes.string,
+  descriptionColumns: PropTypes.arrayOf(PropTypes.string),
+  timeColumn: PropTypes.string,
+  intervalEndColumn: PropTypes.string,
+  vizType: PropTypes.string,
+
+  error: PropTypes.string,
+  colorScheme: PropTypes.string,
+
+  addAnnotationLayer: PropTypes.func,
+  removeAnnotationLayer: PropTypes.func,
+  close: PropTypes.func,
+};
+
+const defaultProps = {
+  name: '',
+  annotationType: DEFAULT_ANNOTATION_TYPE,
+  sourceType: '',
+  color: AUTOMATIC_COLOR,
+  opacity: '',
+  style: 'solid',
+  width: 1,
+  overrides: {},
+  colorScheme: 'd3Category10',
+  show: true,
+  titleColumn: '',
+  descriptionColumns: [],
+  timeColumn: '',
+  intervalEndColumn: '',
+
+  addAnnotationLayer: () => {},
+  removeAnnotationLayer: () => {},
+  close: () => {},
+};
+
+export default class AnnotationLayer extends React.PureComponent {
+  constructor(props) {
+    super(props);
+    const { name, annotationType, sourceType,
+      color, opacity, style, width, value,
+      overrides, show, titleColumn, descriptionColumns,
+      timeColumn, intervalEndColumn } = props;
+    this.state = {
+      // base
+      name,
+      oldName: !this.props.name ? null : name,
+      annotationType,
+      sourceType,
+      value,
+      overrides,
+      show,
+      // slice
+      titleColumn,
+      descriptionColumns,
+      timeColumn,
+      intervalEndColumn,
+      // display
+      color: color || AUTOMATIC_COLOR,
+      opacity,
+      style,
+      width,
+      // refData
+      isNew: !this.props.name,
+      isLoadingOptions: true,
+      valueOptions: [],
+    };
+    this.submitAnnotation = this.submitAnnotation.bind(this);
+    this.deleteAnnotation = this.deleteAnnotation.bind(this);
+    this.applyAnnotation = this.applyAnnotation.bind(this);
+    this.fetchOptions = this.fetchOptions.bind(this);
+    this.handleAnnotationType = this.handleAnnotationType.bind(this);
+    this.handleAnnotationSourceType =
+        this.handleAnnotationSourceType.bind(this);
+    this.handleValue = this.handleValue.bind(this);
+    this.isValidForm = this.isValidForm.bind(this);
+  }
+
+  componentDidMount() {
+    const { annotationType, sourceType, isLoadingOptions } = this.state;
+    this.fetchOptions(annotationType, sourceType, isLoadingOptions);
+  }
+
+  componentDidUpdate(prevProps, prevState) {
+    if (prevState.sourceType !== this.state.sourceType) {
+      this.fetchOptions(this.state.annotationType, this.state.sourceType, 
true);
+    }
+  }
+
+  isValidFormula(value, annotationType) {
+    if (annotationType === AnnotationTypes.FORMULA) {
+      try {
+        mathjs.parse(value).compile().eval({ x: 0 });
+      } catch (err) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  isValidForm() {
+    const {
+      name, annotationType, sourceType,
+      value, timeColumn, intervalEndColumn,
+    } = this.state;
+    const errors = [nonEmpty(name), nonEmpty(annotationType), nonEmpty(value)];
+    if (sourceType !== ANNOTATION_SOURCE_TYPES.NATIVE) {
+      if (annotationType === AnnotationTypes.EVENT) {
+        errors.push(nonEmpty(timeColumn));
+      }
+      if (annotationType === AnnotationTypes.INTERVAL) {
+        errors.push(nonEmpty(timeColumn));
+        errors.push(nonEmpty(intervalEndColumn));
+      }
+    }
+    errors.push(this.isValidFormula(value, annotationType));
+    return !errors.filter(x => x).length;
+  }
+
+
+  handleAnnotationType(annotationType) {
+    this.setState({
+      annotationType,
+      sourceType: null,
+      validationErrors: {},
+      value: null,
+    });
+  }
+
+  handleAnnotationSourceType(sourceType) {
+    this.setState({
+      sourceType,
+      isLoadingOptions: true,
+      validationErrors: {},
+      value: null,
+    });
+  }
+
+  handleValue(value) {
+    this.setState({
+      value,
+      descriptionColumns: null,
+      intervalEndColumn: null,
+      timeColumn: null,
+      titleColumn: null,
+      overrides: { since: null, until: null },
+    });
+  }
+
+  fetchOptions(annotationType, sourceType, isLoadingOptions) {
+    if (isLoadingOptions === true) {
+      if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) {
+        $.ajax({
+          type: 'GET',
+          url: '/annotationlayermodelview/api/read?',
+        }).then((data) => {
+          const layers = data ? data.result.map(layer => ({
+            value: layer.id,
+            label: layer.name,
+          })) : [];
+          this.setState({
+            isLoadingOptions: false,
+            valueOptions: layers,
+          });
+        });
+      } else if (requiresQuery(sourceType)) {
+        $.ajax({
+          type: 'GET',
+          url: '/superset/user_slices',
+        }).then(data =>
+          this.setState({
+            isLoadingOptions: false,
+            valueOptions: data.filter(
+                x => getSupportedSourceTypes(annotationType)
+                .find(v => v === x.viz_type))
+                .map(x => ({ value: x.id, label: x.title, slice: x }),
+              ),
+          }),
+        );
+      } else {
+        this.setState({
+          isLoadingOptions: false,
+          valueOptions: [],
+        });
+      }
+    }
+  }
+
+  deleteAnnotation() {
+    this.props.close();
+    if (!this.state.isNew) {
+      this.props.removeAnnotationLayer(this.state);
+    }
+  }
+
+  applyAnnotation() {
+    if (this.state.name.length) {
+      const annotation = { ...this.state };
+      annotation.color = annotation.color === AUTOMATIC_COLOR ? null : 
annotation.color;
+      delete annotation.isNew;
+      delete annotation.valueOptions;
+      delete annotation.isLoadingOptions;
+      this.props.addAnnotationLayer(annotation);
+      this.setState({ isNew: false, oldName: this.state.name });
+    }
+  }
+
+  submitAnnotation() {
+    this.applyAnnotation();
+    this.props.close();
+  }
+
+  renderValueConfiguration() {
+    const { annotationType, sourceType, value,
+      valueOptions, isLoadingOptions } = this.state;
+    let label = '';
+    let description = '';
+    if (requiresQuery(sourceType)) {
+      if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) {
+        label = 'Annotation Layer';
+        description = 'Select the Annotation Layer you would like to use.';
+      } else {
+        label = 'Slice';
+        description = `Use a pre defined Superset Slice as a source for 
annotations and overlays. 
+        'your Slice must be one of these visualization types:
+        '[${getSupportedSourceTypes(sourceType)
+            .map(x => vizTypes[x].label).join(', ')}]'`;
+      }
+    } else if (annotationType === AnnotationTypes.FORMULA) {
+      label = 'Formula';
+      description = `Expects a formula with depending time parameter 'x'
+        in milliseconds since epoch. mathjs is used to evaluate the formulas.
+        Example: '2x+5'`;
+    }
+    if (requiresQuery(sourceType)) {
+      return (
+        <SelectControl
+          name="annotation-layer-value"
+          showHeader
+          hovered
+          description={description}
+          label={label}
+          placeholder=""
+          options={valueOptions}
+          isLoading={isLoadingOptions}
+          value={value}
+          onChange={this.handleValue}
+          validationErrors={!value ? ['Mandatory'] : []}
+        />
+      );
+    } if (annotationType === AnnotationTypes.FORMULA) {
+      return (
+        <TextControl
+          name="annotation-layer-value"
+          hovered
+          showHeader
+          description={description}
+          label={label}
+          placeholder=""
+          value={value}
+          onChange={this.handleValue}
+          validationErrors={this.isValidFormula(value, annotationType) ? ['Bad 
formula.'] : []}
+        />
+      );
+    }
+    return '';
+  }
+
+  renderSliceConfiguration() {
+    const { annotationType, sourceType, value, valueOptions, overrides, 
titleColumn,
+      timeColumn, intervalEndColumn, descriptionColumns } = this.state;
+    const slice = (valueOptions.find(x => x.value === value) || {}).slice;
+    if (sourceType !== ANNOTATION_SOURCE_TYPES.NATIVE && slice) {
+      const columns = (slice.data.groupby || []).concat(
+        (slice.data.all_columns || [])).map(x => ({ value: x, label: x }));
+      const timeColumnOptions = slice.data.include_time ?
+        [{ value: '__timestamp', label: '__timestamp' }].concat(columns) : 
columns;
+      return (
+        <div style={{ marginRight: '2rem' }}>
+          <PopoverSection
+            isSelected
+            onSelect={() => {
+            }}
+            title="Annotation Slice Configuration"
+            info={
+              `This section allows you to configure how to use the slice
+               to generate annotations.`
+            }
+          >
+            {
+              (
+                annotationType === AnnotationTypes.EVENT ||
+                annotationType === AnnotationTypes.INTERVAL
+              ) &&
+              <SelectControl
+                hovered
+                name="annotation-layer-time-column"
+                label={
+                  annotationType === AnnotationTypes.INTERVAL ?
+                    'Interval Start column' : 'Event Time Column'
+                }
+                description={'This column must contain date/time information.'}
+                validationErrors={!timeColumn ? ['Mandatory'] : []}
+                clearable={false}
+                options={timeColumnOptions}
+                value={timeColumn}
+                onChange={v => this.setState({ timeColumn: v })}
+              />
+            }
+            {
+              annotationType === AnnotationTypes.INTERVAL &&
+              <SelectControl
+                hovered
+                name="annotation-layer-intervalEnd"
+                label="Interval End column"
+                description={'This column must contain date/time information.'}
+                validationErrors={!intervalEndColumn ? ['Mandatory'] : []}
+                options={columns}
+                value={intervalEndColumn}
+                onChange={v => this.setState({ intervalEndColumn: v })}
+              />
+            }
+            <SelectControl
+              hovered
+              name="annotation-layer-title"
+              label="Title Column"
+              description={'Pick a title for you annotation.'}
+              options={
+                [{ value: '', label: 'None' }].concat(columns)
+              }
+              value={titleColumn}
+              onChange={v => this.setState({ titleColumn: v })}
+            />
+            {
+              annotationType !== AnnotationTypes.TIME_SERIES &&
+              <SelectControl
+                hovered
+                name="annotation-layer-title"
+                label="Description Columns"
+                description={`Pick one or more columns that should be shown in 
the
+                  annotation. If you don't select a column all of them will be 
shown.`}
+                multi
+                options={
+                  columns
+                }
+                value={descriptionColumns}
+                onChange={v => this.setState({ descriptionColumns: v })}
+              />
+            }
+            <div style={{ marginTop: '1rem' }}>
+              <CheckboxControl
+                hovered
+                name="annotation-override-since"
+                label="Override 'Since'"
+                description={`This controls whether the "Since" field from the 
current
+                  view should be passed down to the slice containing the 
annotation data.`}
+                value={!!Object.keys(overrides).find(x => x === 'since')}
+                onChange={(v) => {
+                  delete overrides.since;
+                  if (v) {
+                    this.setState({ overrides: { ...overrides, since: null } 
});
+                  } else {
+                    this.setState({ overrides: { ...overrides } });
+                  }
+                }}
+              />
+              <CheckboxControl
+                hovered
+                name="annotation-override-until"
+                label="Override 'Until'"
+                description={`This controls whether the "Until" field from the 
current
+                  view should be passed down to the slice containing the 
annotation data.`}
+                value={!!Object.keys(overrides).find(x => x === 'until')}
+                onChange={(v) => {
+                  delete overrides.until;
+                  if (v) {
+                    this.setState({ overrides: { ...overrides, until: null } 
});
+                  } else {
+                    this.setState({ overrides: { ...overrides } });
+                  }
+                }}
+              />
+              <TextControl
+                hovered
+                name="annotation-layer-timeshift"
+                label="Time Shift"
+                description={`Time delta in natural language
+                  (example:  24 hours, 7 days, 56 weeks, 365 days)`}
+                placeholder=""
+                value={overrides.time_shift}
+                onChange={v => this.setState({ overrides: { ...overrides, 
time_shift: v } })}
+              />
+            </div>
+          </PopoverSection>
+        </div>
+      );
+    }
+    return ('');
+  }
+
+  renderDisplayConfiguration() {
+    const { color, opacity, style, width } = this.state;
+    const colorScheme = [...ALL_COLOR_SCHEMES[this.props.colorScheme]];
+    if (color && color !== AUTOMATIC_COLOR &&
+      !colorScheme.find(x => x.toLowerCase() === color.toLowerCase())) {
+      colorScheme.push(color);
+    }
+    return (
+      <PopoverSection
+        isSelected
+        onSelect={() => {}}
+        title="Display configuration"
+        info="Configure your how you overlay is displayed here."
+      >
+        <SelectControl
+          name="annotation-layer-stroke"
+          label="Style"
+            // see '../../../../visualizations/nvd3_vis.css'
+          options={[
+              { value: 'solid', label: 'Solid' },
+              { value: 'dashed', label: 'Dashed' },
+              { value: 'longDashed', label: 'Long Dashed' },
+              { value: 'dotted', label: 'Dotted' },
+          ]}
+          value={style}
+          onChange={v => this.setState({ style: v })}
+        />
+        <SelectControl
+          name="annotation-layer-opacity"
+          label="Opacity"
+            // see '../../../../visualizations/nvd3_vis.css'
+          options={[
+              { value: '', label: 'Solid' },
+              { value: 'opacityLow', label: '0.2' },
+              { value: 'opacityMedium', label: '0.5' },
+              { value: 'opacityHigh', label: '0.8' },
+          ]}
+          value={opacity}
+          onChange={v => this.setState({ opacity: v })}
+        />
+        <div>
+          <ControlHeader label="Color" />
+          <div style={{ display: 'flex', flexDirection: 'column' }}>
+            <CompactPicker
+              color={color}
+              colors={colorScheme}
+              onChangeComplete={v => this.setState({ color: v.hex })}
+            />
+            <Button
+              style={{ marginTop: '0.5rem', marginBottom: '0.5rem' }}
+              bsStyle={color === AUTOMATIC_COLOR ? 'success' : 'default'}
+              bsSize="xsmall"
+              onClick={() => this.setState({ color: AUTOMATIC_COLOR })}
+            >
+              Automatic Color
+            </Button>
+          </div>
+        </div>
+        <TextControl
+          name="annotation-layer-stroke-width"
+          label="Line Width"
+          isInt
+          value={width}
+          onChange={v => this.setState({ width: v })}
+        />
+      </PopoverSection>
+    );
+  }
+
+  render() {
+    const { isNew, name, annotationType,
+      sourceType, show } = this.state;
+    const isValid = this.isValidForm();
+    return (
+      <div>
+        {
+          this.props.error &&
+          <span style={{ color: 'red' }}>
+            ERROR: {this.props.error}
+          </span>
+        }
+        <div style={{ display: 'flex', flexDirection: 'row' }}>
+          <div style={{ marginRight: '2rem' }}>
+            <PopoverSection
+              isSelected
+              onSelect={() => {}}
+              title="Layer Configuration"
+              info="Configure the basics of your Annotation Layer."
+            >
+              <TextControl
+                name="annotation-layer-name"
+                label="Name"
+                placeholder=""
+                value={name}
+                onChange={v => this.setState({ name: v })}
+                validationErrors={!name ? ['Mandatory'] : []}
+              />
+              <CheckboxControl
+                name="annotation-layer-hide"
+                label="Hide Layer"
+                value={!show}
+                onChange={v => this.setState({ show: !v })}
+              />
+              <SelectControl
+                hovered
+                description="Choose the Annotation Layer Type"
+                label="Annotation Layer Type"
+                name="annotation-layer-type"
+                options={getSupportedAnnotationTypes(this.props.vizType).map(
+                    x => ({ value: x, label: getAnnotationTypeLabel(x) }))}
+                value={annotationType}
+                onChange={this.handleAnnotationType}
+              />
+              {!!getSupportedSourceTypes(annotationType).length &&
+                <SelectControl
+                  hovered
+                  description="Choose the source of your annotations"
+                  label="Annotation Source"
+                  name="annotation-source-type"
+                  options={getSupportedSourceTypes(annotationType).map(
+                        x => ({ value: x, label: 
getAnnotationSourceTypeLabels(x) }))}
+                  value={sourceType}
+                  onChange={this.handleAnnotationSourceType}
+                />
+              }
+              { this.renderValueConfiguration() }
+            </PopoverSection>
+          </div>
+          { this.renderSliceConfiguration() }
+          { this.renderDisplayConfiguration() }
+        </div>
+        <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+          <Button
+            bsSize="sm"
+            onClick={this.deleteAnnotation}
+          >
+            { !isNew ? 'Remove' : 'Cancel' }
+          </Button>
+          <div>
+            <Button
+              bsSize="sm"
+              disabled={!isValid}
+              onClick={this.applyAnnotation}
+            >
+              Apply
+            </Button>
+
+            <Button
+              bsSize="sm"
+              disabled={!isValid}
+              onClick={this.submitAnnotation}
+            >
+              OK
+            </Button>
+          </div>
+        </div>
+      </div>
+    );
+  }
+}
+AnnotationLayer.propTypes = propTypes;
+AnnotationLayer.defaultProps = defaultProps;
diff --git 
a/superset/assets/javascripts/explore/components/controls/AnnotationLayerControl.jsx
 
b/superset/assets/javascripts/explore/components/controls/AnnotationLayerControl.jsx
new file mode 100644
index 0000000000..3e4cd24e31
--- /dev/null
+++ 
b/superset/assets/javascripts/explore/components/controls/AnnotationLayerControl.jsx
@@ -0,0 +1,177 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { OverlayTrigger, Popover, ListGroup, ListGroupItem } from 
'react-bootstrap';
+import { connect } from 'react-redux';
+import { getChartKey } from '../../exploreUtils';
+import { runAnnotationQuery } from '../../../chart/chartAction';
+import InfoTooltipWithTrigger from 
'../../../components/InfoTooltipWithTrigger';
+
+
+import AnnotationLayer from './AnnotationLayer';
+import { t } from '../../../locales';
+
+
+const propTypes = {
+  colorScheme: PropTypes.string.isRequired,
+  annotationError: PropTypes.object,
+  annotationQuery: PropTypes.object,
+  vizType: PropTypes.string,
+
+  validationErrors: PropTypes.array,
+  name: PropTypes.string.isRequired,
+  actions: PropTypes.object,
+  value: PropTypes.arrayOf(PropTypes.object),
+  onChange: PropTypes.func,
+  refreshAnnotationData: PropTypes.func,
+};
+
+const defaultProps = {
+  vizType: '',
+  value: [],
+  annotationError: {},
+  annotationQuery: {},
+  onChange: () => {},
+};
+
+class AnnotationLayerControl extends React.PureComponent {
+  constructor(props) {
+    super(props);
+    this.addAnnotationLayer = this.addAnnotationLayer.bind(this);
+    this.removeAnnotationLayer = this.removeAnnotationLayer.bind(this);
+  }
+
+  componentWillReceiveProps(nextProps) {
+    const { name, annotationError, validationErrors, value } = nextProps;
+    if (Object.keys(annotationError).length && !validationErrors.length) {
+      this.props.actions.setControlValue(name, value, 
Object.keys(annotationError));
+    }
+    if (!Object.keys(annotationError).length && validationErrors.length) {
+      this.props.actions.setControlValue(name, value, []);
+    }
+  }
+
+  addAnnotationLayer(annotationLayer) {
+    const annotation = annotationLayer;
+    let annotations = this.props.value.slice();
+    const i = annotations.findIndex(x => x.name === (annotation.oldName || 
annotation.name));
+    delete annotation.oldName;
+    if (i > -1) {
+      annotations[i] = annotation;
+    } else {
+      annotations = annotations.concat(annotation);
+    }
+    this.props.refreshAnnotationData(annotation);
+    this.props.onChange(annotations);
+  }
+
+  removeAnnotationLayer(annotation) {
+    const annotations = this.props.value.slice()
+      .filter(x => x.name !== annotation.oldName);
+    this.props.onChange(annotations);
+  }
+
+  renderPopover(parent, annotation, error) {
+    const id = !annotation ? '_new' : annotation.name;
+    return (
+      <Popover
+        style={{ maxWidth: 'none' }}
+        title={annotation ? 'Edit Annotation Layer' : 'Add Annotation Layer'}
+        id={`annotation-pop-${id}`}
+      >
+        <AnnotationLayer
+          {...annotation}
+          error={error}
+          colorScheme={this.props.colorScheme}
+          vizType={this.props.vizType}
+          addAnnotationLayer={this.addAnnotationLayer}
+          removeAnnotationLayer={this.removeAnnotationLayer}
+          close={() => this.refs[parent].hide()}
+        />
+      </Popover>
+    );
+  }
+
+  renderInfo(anno) {
+    const { annotationError, annotationQuery } = this.props;
+    if (annotationQuery[anno.name]) {
+      return (
+        <i className="fa fa-refresh" style={{ color: 'orange' }} aria-hidden />
+      );
+    }
+    if (annotationError[anno.name]) {
+      return (
+        <InfoTooltipWithTrigger
+          label="validation-errors"
+          bsStyle="danger"
+          tooltip={annotationError[anno.name]}
+        />
+      );
+    }
+    if (!anno.show) {
+      return <span style={{ color: 'red' }}> Hidden </span>;
+    }
+    return '';
+  }
+
+  render() {
+    const annotations = this.props.value.map((anno, i) => (
+      <OverlayTrigger
+        key={i}
+        trigger="click"
+        rootClose
+        ref={`overlay-${i}`}
+        placement="right"
+        overlay={this.renderPopover(`overlay-${i}`, anno,
+          this.props.annotationError[anno.name])}
+      >
+        <ListGroupItem>
+          <span>{anno.name}</span>
+          <span style={{ float: 'right' }}>
+            {this.renderInfo(anno)}
+          </span>
+        </ListGroupItem>
+      </OverlayTrigger>
+    ));
+    return (
+      <div>
+        <ListGroup>
+          {annotations}
+          <OverlayTrigger
+            trigger="click"
+            rootClose
+            ref="overlay-new"
+            placement="right"
+            overlay={this.renderPopover('overlay-new')}
+          >
+            <ListGroupItem>
+              <i className="fa fa-plus" /> &nbsp; {t('Add Annotation Layer')}
+            </ListGroupItem>
+          </OverlayTrigger>
+        </ListGroup>
+      </div>
+    );
+  }
+}
+
+AnnotationLayerControl.propTypes = propTypes;
+AnnotationLayerControl.defaultProps = defaultProps;
+
+// Tried to hook this up through stores/control.jsx instead of using redux
+// directly, could not figure out how to get access to the color_scheme
+function mapStateToProps({ charts, explore }) {
+  const chartKey = getChartKey(explore);
+  return {
+    colorScheme: (explore.controls || {}).color_scheme.value,
+    annotationError: charts[chartKey].annotationError,
+    annotationQuery: charts[chartKey].annotationQuery,
+    vizType: explore.controls.viz_type.value,
+  };
+}
+
+function mapDispatchToProps(dispatch) {
+  return {
+    refreshAnnotationData: annotationLayer => 
dispatch(runAnnotationQuery(annotationLayer)),
+  };
+}
+
+export default connect(mapStateToProps, 
mapDispatchToProps)(AnnotationLayerControl);
diff --git a/superset/assets/javascripts/explore/components/controls/index.js 
b/superset/assets/javascripts/explore/components/controls/index.js
index 94b8c66ef6..35aaeeff2b 100644
--- a/superset/assets/javascripts/explore/components/controls/index.js
+++ b/superset/assets/javascripts/explore/components/controls/index.js
@@ -1,3 +1,4 @@
+import AnnotationLayerControl from './AnnotationLayerControl';
 import BoundsControl from './BoundsControl';
 import CheckboxControl from './CheckboxControl';
 import CollectionControl from './CollectionControl';
@@ -18,6 +19,7 @@ import ViewportControl from './ViewportControl';
 import VizTypeControl from './VizTypeControl';
 
 const controlMap = {
+  AnnotationLayerControl,
   BoundsControl,
   CheckboxControl,
   CollectionControl,
diff --git a/superset/assets/javascripts/explore/exploreUtils.js 
b/superset/assets/javascripts/explore/exploreUtils.js
index 2356da5eb6..8a01745d9a 100644
--- a/superset/assets/javascripts/explore/exploreUtils.js
+++ b/superset/assets/javascripts/explore/exploreUtils.js
@@ -1,13 +1,30 @@
 /* eslint camelcase: 0 */
 import URI from 'urijs';
 
+export function getChartKey(explore) {
+  const slice = explore.slice;
+  return slice ? ('slice_' + slice.slice_id) : 'slice';
+}
+
+export function getAnnotationJsonUrl(slice_id, form_data, isNative) {
+  if (slice_id === null || slice_id === undefined) {
+    return null;
+  }
+  const uri = URI(window.location.search);
+  const endpoint = isNative ? 'annotation_json' : 'slice_json';
+  return uri.pathname(`/superset/${endpoint}/${slice_id}`)
+    .search({
+      form_data: JSON.stringify(form_data,
+        (key, value) => value === null ? undefined : value),
+    }).toString();
+}
+
 export function getExploreUrl(form_data, endpointType = 'base', force = false,
   curUrl = null, requestParams = {}) {
   if (!form_data.datasource) {
     return null;
   }
 
-
   // The search params from the window.location are carried through,
   // but can be specified with curUrl (used for unit tests to spoof
   // the window.location).
diff --git a/superset/assets/javascripts/explore/index.jsx 
b/superset/assets/javascripts/explore/index.jsx
index 7e2ed35926..d66ad52e79 100644
--- a/superset/assets/javascripts/explore/index.jsx
+++ b/superset/assets/javascripts/explore/index.jsx
@@ -7,6 +7,7 @@ import thunk from 'redux-thunk';
 
 import { now } from '../modules/dates';
 import { initEnhancer } from '../reduxUtils';
+import { getChartKey } from './exploreUtils';
 import AlertsWrapper from '../components/AlertsWrapper';
 import { getControlsState, getFormDataFromControls } from './stores/store';
 import { initJQueryAjax } from '../modules/utils';
@@ -41,7 +42,7 @@ const sliceFormData = slice ?
   getFormDataFromControls(getControlsState(bootstrapData, slice.form_data))
   :
   null;
-const chartKey = slice ? ('slice_' + slice.slice_id) : 'slice';
+const chartKey = getChartKey(bootstrappedState);
 const initState = {
   charts: {
     [chartKey]: {
diff --git a/superset/assets/javascripts/explore/main.css 
b/superset/assets/javascripts/explore/main.css
index a6afe5eba9..434e6f8aca 100644
--- a/superset/assets/javascripts/explore/main.css
+++ b/superset/assets/javascripts/explore/main.css
@@ -121,3 +121,4 @@
   padding: 0;
   background-color: transparent;
 }
+
diff --git a/superset/assets/javascripts/explore/stores/controls.jsx 
b/superset/assets/javascripts/explore/stores/controls.jsx
index b18039f232..eb4efba5ad 100644
--- a/superset/assets/javascripts/explore/stores/controls.jsx
+++ b/superset/assets/javascripts/explore/stores/controls.jsx
@@ -135,7 +135,6 @@ export const controls = {
       choices: (state.datasource) ? state.datasource.order_by_choices : [],
     }),
   },
-
   color_picker: {
     label: t('Fixed Color'),
     description: t('Use this to define a static color for all circles'),
@@ -144,23 +143,6 @@ export const controls = {
     renderTrigger: true,
   },
 
-  annotation_layers: {
-    type: 'SelectAsyncControl',
-    multi: true,
-    label: t('Annotation Layers'),
-    default: [],
-    description: t('Annotation layers to overlay on the visualization'),
-    dataEndpoint: '/annotationlayermodelview/api/read?',
-    placeholder: t('Select a annotation layer'),
-    onAsyncErrorMessage: t('Error while fetching annotation layers'),
-    mutator: (data) => {
-      if (!data || !data.result) {
-        return [];
-      }
-      return data.result.map(layer => ({ value: layer.id, label: layer.name 
}));
-    },
-  },
-
   metric: {
     type: 'SelectControl',
     label: t('Metric'),
@@ -1561,6 +1543,14 @@ export const controls = {
     }),
   },
 
+  annotation_layers: {
+    type: 'AnnotationLayerControl',
+    label: '',
+    default: [],
+    description: 'Annotation Layers',
+    renderTrigger: true,
+  },
+
   having_filters: {
     type: 'FilterControl',
     label: '',
diff --git a/superset/assets/javascripts/explore/stores/visTypes.js 
b/superset/assets/javascripts/explore/stores/visTypes.js
index a243cbfd2d..ef9dc4112c 100644
--- a/superset/assets/javascripts/explore/stores/visTypes.js
+++ b/superset/assets/javascripts/explore/stores/visTypes.js
@@ -45,7 +45,7 @@ export const sections = {
     description: t('This section exposes ways to include snippets of SQL in 
your query'),
   },
   annotations: {
-    label: t('Annotations'),
+    label: t('Annotations and Layers'),
     expanded: true,
     controlSetRows: [
       ['annotation_layers'],
diff --git a/superset/assets/javascripts/modules/AnnotationTypes.js 
b/superset/assets/javascripts/modules/AnnotationTypes.js
new file mode 100644
index 0000000000..28684bbcb6
--- /dev/null
+++ b/superset/assets/javascripts/modules/AnnotationTypes.js
@@ -0,0 +1,94 @@
+import { VIZ_TYPES } from '../../visualizations/main';
+import vizTypes from '../explore/stores/visTypes';
+
+export const ANNOTATION_TYPES = {
+  FORMULA: 'FORMULA',
+  EVENT: 'EVENT',
+  INTERVAL: 'INTERVAL',
+  TIME_SERIES: 'TIME_SERIES',
+};
+
+export const ANNOTATION_TYPE_LABELS = {
+  FORMULA: 'Formula ',
+  EVENT: 'Event',
+  INTERVAL: 'Interval',
+  TIME_SERIES: 'Time Series',
+};
+
+export function getAnnotationTypeLabel(annotationType) {
+  return ANNOTATION_TYPE_LABELS[annotationType];
+}
+
+export const DEFAULT_ANNOTATION_TYPE = ANNOTATION_TYPES.FORMULA;
+
+export const ANNOTATION_SOURCE_TYPES = {
+  NATIVE: 'NATIVE',
+  ...VIZ_TYPES,
+};
+
+export function getAnnotationSourceTypeLabels(sourceType) {
+  return ANNOTATION_SOURCE_TYPES.NATIVE === sourceType ? 'Superset annotation' 
:
+      vizTypes[sourceType].label;
+}
+
+export function requiresQuery(annotationSourceType) {
+  return !!annotationSourceType;
+}
+
+// Map annotation type to annotation source type
+const SUPPORTED_SOURCE_TYPE_MAP = {
+  [ANNOTATION_TYPES.EVENT]: [
+    ANNOTATION_SOURCE_TYPES.NATIVE,
+    ANNOTATION_SOURCE_TYPES.table,
+  ],
+  [ANNOTATION_TYPES.INTERVAL]: [
+    ANNOTATION_SOURCE_TYPES.NATIVE,
+    ANNOTATION_SOURCE_TYPES.table,
+  ],
+  [ANNOTATION_TYPES.TIME_SERIES]: [
+    ANNOTATION_SOURCE_TYPES.line,
+  ],
+};
+
+export function getSupportedSourceTypes(annotationType) {
+  return SUPPORTED_SOURCE_TYPE_MAP[annotationType] || [];
+}
+
+// Map from viz type to supported annotation
+const SUPPORTED_ANNOTATIONS = {
+  [VIZ_TYPES.line]: [
+    ANNOTATION_TYPES.TIME_SERIES,
+    ANNOTATION_TYPES.INTERVAL,
+    ANNOTATION_TYPES.EVENT,
+    ANNOTATION_TYPES.FORMULA,
+  ],
+  [VIZ_TYPES.bar]: [
+    ANNOTATION_TYPES.INTERVAL,
+    ANNOTATION_TYPES.EVENT,
+  ],
+  [VIZ_TYPES.area]: [
+    ANNOTATION_TYPES.INTERVAL,
+    ANNOTATION_TYPES.EVENT,
+  ],
+};
+
+export function getSupportedAnnotationTypes(vizType) {
+  return SUPPORTED_ANNOTATIONS[vizType] || [];
+}
+
+const NATIVE_COLUMN_NAMES = {
+  timeColumn: 'start_dttm',
+  intervalEndColumn: 'end_dttm',
+  titleColumn: 'short_descr',
+  descriptionColumns: ['long_descr'],
+};
+
+export function applyNativeColumns(annotation) {
+  if (annotation.sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) {
+    return { ...annotation, ...NATIVE_COLUMN_NAMES };
+  }
+  return annotation;
+}
+
+export default ANNOTATION_TYPES;
+
diff --git a/superset/assets/package.json b/superset/assets/package.json
index 9d8b01a811..905e770294 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -15,6 +15,7 @@
     "prod": "NODE_ENV=production node --max_old_space_size=4096 
./node_modules/webpack/bin/webpack.js -p --colors --progress",
     "build": "NODE_ENV=production webpack --colors --progress",
     "lint": "eslint --ignore-path=.eslintignore --ext .js,.jsx .",
+    "lint-fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx .",
     "sync-backend": "babel-node --presets env javascripts/syncBackend.js"
   },
   "repository": {
@@ -64,6 +65,7 @@
     "jquery": "3.1.1",
     "lodash.throttle": "^4.1.1",
     "luma.gl": "^4.0.5",
+    "mathjs": "^3.16.3",
     "moment": "2.18.1",
     "mustache": "^2.2.1",
     "nvd3": "1.8.6",
diff --git a/superset/assets/spec/javascripts/explore/chartActions_spec.js 
b/superset/assets/spec/javascripts/explore/chartActions_spec.js
index f88de8f955..4caeccd3e2 100644
--- a/superset/assets/spec/javascripts/explore/chartActions_spec.js
+++ b/superset/assets/spec/javascripts/explore/chartActions_spec.js
@@ -23,14 +23,16 @@ describe('chart actions', () => {
   });
 
   it('should handle query timeout', () => {
-    ajaxStub.yieldsTo('error', { statusText: 'timeout' });
+    ajaxStub.rejects({ statusText: 'timeout' });
     request = actions.runQuery({});
-    request(dispatch, sinon.stub().returns({
+    const promise = request(dispatch, sinon.stub().returns({
       explore: {
         controls: [],
       },
     }));
-    expect(dispatch.callCount).to.equal(3);
-    expect(dispatch.args[0][0].type).to.equal(actions.CHART_UPDATE_TIMEOUT);
+    promise.then(() => {
+      expect(dispatch.callCount).to.equal(3);
+      expect(dispatch.args[0][0].type).to.equal(actions.CHART_UPDATE_TIMEOUT);
+    });
   });
 });
diff --git a/superset/assets/stylesheets/dashboard.css 
b/superset/assets/stylesheets/dashboard.css
index b6d86abbd0..c1f08a7e38 100644
--- a/superset/assets/stylesheets/dashboard.css
+++ b/superset/assets/stylesheets/dashboard.css
@@ -146,3 +146,11 @@ div.widget:hover .chart-controls {
 .slice_container .alert {
     margin: 10px;
 }
+
+i.danger {
+  color: red;
+}
+
+i.warning {
+  color: orange;
+}
diff --git a/superset/assets/stylesheets/superset.less 
b/superset/assets/stylesheets/superset.less
index d86ad74b82..ae0be2cd64 100644
--- a/superset/assets/stylesheets/superset.less
+++ b/superset/assets/stylesheets/superset.less
@@ -376,7 +376,7 @@ iframe {
   padding-bottom: 10px;
 }
 .popover {
-  max-width: 500px !important;
+  max-width: 500px;
 }
 .float-left {
   float: left;
diff --git a/superset/assets/visualizations/main.js 
b/superset/assets/visualizations/main.js
index 9976614048..fdcb7b1eab 100644
--- a/superset/assets/visualizations/main.js
+++ b/superset/assets/visualizations/main.js
@@ -1,45 +1,90 @@
 /* eslint-disable global-require */
+
+// You ***should*** use these to reference viz_types in code
+export const VIZ_TYPES = {
+  area: 'area',
+  bar: 'bar',
+  big_number: 'big_number',
+  big_number_total: 'big_number_total',
+  box_plot: 'box_plot',
+  bubble: 'bubble',
+  bullet: 'bullet',
+  cal_heatmap: 'cal_heatmap',
+  compare: 'compare',
+  directed_force: 'directed_force',
+  chord: 'chord',
+  dist_bar: 'dist_bar',
+  filter_box: 'filter_box',
+  heatmap: 'heatmap',
+  histogram: 'histogram',
+  horizon: 'horizon',
+  iframe: 'iframe',
+  line: 'line',
+  mapbox: 'mapbox',
+  markup: 'markup',
+  para: 'para',
+  pie: 'pie',
+  pivot_table: 'pivot_table',
+  sankey: 'sankey',
+  separator: 'separator',
+  sunburst: 'sunburst',
+  table: 'table',
+  time_table: 'time_table',
+  treemap: 'treemap',
+  country_map: 'country_map',
+  word_cloud: 'word_cloud',
+  world_map: 'world_map',
+  dual_line: 'dual_line',
+  event_flow: 'event_flow',
+  paired_ttest: 'paired_ttest',
+  partition: 'partition',
+  deck_scatter: 'deck_scatter',
+  deck_screengrid: 'deck_screengrid',
+  deck_grid: 'deck_grid',
+  deck_hex: 'deck_hex',
+};
+
 const vizMap = {
-  area: require('./nvd3_vis.js'),
-  bar: require('./nvd3_vis.js'),
-  big_number: require('./big_number.js'),
-  big_number_total: require('./big_number.js'),
-  box_plot: require('./nvd3_vis.js'),
-  bubble: require('./nvd3_vis.js'),
-  bullet: require('./nvd3_vis.js'),
-  cal_heatmap: require('./cal_heatmap.js'),
-  compare: require('./nvd3_vis.js'),
-  directed_force: require('./directed_force.js'),
-  chord: require('./chord.jsx'),
-  dist_bar: require('./nvd3_vis.js'),
-  filter_box: require('./filter_box.jsx'),
-  heatmap: require('./heatmap.js'),
-  histogram: require('./histogram.js'),
-  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'),
-  pie: require('./nvd3_vis.js'),
-  pivot_table: require('./pivot_table.js'),
-  sankey: require('./sankey.js'),
-  separator: require('./markup.js'),
-  sunburst: require('./sunburst.js'),
-  table: require('./table.js'),
-  time_table: require('./time_table.jsx'),
-  treemap: require('./treemap.js'),
-  country_map: require('./country_map.js'),
-  word_cloud: require('./word_cloud.js'),
-  world_map: require('./world_map.js'),
-  dual_line: require('./nvd3_vis.js'),
-  event_flow: require('./EventFlow.jsx'),
-  paired_ttest: require('./paired_ttest.jsx'),
-  partition: require('./partition.js'),
-  deck_scatter: require('./deckgl/scatter.jsx'),
-  deck_screengrid: require('./deckgl/screengrid.jsx'),
-  deck_grid: require('./deckgl/grid.jsx'),
-  deck_hex: require('./deckgl/hex.jsx'),
+  [VIZ_TYPES.area]: require('./nvd3_vis.js'),
+  [VIZ_TYPES.bar]: require('./nvd3_vis.js'),
+  [VIZ_TYPES.big_number]: require('./big_number.js'),
+  [VIZ_TYPES.big_number_total]: require('./big_number.js'),
+  [VIZ_TYPES.box_plot]: require('./nvd3_vis.js'),
+  [VIZ_TYPES.bubble]: require('./nvd3_vis.js'),
+  [VIZ_TYPES.bullet]: require('./nvd3_vis.js'),
+  [VIZ_TYPES.cal_heatmap]: require('./cal_heatmap.js'),
+  [VIZ_TYPES.compare]: require('./nvd3_vis.js'),
+  [VIZ_TYPES.directed_force]: require('./directed_force.js'),
+  [VIZ_TYPES.chord]: require('./chord.jsx'),
+  [VIZ_TYPES.dist_bar]: require('./nvd3_vis.js'),
+  [VIZ_TYPES.filter_box]: require('./filter_box.jsx'),
+  [VIZ_TYPES.heatmap]: require('./heatmap.js'),
+  [VIZ_TYPES.histogram]: require('./histogram.js'),
+  [VIZ_TYPES.horizon]: require('./horizon.js'),
+  [VIZ_TYPES.iframe]: require('./iframe.js'),
+  [VIZ_TYPES.line]: require('./nvd3_vis.js'),
+  [VIZ_TYPES.time_pivot]: require('./nvd3_vis.js'),
+  [VIZ_TYPES.mapbox]: require('./mapbox.jsx'),
+  [VIZ_TYPES.markup]: require('./markup.js'),
+  [VIZ_TYPES.para]: require('./parallel_coordinates.js'),
+  [VIZ_TYPES.pie]: require('./nvd3_vis.js'),
+  [VIZ_TYPES.pivot_table]: require('./pivot_table.js'),
+  [VIZ_TYPES.sankey]: require('./sankey.js'),
+  [VIZ_TYPES.separator]: require('./markup.js'),
+  [VIZ_TYPES.sunburst]: require('./sunburst.js'),
+  [VIZ_TYPES.table]: require('./table.js'),
+  [VIZ_TYPES.time_table]: require('./time_table.jsx'),
+  [VIZ_TYPES.treemap]: require('./treemap.js'),
+  [VIZ_TYPES.country_map]: require('./country_map.js'),
+  [VIZ_TYPES.word_cloud]: require('./word_cloud.js'),
+  [VIZ_TYPES.world_map]: require('./world_map.js'),
+  [VIZ_TYPES.dual_line]: require('./nvd3_vis.js'),
+  [VIZ_TYPES.event_flow]: require('./EventFlow.jsx'),
+  [VIZ_TYPES.paired_ttest]: require('./paired_ttest.jsx'),
+  [VIZ_TYPES.partition]: require('./partition.js'),
+  [VIZ_TYPES.deck_scatter]: require('./deckgl/scatter.jsx'),
+  [VIZ_TYPES.deck_screengrid]: require('./deckgl/screengrid.jsx'),
+  [VIZ_TYPES.deck_grid]: require('./deckgl/grid.jsx'),
+  [VIZ_TYPES.deck_hex]: require('./deckgl/hex.jsx'),
 };
 export default vizMap;
diff --git a/superset/assets/visualizations/nvd3_vis.css 
b/superset/assets/visualizations/nvd3_vis.css
index 1a5897f14d..fed0d013dc 100644
--- a/superset/assets/visualizations/nvd3_vis.css
+++ b/superset/assets/visualizations/nvd3_vis.css
@@ -35,3 +35,32 @@ text.nv-axislabel {
 text.nv-axislabel {
   font-size: 14px !important;
 }
+
+g.solid path, line.solid {
+  stroke-dasharray: unset;
+}
+
+g.dashed path, line.dashed {
+  stroke-dasharray: 5, 5;
+}
+
+g.longDashed path, line.longDashed {
+  stroke-dasharray: 10, 2;
+}
+
+g.dotted path, line.dotted {
+  stroke-dasharray: 1, 1;
+}
+
+g.opacityLow path, line.opacityLow {
+  stroke-opacity: .2
+}
+
+g.opacityMedium path, line.opacityMedium {
+  stroke-opacity: .5
+}
+
+g.opacityHigh path, line.opacityHigh {
+  stroke-opacity: .8
+}
+
diff --git a/superset/assets/visualizations/nvd3_vis.js 
b/superset/assets/visualizations/nvd3_vis.js
index f1b6c11cde..0e3d9e87a6 100644
--- a/superset/assets/visualizations/nvd3_vis.js
+++ b/superset/assets/visualizations/nvd3_vis.js
@@ -3,14 +3,19 @@ import $ from 'jquery';
 import throttle from 'lodash.throttle';
 import d3 from 'd3';
 import nv from 'nvd3';
+import mathjs from 'mathjs';
 import d3tip from 'd3-tip';
 
 import { getColorFromScheme } from '../javascripts/modules/colors';
+import AnnotationTypes, {
+  applyNativeColumns,
+} from '../javascripts/modules/AnnotationTypes';
 import { customizeToolTip, d3TimeFormatPreset, d3FormatPreset, tryNumify } 
from '../javascripts/modules/utils';
 
 // CSS
 import '../node_modules/nvd3/build/nv.d3.min.css';
 import './nvd3_vis.css';
+import { VIZ_TYPES } from './main';
 
 const minBarWidth = 15;
 const animationTime = 1000;
@@ -392,7 +397,7 @@ function nvd3Vis(slice, payload) {
         return `rgba(${c.r}, ${c.g}, ${c.b}, ${alpha})`;
       });
     } else if (vizType !== 'bullet') {
-      chart.color(d => getColorFromScheme(d[colorKey], fd.color_scheme));
+      chart.color(d => d.color || getColorFromScheme(d[colorKey], 
fd.color_scheme));
     }
     if ((vizType === 'line' || vizType === 'area') && fd.rich_tooltip) {
       chart.useInteractiveGuideline(true);
@@ -526,82 +531,200 @@ function nvd3Vis(slice, payload) {
       .attr('width', width)
       .call(chart);
 
-      // add annotation_layer
-      if (isTimeSeries && payload.annotations && payload.annotations.length) {
-        const tip = d3tip()
+      // on scroll, hide tooltips. throttle to only 4x/second.
+      $(window).scroll(throttle(hideTooltips, 250));
+
+      const annotationLayers = (slice.formData.annotation_layers || [])
+          .filter(x => x.show);
+      if (isTimeSeries && annotationLayers) {
+        // Formula annotations
+        const formulas = annotationLayers.filter(a => a.annotationType === 
AnnotationTypes.FORMULA)
+          .map(a => ({ ...a, formula: mathjs.parse(a.value) }));
+
+        let xMax;
+        let xMin;
+        let xScale;
+        if (vizType === VIZ_TYPES.bar) {
+          xMin = d3.min(data[0].values, d => (d.x));
+          xMax = d3.max(data[0].values, d => (d.x));
+          xScale = d3.scale.quantile()
+            .domain([xMin, xMax])
+            .range(chart.xAxis.range());
+        } else {
+          xMin = chart.xAxis.scale().domain()[0].valueOf();
+          xMax = chart.xAxis.scale().domain()[1].valueOf();
+          xScale = chart.xScale();
+        }
+
+        if (Array.isArray(formulas) && formulas.length) {
+          const xValues = [];
+          if (vizType === VIZ_TYPES.bar) {
+            // For bar-charts we want one data point evaluated for every
+            // data point that will be displayed.
+            const distinct = data.reduce((xVals, d) => {
+              d.values.forEach(x => xVals.add(x.x));
+              return xVals;
+            }, new Set());
+            xValues.push(...distinct.values());
+            xValues.sort();
+          } else {
+            // For every other time visualization it should be ok, to have a
+            // data points in even intervals.
+            let period = Math.min(...data.map(d =>
+              Math.min(...d.values.slice(1).map((v, i) => v.x - 
d.values[i].x))));
+            const dataPoints = (xMax - xMin) / (period || 1);
+            // make sure that there are enough data points and not too many
+            period = dataPoints < 100 ? (xMax - xMin) / 100 : period;
+            period = dataPoints > 500 ? (xMax - xMin) / 500 : period;
+            xValues.push(xMin);
+            for (let x = xMin; x < xMax; x += period) {
+              xValues.push(x);
+            }
+            xValues.push(xMax);
+          }
+          const formulaData = formulas.map(fo => ({
+            key: fo.name,
+            values: xValues.map((x => ({ y: fo.formula.eval({ x }), x }))),
+            color: fo.color,
+            strokeWidth: fo.width,
+            classed: `${fo.opacity} ${fo.style}`,
+          }));
+          data.push(...formulaData);
+        }
+
+        const annotationHeight = chart.yAxis.scale().range()[0];
+        const tipFactory = layer => d3tip()
           .attr('class', 'd3-tip')
           .direction('n')
           .offset([-5, 0])
           .html((d) => {
-            if (!d || !d.layer) {
+            if (!d) {
               return '';
             }
-
-            const title = d.short_descr ?
-              d.short_descr + ' - ' + d.layer :
-              d.layer;
-            const body = d.long_descr;
+            const title = d[layer.titleColumn] && d[layer.titleColumn].length ?
+              d[layer.titleColumn] + ' - ' + layer.name :
+              layer.name;
+            const body = Array.isArray(layer.descriptionColumns) ?
+              layer.descriptionColumns.map(c => d[c]) : Object.values(d);
             return '<div><strong>' + title + '</strong></div><br/>' +
-            '<div>' + body + '</div>';
+              '<div>' + body.join(', ') + '</div>';
           });
 
-        const hh = chart.yAxis.scale().range()[0];
+        if (slice.annotationData && Object.keys(slice.annotationData).length) {
+          // Event annotations
+          annotationLayers.filter(x => (
+            x.annotationType === AnnotationTypes.EVENT &&
+            slice.annotationData && slice.annotationData[x.name]
+          )).forEach((config, index) => {
+            const e = applyNativeColumns(config);
+            // Add event annotation layer
+            const annotations = 
d3.select(slice.selector).select('.nv-wrap').append('g')
+              .attr('class', `nv-event-annotation-layer-${index}`);
+            const aColor = e.color || getColorFromScheme(e.name, 
fd.color_scheme);
+
+            const tip = tipFactory(e);
+            const records = (slice.annotationData[e.name].records || 
[]).map((r) => {
+              const timeColumn = new Date(r[e.timeColumn]);
+              return {
+                ...r,
+                [e.timeColumn]: timeColumn,
+              };
+            }).filter(r => !Number.isNaN(r[e.timeColumn].getMilliseconds()));
+            if (records.length) {
+              annotations.selectAll('line')
+                .data(records)
+                .enter()
+                .append('line')
+                .attr({
+                  x1: d => xScale(new Date(d[e.timeColumn])),
+                  y1: 0,
+                  x2: d => xScale(new Date(d[e.timeColumn])),
+                  y2: annotationHeight,
+                })
+                .attr('class', `${e.opacity} ${e.style}`)
+                .style('stroke', aColor)
+                .style('stroke-width', e.width)
+                .on('mouseover', tip.show)
+                .on('mouseout', tip.hide)
+                .call(tip);
+            }
+          });
 
-        let annotationLayer;
-        let xScale;
-        let minStep;
-        if (vizType === 'bar') {
-          const xMax = d3.max(payload.data[0].values, d => (d.x));
-          const xMin = d3.min(payload.data[0].values, d => (d.x));
-          minStep = chart.xAxis.range()[1] - chart.xAxis.range()[0];
-          annotationLayer = svg.select('.nv-barsWrap')
-            .insert('g', ':first-child');
-          xScale = d3.scale.quantile()
-            .domain([xMin, xMax])
-            .range(chart.xAxis.range());
-        } else {
-          minStep = 1;
-          annotationLayer = svg.select('.nv-background')
-            .append('g');
-          xScale = chart.xScale();
-        }
 
-        annotationLayer
-          .attr('class', 'annotation-container')
-          .append('defs')
-          .append('pattern')
-          .attr('id', 'diagonal')
-          .attr('patternUnits', 'userSpaceOnUse')
-          .attr('width', 8)
-          .attr('height', 10)
-          .attr('patternTransform', 'rotate(45 50 50)')
-          .append('line')
-          .attr('stroke-width', 7)
-          .attr('y2', 10);
-
-        annotationLayer.selectAll('rect')
-          .data(payload.annotations)
-          .enter()
-          .append('rect')
-          .attr('class', 'annotation')
-          .attr('x', d => (xScale(d.start_dttm)))
-          .attr('y', 0)
-          .attr('width', (d) => {
-            const w = xScale(d.end_dttm) - xScale(d.start_dttm);
-            return w === 0 ? minStep : w;
-          })
-          .attr('height', hh)
-          .attr('fill', 'url(#diagonal)')
-          .on('mouseover', tip.show)
-          .on('mouseout', tip.hide);
-
-        annotationLayer.selectAll('rect').call(tip);
-      }
-    }
+          // Interval annotations
+          annotationLayers.filter(x => (
+            x.annotationType === AnnotationTypes.INTERVAL &&
+            slice.annotationData && slice.annotationData[x.name]
+          )).forEach((config, index) => {
+            const e = applyNativeColumns(config);
+            // Add interval annotation layer
+            const annotations = 
d3.select(slice.selector).select('.nv-wrap').append('g')
+              .attr('class', `nv-interval-annotation-layer-${index}`);
+
+            const aColor = e.color || getColorFromScheme(e.name, 
fd.color_scheme);
+            const tip = tipFactory(e);
+
+            const records = (slice.annotationData[e.name].records || 
[]).map((r) => {
+              const timeColumn = new Date(r[e.timeColumn]);
+              const intervalEndColumn = new Date(r[e.intervalEndColumn]);
+              return {
+                ...r,
+                [e.timeColumn]: timeColumn,
+                [e.intervalEndColumn]: intervalEndColumn,
+              };
+            }).filter(r => !Number.isNaN(r[e.timeColumn].getMilliseconds()) &&
+              !Number.isNaN(r[e.intervalEndColumn].getMilliseconds()));
+            if (records.length) {
+              annotations.selectAll('rect')
+                .data(records)
+                .enter()
+                .append('rect')
+                .attr({
+                  x: d => Math.min(xScale(new Date(d[e.timeColumn])),
+                    xScale(new Date(d[e.intervalEndColumn]))),
+                  y: 0,
+                  width: d => Math.abs(xScale(new 
Date(d[e.intervalEndColumn])) -
+                    xScale(new Date(d[e.timeColumn]))),
+                  height: annotationHeight,
+                })
+                .attr('class', `${e.opacity} ${e.style}`)
+                .style('stroke-width', e.width)
+                .style('stroke', aColor)
+                .style('fill', aColor)
+                .style('fill-opacity', 0.2)
+                .on('mouseover', tip.show)
+                .on('mouseout', tip.hide)
+                .call(tip);
+            }
+          });
 
-    // on scroll, hide tooltips. throttle to only 4x/second.
-    $(window).scroll(throttle(hideTooltips, 250));
+          // Time series annotations
+          const timeSeriesAnnotations = annotationLayers
+            .filter(a => a.annotationType === 
AnnotationTypes.TIME_SERIES).reduce((bushel, a) =>
+              bushel.concat((slice.annotationData[a.name] || []).map((series) 
=> {
+                if (!series) {
+                  return {};
+                }
+                const key = Array.isArray(series.key) ?
+                  `${a.name}, ${series.key.join(', ')}` : a.name;
+                return {
+                  ...series,
+                  key,
+                  color: a.color,
+                  strokeWidth: a.width,
+                  classed: `${a.opacity} ${a.style}`,
+                };
+              })), []);
+          data.push(...timeSeriesAnnotations);
+        }
+      }
 
+      // rerender chart
+      svg.datum(data)
+        .attr('height', height)
+        .attr('width', width)
+        .call(chart);
+    }
     return chart;
   };
 
diff --git a/superset/connectors/sqla/models.py 
b/superset/connectors/sqla/models.py
index e0952288ff..18803c565b 100644
--- a/superset/connectors/sqla/models.py
+++ b/superset/connectors/sqla/models.py
@@ -21,12 +21,49 @@
 from superset import db, import_util, sm, utils
 from superset.connectors.base.models import BaseColumn, BaseDatasource, 
BaseMetric
 from superset.jinja_context import get_template_processor
+from superset.models.annotations import Annotation
 from superset.models.core import Database
 from superset.models.helpers import QueryResult
 from superset.models.helpers import set_perm
 from superset.utils import DTTM_ALIAS, QueryStatus
 
 
+class AnnotationDatasource(BaseDatasource):
+    """ Dummy object so we can query annotations using 'Viz' objects just like
+        regular datasources.
+    """
+
+    cache_timeout = 0
+
+    def query(self, query_obj):
+        df = None
+        error_message = None
+        qry = db.session.query(Annotation)
+        qry = qry.filter(Annotation.layer_id == query_obj['filter'][0]['val'])
+        qry = qry.filter(Annotation.start_dttm >= query_obj['from_dttm'])
+        qry = qry.filter(Annotation.end_dttm <= query_obj['to_dttm'])
+        status = QueryStatus.SUCCESS
+        try:
+            df = pd.read_sql_query(qry.statement, db.engine)
+        except Exception as e:
+            status = QueryStatus.FAILED
+            logging.exception(e)
+            error_message = (
+                utils.error_msg_from_exception(e))
+        return QueryResult(
+            status=status,
+            df=df,
+            duration=0,
+            query='',
+            error_message=error_message)
+
+    def get_query_str(self, query_obj):
+        raise NotImplementedError()
+
+    def values_for_column(self, column_name, limit=10000):
+        raise NotImplementedError()
+
+
 class TableColumn(Model, BaseColumn):
 
     """ORM object for table columns, each table can have multiple columns"""
diff --git a/superset/models/annotations.py b/superset/models/annotations.py
index 8aac6a2217..e082be0923 100644
--- a/superset/models/annotations.py
+++ b/superset/models/annotations.py
@@ -48,6 +48,7 @@ class Annotation(Model, AuditMixinNullable):
     @property
     def data(self):
         return {
+            'layer_id': self.layer_id,
             'start_dttm': self.start_dttm,
             'end_dttm': self.end_dttm,
             'short_descr': self.short_descr,
diff --git a/superset/views/core.py b/superset/views/core.py
index 00254b4ca2..e83d34f70b 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -37,7 +37,7 @@
     viz,
 )
 from superset.connectors.connector_registry import ConnectorRegistry
-from superset.connectors.sqla.models import SqlaTable
+from superset.connectors.sqla.models import AnnotationDatasource, SqlaTable
 from superset.forms import CsvToDatabaseForm
 from superset.legacy import cast_form_data
 import superset.models.core as models
@@ -946,7 +946,7 @@ def get_form_data(self):
     def get_viz(
             self,
             slice_id=None,
-            args=None,
+            form_data=None,
             datasource_type=None,
             datasource_id=None):
         if slice_id:
@@ -957,7 +957,6 @@ def get_viz(
             )
             return slc.get_viz()
         else:
-            form_data = self.get_form_data()
             viz_type = form_data.get('viz_type', 'table')
             datasource = ConnectorRegistry.get_datasource(
                 datasource_type, datasource_id, db.session)
@@ -971,14 +970,11 @@ def get_viz(
     @expose('/slice/<slice_id>/')
     def slice(self, slice_id):
         viz_obj = self.get_viz(slice_id)
-        endpoint = (
-            '/superset/explore/{}/{}?form_data={}'
-            .format(
+        endpoint = '/superset/explore/{}/{}?form_data={}'.format(
                 viz_obj.datasource.type,
                 viz_obj.datasource.id,
                 parse.quote(json.dumps(viz_obj.form_data)),
             )
-        )
         if request.args.get('standalone') == 'true':
             endpoint += '&standalone=true'
         return redirect(endpoint)
@@ -997,15 +993,13 @@ def get_query_string_response(self, viz_obj):
             status=200,
             mimetype='application/json')
 
-    @log_this
-    @has_access_api
-    @expose('/explore_json/<datasource_type>/<datasource_id>/')
-    def explore_json(self, datasource_type, datasource_id):
+    def generate_json(self, datasource_type, datasource_id, form_data,
+                      csv=False, query=False, force=False):
         try:
             viz_obj = self.get_viz(
                 datasource_type=datasource_type,
                 datasource_id=datasource_id,
-                args=request.args)
+                form_data=form_data)
         except Exception as e:
             logging.exception(e)
             return json_error_response(
@@ -1015,20 +1009,19 @@ def explore_json(self, datasource_type, datasource_id):
         if not self.datasource_access(viz_obj.datasource):
             return json_error_response(DATASOURCE_ACCESS_ERR, status=404)
 
-        if request.args.get('csv') == 'true':
+        if csv:
             return CsvResponse(
                 viz_obj.get_csv(),
                 status=200,
                 headers=generate_download_headers('csv'),
                 mimetype='application/csv')
 
-        if request.args.get('query') == 'true':
+        if query:
             return self.get_query_string_response(viz_obj)
 
-        payload = {}
         try:
             payload = viz_obj.get_payload(
-                force=request.args.get('force') == 'true')
+                force=force)
         except Exception as e:
             logging.exception(e)
             return json_error_response(utils.error_msg_from_exception(e))
@@ -1039,6 +1032,70 @@ def explore_json(self, datasource_type, datasource_id):
 
         return json_success(viz_obj.json_dumps(payload), status=status)
 
+    @log_this
+    @has_access_api
+    @expose('/slice_json/<slice_id>')
+    def slice_json(self, slice_id):
+        try:
+            viz_obj = self.get_viz(slice_id)
+            datasource_type = viz_obj.datasource.type
+            datasource_id = viz_obj.datasource.id
+            form_data = viz_obj.form_data
+            # This allows you to override the saved slice form data with
+            # data from the current request (e.g. change the time window)
+            form_data.update(self.get_form_data())
+        except Exception as e:
+            return json_error_response(
+                utils.error_msg_from_exception(e),
+                stacktrace=traceback.format_exc())
+        return self.generate_json(datasource_type=datasource_type,
+                                  datasource_id=datasource_id,
+                                  form_data=form_data)
+
+    @log_this
+    @has_access_api
+    @expose('/annotation_json/<layer_id>')
+    def annotation_json(self, layer_id):
+        form_data = self.get_form_data()
+        form_data['layer_id'] = layer_id
+        form_data['filters'] = [{'col': 'layer_id',
+                                 'op': '==',
+                                 'val': layer_id}]
+        datasource = AnnotationDatasource()
+        viz_obj = viz.viz_types['table'](
+          datasource,
+          form_data=form_data,
+        )
+        try:
+            payload = viz_obj.get_payload(force=False)
+        except Exception as e:
+            logging.exception(e)
+            return json_error_response(utils.error_msg_from_exception(e))
+        status = 200
+        if payload.get('status') == QueryStatus.FAILED:
+            status = 400
+        return json_success(viz_obj.json_dumps(payload), status=status)
+
+    @log_this
+    @has_access_api
+    @expose('/explore_json/<datasource_type>/<datasource_id>/')
+    def explore_json(self, datasource_type, datasource_id):
+        try:
+            csv = request.args.get('csv') == 'true'
+            query = request.args.get('query') == 'true'
+            force = request.args.get('force') == 'true'
+            form_data = self.get_form_data()
+        except Exception as e:
+                return json_error_response(
+                    utils.error_msg_from_exception(e),
+                    stacktrace=traceback.format_exc())
+        return self.generate_json(datasource_type=datasource_type,
+                                  datasource_id=datasource_id,
+                                  form_data=form_data,
+                                  csv=csv,
+                                  query=query,
+                                  force=force)
+
     @log_this
     @has_access
     @expose('/import_dashboards', methods=['GET', 'POST'])
@@ -1644,9 +1701,51 @@ def created_dashboards(self, user_id):
 
     @api
     @has_access_api
+    @expose('/user_slices', methods=['GET'])
+    @expose('/user_slices/<user_id>/', methods=['GET'])
+    def user_slices(self, user_id=None):
+        """List of slices a user created, or faved"""
+        if not user_id:
+            user_id = g.user.id
+        Slice = models.Slice  # noqa
+        FavStar = models.FavStar # noqa
+        qry = (
+            db.session.query(Slice,
+                             FavStar.dttm).join(
+                models.FavStar,
+                sqla.and_(
+                    models.FavStar.user_id == int(user_id),
+                    models.FavStar.class_name == 'slice',
+                    models.Slice.id == models.FavStar.obj_id,
+                ),
+                isouter=True).filter(
+                sqla.or_(
+                    Slice.created_by_fk == user_id,
+                    Slice.changed_by_fk == user_id,
+                    FavStar.user_id == user_id,
+                ),
+            )
+            .order_by(Slice.slice_name.asc())
+        )
+        payload = [{
+            'id': o.Slice.id,
+            'title': o.Slice.slice_name,
+            'url': o.Slice.slice_url,
+            'data': o.Slice.form_data,
+            'dttm': o.dttm if o.dttm else o.Slice.changed_on,
+            'viz_type': o.Slice.viz_type,
+        } for o in qry.all()]
+        return json_success(
+            json.dumps(payload, default=utils.json_int_dttm_ser))
+
+    @api
+    @has_access_api
+    @expose('/created_slices', methods=['GET'])
     @expose('/created_slices/<user_id>/', methods=['GET'])
-    def created_slices(self, user_id):
+    def created_slices(self, user_id=None):
         """List of slices created by this user"""
+        if not user_id:
+            user_id = g.user.id
         Slice = models.Slice  # noqa
         qry = (
             db.session.query(Slice)
@@ -1663,15 +1762,19 @@ def created_slices(self, user_id):
             'title': o.slice_name,
             'url': o.slice_url,
             'dttm': o.changed_on,
+            'viz_type': o.viz_type,
         } for o in qry.all()]
         return json_success(
             json.dumps(payload, default=utils.json_int_dttm_ser))
 
     @api
     @has_access_api
+    @expose('/fave_slices', methods=['GET'])
     @expose('/fave_slices/<user_id>/', methods=['GET'])
-    def fave_slices(self, user_id):
+    def fave_slices(self, user_id=None):
         """Favorite slices for a user"""
+        if not user_id:
+            user_id = g.user.id
         qry = (
             db.session.query(
                 models.Slice,
@@ -1696,6 +1799,7 @@ def fave_slices(self, user_id):
                 'title': o.Slice.slice_name,
                 'url': o.Slice.slice_url,
                 'dttm': o.dttm,
+                'viz_type': o.Slice.viz_type,
             }
             if o.Slice.created_by:
                 user = o.Slice.created_by
diff --git a/superset/viz.py b/superset/viz.py
index 6551577de1..9b913e4dbe 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -61,7 +61,7 @@ def __init__(self, datasource, form_data):
             'token', 'token_' + uuid.uuid4().hex[:8])
         self.metrics = self.form_data.get('metrics') or []
         self.groupby = self.form_data.get('groupby') or []
-        self.annotation_layers = []
+        self.time_shift = timedelta()
 
         self.status = None
         self.error_message = None
@@ -121,6 +121,7 @@ def get_df(self, query_obj=None):
                         df[DTTM_ALIAS], utc=False, format=timestamp_format)
                 if self.datasource.offset:
                     df[DTTM_ALIAS] += timedelta(hours=self.datasource.offset)
+                df[DTTM_ALIAS] += self.time_shift
             df.replace([np.inf, -np.inf], np.nan)
             fillna = self.get_fillna_for_columns(df.columns)
             df = df.fillna(fillna)
@@ -158,6 +159,7 @@ def query_obj(self):
 
         since = form_data.get('since', '')
         until = form_data.get('until', 'now')
+        time_shift = form_data.get('time_shift', '')
 
         # Backward compatibility hack
         if since:
@@ -166,15 +168,15 @@ def query_obj(self):
             if (len(since_words) == 2 and since_words[1] in grains):
                 since += ' ago'
 
-        from_dttm = utils.parse_human_datetime(since)
+        self.time_shift = utils.parse_human_timedelta(time_shift)
 
-        to_dttm = utils.parse_human_datetime(until)
+        from_dttm = utils.parse_human_datetime(since) - self.time_shift
+        to_dttm = utils.parse_human_datetime(until) - self.time_shift
         if from_dttm and to_dttm and from_dttm > to_dttm:
             raise Exception(_('From date cannot be larger than to date'))
 
         self.from_dttm = from_dttm
         self.to_dttm = to_dttm
-        self.annotation_layers = form_data.get('annotation_layers') or []
 
         # extras are used to query elements specific to a datasource type
         # for instance the extra where clause that applies only to Tables
@@ -227,23 +229,6 @@ def cache_key(self):
         s = str([(k, form_data[k]) for k in sorted(form_data.keys())])
         return hashlib.md5(s.encode('utf-8')).hexdigest()
 
-    def get_annotations(self):
-        """Fetches the annotations for the specified layers and date range"""
-        annotations = []
-        if self.annotation_layers:
-            from superset.models.annotations import Annotation
-            from superset import db
-            qry = (
-                db.session
-                .query(Annotation)
-                .filter(Annotation.layer_id.in_(self.annotation_layers)))
-            if self.from_dttm:
-                qry = qry.filter(Annotation.start_dttm >= self.from_dttm)
-            if self.to_dttm:
-                qry = qry.filter(Annotation.end_dttm <= self.to_dttm)
-            annotations = [o.data for o in qry.all()]
-        return annotations
-
     def get_payload(self, force=False):
         """Handles caching around the json payload retrieval"""
         cache_key = self.cache_key
@@ -272,13 +257,11 @@ def get_payload(self, force=False):
             is_cached = False
             cache_timeout = self.cache_timeout
             stacktrace = None
-            annotations = []
             rowcount = None
             try:
                 df = self.get_df()
                 if not self.error_message:
                     data = self.get_data(df)
-                annotations = self.get_annotations()
                 rowcount = len(df.index)
             except Exception as e:
                 logging.exception(e)
@@ -296,7 +279,6 @@ def get_payload(self, force=False):
                 'query': self.query,
                 'status': self.status,
                 'stacktrace': stacktrace,
-                'annotations': annotations,
                 'rowcount': rowcount,
             }
             payload['cached_dttm'] = 
datetime.utcnow().isoformat().split('.')[0]
diff --git a/tests/viz_tests.py b/tests/viz_tests.py
index 06096e9292..67f4bf89b3 100644
--- a/tests/viz_tests.py
+++ b/tests/viz_tests.py
@@ -89,9 +89,13 @@ def test_get_df_handles_dttm_col(self):
         mock_call = df.__setitem__.mock_calls[2]
         self.assertEqual(mock_call[1][0], DTTM_ALIAS)
         self.assertFalse(mock_call[1][1].empty)
-        self.assertEqual(mock_call[1][1][0].hour, 6)
+        self.assertEqual(mock_call[1][1][0].hour, 7)
         mock_call = df.__setitem__.mock_calls[3]
         self.assertEqual(mock_call[1][0], DTTM_ALIAS)
+        self.assertEqual(mock_call[1][1][0].hour, 6)
+        self.assertEqual(mock_call[1][1].dtype, 'datetime64[ns]')
+        mock_call = df.__setitem__.mock_calls[4]
+        self.assertEqual(mock_call[1][0], DTTM_ALIAS)
         self.assertEqual(mock_call[1][1][0].hour, 7)
         self.assertEqual(mock_call[1][1].dtype, 'datetime64[ns]')
 


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

Reply via email to