Repository: lux-org/lux (language: python)

Issue:
Improve Temporal action to display different time scales
For temporal attributes like dates, the column can often be visualized at different time-scales. For example, a date formatted as %y-%m-%d can be plotted as 3+1 separate visualizations: one based on year, one based on the month, and one based on day, and one based on the overall date. We should extend the temporal action to display the temporal columns at these different time scales.
New action: "All-column" vis when only few columns in dataframe
When there are only two columns in a dataframe involving a quantitative and a temporal attribute, we display separate univariate charts. The better chart to display here would be a line chart with both attributes. We need to change the action logic to account for this use case.

```python
tmax = pd.read_csv('https://raw.githubusercontent.com/koldunovn/python_for_geosciences/master/DelhiTmax.txt', delimiter=r"\s+", parse_dates=[[0,1,2]], header=None)
tmax.columns = ['Date', 'Temp']
```

![image](https://user-images.githubusercontent.com/5554675/103758845-bd166f00-504d-11eb-8cda-09d4c46233bd.png)

[This notebook](https://github.com/koldunovn/python_for_geosciences/blob/master/06%20-%20Time%20series%20(Pandas).ipynb) has a good use case example for time series.




Discussion / hints:

A similar use case is when there are 1 dimension and 2 quantitative variables, the visualization shows as two separate charts.
![image](https://user-images.githubusercontent.com/5554675/104676717-e35d9e00-5722-11eb-8581-ab448dd2d64e.png)
![image](https://user-images.githubusercontent.com/5554675/104676725-e8225200-5722-11eb-9bf7-8d650801e749.png)
A single visualization like this would be more appropriate

![image](https://user-images.githubusercontent.com/5554675/104676820-1ef86800-5723-11eb-90e5-79a45b1bd72d.png)
![image](https://user-images.githubusercontent.com/5554675/104676837-26b80c80-5723-11eb-948d-32e9eac1d998.png)

Example code:
```python
df = pd.DataFrame({'nPts': {0: 49999, 1: 71174, 2: 101317, 3: 144224, 4: 205303, 5: 292249, 6: 416016, 7: 592198, 8: 842993, 9: 1200000}, 't_heatmap': {0: 0.15121674537658691, 1: 0.1811518669128418, 2: 0.2179429531097412, 3: 0.2787730693817139, 4: 0.3973350524902344, 5: 0.4233138561248779, 6: 0.580251932144165, 7: 0.7928099632263184, 8: 1.0876789093017578, 9: 1.6242818832397459}, 't_color_heatmap': {0: 0.14242982864379886, 1: 0.18866705894470212, 2: 0.1566781997680664, 3: 0.16737699508666992, 4: 0.19900894165039065, 5: 0.2701129913330078, 6: 0.2533812522888184, 7: 0.37183785438537603, 8: 0.3830866813659668, 9: 0.39321017265319824}, 't_bar': {0: 0.01940608024597168, 1: 0.02618718147277832, 2: 0.024693727493286133, 3: 0.029685020446777344, 4: 0.03471803665161133, 5: 0.04173588752746582, 6: 0.04706382751464844, 7: 0.0667569637298584, 8: 0.08260798454284668, 9: 0.1006929874420166}, 't_cbar': {0: 0.04185795783996582, 1: 0.050965070724487305, 2: 0.052091121673583984, 3: 0.0610501766204834, 4: 0.07504606246948242, 5: 0.09924101829528807, 6: 0.10392117500305176, 7: 0.13044309616088867, 8: 0.18137121200561526, 9: 0.2069528102874756}, 't_hist': {0: 0.01846003532409668, 1: 0.018136978149414062, 2: 0.018748998641967773, 3: 0.01876473426818848, 4: 0.02434706687927246, 5: 0.025368213653564453, 6: 0.02530217170715332, 7: 0.02823114395141602, 8: 0.029034852981567383, 9: 0.034781217575073235}, 't_scatter': {0: 0.8179378509521484, 1: 1.0982017517089844, 2: 1.4875690937042236, 3: 2.146117925643921, 4: 3.1123709678649902, 5: 3.92416787147522, 6: 6.097048044204713, 7: 8.803220987319945, 8: 12.054464101791382, 9: 17.740845680236816}, 't_color_scatter': {0: 1.3474130630493164, 1: 1.7953579425811768, 2: 2.6268541812896733, 3: 3.646371126174927, 4: 4.901940107345581, 5: 6.974237203598023, 6: 10.197483777999876, 7: 14.134275913238525, 8: 21.13951110839844, 9: 27.127063989639282}})
pdf = df.melt(id_vars=["nPts"],value_vars=['t_heatmap', 't_color_heatmap', 't_bar', 't_cbar', 't_hist','t_scatter', 't_color_scatter'], var_name="type", value_name='time')
```

Fix patch:
diff --git a/README.md b/README.md
index a8f7e566..5ecc3603 100644
--- a/README.md
+++ b/README.md
@@ -28,11 +28,16 @@
     </a>
 </p>
 
-Lux is a Python library that makes data science easier by automating aspects of the data exploration process. Lux facilitate faster experimentation with data, even when the user does not have a clear idea of what they are looking for. Visualizations are displayed via [an interactive widget](https://github.com/lux-org/lux-widget) that allow users to quickly browse through large collections of visualizations directly within their Jupyter notebooks.
+Lux is a Python library that facilitate fast and easy data exploration by automating the visualization and data analysis process. By simply printing out a dataframe in a Jupyter notebook, Lux recommends a set of visualizations highlighting interesting trends and patterns in the dataset. Visualizations are displayed via [an interactive widget](https://github.com/lux-org/lux-widget) that enables users to quickly browse through large collections of visualizations and make sense of their data.
 
+<img src="https://github.com/lux-org/lux-resources/blob/master/readme_img/demohighlight.gif?raw=true"
+     alt="Highlighted Visual Dataframe Workflow with Lux"
+     style="width:900px" />
+     
 Here is a [1-min video](https://www.youtube.com/watch?v=qmnYP-LmbNU) introducing Lux, and [slides](http://dorisjunglinlee.com/files/Zillow_07_2020_Slide.pdf) from a more extended talk.
 
-Try out Lux on your own in a live Jupyter Notebook [here](https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/demo/employee_demo.ipynb)! 
+Check out our [notebook gallery](https://lux-api.readthedocs.io/en/latest/source/reference/gallery.html) with examples of how Lux can be used with different datasets and analyses. 
+<br>Or try out Lux on your own in a [live Jupyter Notebook](https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/demo/employee_demo.ipynb)! 
 
 # Getting Started
 
@@ -43,26 +48,22 @@ import lux
 import pandas as pd
 ```
 
-Then, Lux can be used as-is, without modifying any of your existing Pandas code. Here, we use Pandas's [read_csv](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html) command to load in a [dataset of colleges](https://github.com/lux-org/lux-datasets/blob/master/data/college.csv) and their properties.
+Lux can be used without modifying any existing Pandas code. Here, we use Pandas's [read_csv](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html) command to load in a [dataset of colleges](https://github.com/lux-org/lux-datasets/blob/master/data/college.csv) and their properties.
 
 ```python    
 df = pd.read_csv("https://raw.githubusercontent.com/lux-org/lux-datasets/master/data/college.csv")
 df
 ```
-
+When the dataframe is printed out, Lux automatically recommends a set of visualizations highlighting interesting trends and patterns in the dataset.
 <img src="https://github.com/lux-org/lux-resources/blob/master/readme_img/basicDemo.gif?raw=true"
      alt="Basic recommendations in Lux"
      style="width:900px" />
 
 Voila! Here's a set of visualizations that you can now use to explore your dataset further!
 
-<!-- # Features
-Lux provides a suite of capabilities that enables users to effortlessly discover visual insights from their data. -->
-
-<!-- Lux guides users to potential next-steps in their exploration. -->
 ### Next-step recommendations based on user intent: 
 
-In addition to dataframe visualizations at every step in the exploration, you can specify in Lux the attributes and values you're interested in. Based on this intent, Lux guides users towards potential next-steps in their exploration.
+In addition to dataframe visualizations at every step in the exploration, you can specify to Lux the attributes and values you're interested in. Based on this intent, Lux guides users towards potential next-steps in their exploration.
 
 For example, we might be interested in the attributes `AverageCost` and `SATAverage`.
 
@@ -93,7 +94,7 @@ df
 
 ### Easy programmatic access and export of visualizations: 
 
-Now that we have found some interesting visualizations through Lux, we might be interested in digging into these visualizations a bit more or sharing it with others. We can save the visualizations generated in Lux as a [static, shareable HTML](https://lux-api.readthedocs.io/en/latest/source/guide/export.html#exporting-widget-visualizations-as-static-html) or programmatically access these visualizations further in Jupyter. Selected `Vis` objects can be translated into [Altair](http://altair-viz.github.io/) or [Vega-Lite](https://vega.github.io/vega-lite/) code, so that they can be further edited.
+Now that we have found some interesting visualizations through Lux, we might be interested in digging into these visualizations a bit more or sharing it with others. We can save the visualizations generated in Lux as a [static, shareable HTML](https://lux-api.readthedocs.io/en/latest/source/guide/export.html#exporting-widget-visualizations-as-static-html) or programmatically access these visualizations further in Jupyter. Selected `Vis` objects can be translated into [Altair](http://altair-viz.github.io/), [Matplotlib](https://matplotlib.org/), or [Vega-Lite](https://vega.github.io/vega-lite/) code, so that they can be further edited.
 
 <img src="https://github.com/lux-org/lux-resources/blob/master/readme_img/export.gif?raw=true"
      alt="Easily exportable visualization object"
@@ -117,7 +118,7 @@ Vis(["Region=New England","MedianEarnings"],df)
 
 Lux provides a powerful abstraction for working with collections of visualizations based on a partially specified queries. Users can provide a list or a wildcard to iterate over combinations of filter or attribute values and quickly browse through large numbers of visualizations. The partial specification is inspired by existing work on visualization query languages, including [ZQL](https://github.com/vega/compassql) and [CompassQL](https://github.com/vega/compassql).
 
-For example, we might be interested in looking at how the `AverageCost` distribution differs across different `Region`s.
+For example, we are interested in how the `AverageCost` distribution differs across different `Region`s.
 
 ```python    
 from lux.vis.VisList import VisList
@@ -134,7 +135,7 @@ To find out more about other features in Lux, see the complete documentation on
 # Installation & Setup
 
 To get started, please follow both the installation and setup instructions in your command line.
-`lux-api` can be installed through [PyPI](https://pypi.org/project/lux-api/). 
+`lux-api` can be installed through [PyPI](https://pypi.org/project/lux-api/) or [conda-forge](https://github.com/conda-forge/lux-api-feedstock). 
 
 ```bash
 pip install lux-api
@@ -162,13 +163,13 @@ Note that you may have to restart the Jupyter Notebook server to ensure that the
 
 ## Setup in Jupyter Lab
 
-To use Lux in [Jupyter Lab](https://github.com/jupyterlab/jupyterlab), activate the lab extension:
+Lux is compatible with both Jupyter Lab version 2 and 3. To use Lux in [Jupyter Lab](https://github.com/jupyterlab/jupyterlab), activate the lab extension:
 
 ```bash
 jupyter labextension install @jupyter-widgets/jupyterlab-manager
 jupyter labextension install luxwidget
 ```
-Lux is only compatible with Jupyter Lab version 2.2.9 and below. Support for the recent [JupyterLab 3](https://blog.jupyter.org/jupyterlab-3-0-is-out-4f58385e25bb) will come soon. Note that JupyterLab and VSCode is supported only for lux-widget version >=0.1.2, if you have an earlier version, please upgrade to the latest version of [lux-widget](https://pypi.org/project/lux-widget/). Lux has only been tested with the Chrome browser. 
+Note that JupyterLab and VSCode is supported only for lux-widget version >=0.1.2, if you have an earlier version, please upgrade to the latest version of [lux-widget](https://pypi.org/project/lux-widget/). Lux has only been tested with the Chrome browser. 
 
 If you encounter issues with the installation, please refer to [this page](https://lux-api.readthedocs.io/en/latest/source/guide/FAQ.html#troubleshooting-tips) to troubleshoot the installation. Follow [these instructions](https://lux-api.readthedocs.io/en/latest/source/getting_started/installation.html#manual-installation-dev-setup) to set up Lux for development purposes.
 
diff --git a/doc/source/advanced/date.rst b/doc/source/advanced/date.rst
index 6ecc9c69..f47f8c3e 100644
--- a/doc/source/advanced/date.rst
+++ b/doc/source/advanced/date.rst
@@ -89,6 +89,22 @@ After changing the Pandas data type to datetime, we see that date field is recog
   :align: center
   :alt: add screenshot
 
+Visualizing Trends across Different Timescales
+----------------------------------------------
+
+Lux automatically detects the temporal attribute and plots the visualizations across different timescales to showcase any cyclical patterns. Here, we see that the `Temporal` tab displays the yearly, monthly, and weekly trends for the number of stock records.
+
+.. code-block:: python
+
+  from vega_datasets import data
+  df = data.stocks()
+
+  df.recommendation["Temporal"]
+
+.. image:: https://github.com/lux-org/lux-resources/blob/master/doc_img/date-12.png?raw=true
+  :width: 600
+  :align: center
+
 Advanced Date Manipulation
 --------------------------
 
diff --git a/doc/source/advanced/executor.rst b/doc/source/advanced/executor.rst
index 071affbb..90abd92f 100644
--- a/doc/source/advanced/executor.rst
+++ b/doc/source/advanced/executor.rst
@@ -42,7 +42,7 @@ To do this, users first need to specify a connection to their SQL database. This
 	engine = create_engine("postgresql://postgres:lux@localhost:5432")
 
 Note that users will have to install these packages on their own if they want to connect Lux to their databases.
-Once this connection is created, users can connect the lux config to the database using the :code:`set_SQL_connection` command.
+Once this connection is created, users can connect Lux to their database using the :code:`set_SQL_connection` command.
 
 .. code-block:: python
 
@@ -54,7 +54,6 @@ Connecting a LuxSQLTable to a Table/View
 ----------------------------------------
 
 LuxSQLTables can be connected to individual tables or views created within your Postgresql database. This can be done by specifying the table or view name in the constructor. 
-.. We are actively working on supporting joins between multiple tables. But as of now, the functionality is limited to one table or view per LuxSQLTable object only.
 
 .. code-block:: python
 
diff --git a/doc/source/reference/gallery.rst b/doc/source/reference/gallery.rst
new file mode 100644
index 00000000..d7fd7910
--- /dev/null
+++ b/doc/source/reference/gallery.rst
@@ -0,0 +1,61 @@
+
+Gallery of Notebook Examples
+==============================
+
+Demo Examples
+------------------
+The following notebooks demonstrates how Lux can be used with a variety of different datasets and analysis
+
+- Basic walkthrough of Lux with the Cars dataset [`Source <https://github.com/lux-org/lux-binder/blob/master/demo/cars_demo.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/demo/cars_demo.ipynb>`_]
+- Basic walkthrough of Lux with the College dataset [`Source <https://github.com/lux-org/lux-binder/blob/master/demo/college_demo.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/demo/college_demo.ipynb>`_]
+- Understanding Global COVID-19 intervention levels [`Source <https://github.com/lux-org/lux-binder/blob/master/demo/hpi_covid_demo.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/demo/hpi_covid_demo.ipynb>`_]
+- Understanding factors leading to Employee attrition [`Source <https://github.com/lux-org/lux-binder/blob/master/demo/employee_demo.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/demo/employee_demo.ipynb>`_]
+
+If you would like to try out Lux on your own, we provide a `notebook environment <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/demo/exercise/4-Data-Playground.ipynb>`_ for you to play around with several provided example datasets.
+
+Tutorials
+---------
+
+The following tutorials cover the most basic to advanced features in Lux. The first five tutorials introduces the core features in Lux:
+
+- Overview of Features in Lux [`Source <https://github.com/lux-org/lux-binder/blob/master/tutorial/0-overview.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/tutorial/0-overview.ipynb>`_]
+- Specifying user analysis intent in Lux [`Source <https://github.com/lux-org/lux-binder/blob/master/tutorial/1-specifying-intent.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/tutorial/1-specifying-intent.ipynb>`_]
+- Creating quick-and-dirty visualizations with :code:`Vis` and :code:`VisList` [`Source <https://github.com/lux-org/lux-binder/blob/master/tutorial/2-constructing-vis-and-vislist.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/tutorial/2-constructing-vis-and-vislist.ipynb>`_]
+- Seamless export of :code:`Vis` from notebook to downstream tasks [`Source <https://github.com/lux-org/lux-binder/blob/master/tutorial/3-widget-vis-export.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/tutorial/3-widget-vis-export.ipynb>`_]
+- Customizing plot styling with :code:`lux.config.plotting_style` [`Source <https://github.com/lux-org/lux-binder/blob/master/tutorial/4-chart-settings.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/tutorial/4-chart-settings.ipynb>`_]
+
+The following notebooks covers more advanced topics in Lux: 
+
+- Understanding data types in Lux [`Source <https://github.com/lux-org/lux-binder/blob/master/tutorial/9-datatype.ipynb?>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/tutorial/9-datatype.ipynb?raw=true>`_]
+- Working with datetime columns [`Source <https://github.com/lux-org/lux-binder/blob/master/tutorial/tutorial/5-datetime.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/tutorial/5-datetime.ipynb>`_] 
+- Working with geographic columns [`ReadTheDocs <https://lux-api.readthedocs.io/en/latest/source/advanced/map.html>`_] 
+- Working with dataframe indexes [`Source <https://github.com/lux-org/lux-binder/blob/master/tutorial/tutorial/6-index-group.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/tutorial/6-index-group.ipynb>`_]
+- Registering custom recommendation actions [`Source <https://github.com/lux-org/lux-binder/blob/master/tutorial/8-custom-action.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/tutorial/8-custom-action.ipynb>`_]
+- Using Lux with a SQL Database [`Source <https://github.com/lux-org/lux-binder-sql/blob/master/notebooks/Using%20Lux%20with%20SQL%20Databases.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder-sql/HEAD>`_]
+
+
+Exercise
+---------
+
+Here are some teaching resources on Lux. The materials are suited for a 1-1.5 hour industry bootcamp or lecture for a data visualization or data science course. Here is a `video <https://www.youtube.com/watch?v=YANIids_Nkk>`_ that walks through these hands-on exercise on Lux. To follow along, check out the instructions `here <https://github.com/lux-org/lux-binder>`_.
+
+1. How to specify your analysis interests as `intents` in Lux? [`Source <https://github.com/lux-org/lux-binder/blob/master/exercise/1-Specify-Intent.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/exercise/1-Specify-Intent.ipynb>`_]
+
+2. How to create visualizations using :code:`Vis` and :code:`VisList`? [`Source <https://github.com/lux-org/lux-binder/blob/master/exercise/2-Quick-Vis.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/exercise/2-Quick-Vis.ipynb>`_]
+
+3. How to export selected visualization(s) out of the notebook for sharing? [`Source <https://github.com/lux-org/lux-binder/blob/master/exercise/3-Export-Widget.ipynb>`_] [`Live Notebook <https://mybinder.org/v2/gh/lux-org/lux-binder/master?urlpath=tree/exercise/3-Export-Widget.ipynb>`_]
+
+Here are the `solutions <https://github.com/lux-org/lux-binder/blob/master/exercise/solution.txt>`_ to the notebook exercises.
+
+
+Community Contributions
+-------------------------
+Here are some awesome articles and tutorials written by the Lux community: 
+
+- `Exploring the Penguins dataset with Lux by Parul Pandey <https://towardsdatascience.com/intelligent-visual-data-discovery-with-lux-a-python-library-dc36a5742b2f>`_
+- `Analysis of the Wine dataset by Domino Data Lab <https://blog.dominodatalab.com/faster-data-exploration-in-jupyter-through-the-lux-widget/>`_
+- `Quick Recommendation-Based Data Exploration with Lux by Cornellius Yudha Wijaya <https://towardsdatascience.com/quick-recommendation-based-data-exploration-with-lux-f4d0ccb68133>`_
+- `Analyzing the Graduate Admissions dataset with Lux by Pranavi Duvva <https://pub.towardsai.net/speed-up-eda-with-the-intelligent-lux-37f96542527b>`_
+
+
+If you would like your notebook or articles to be featured here, please submit a pull request `here <https://github.com/lux-org/lux-binder>`_ to let us know!
\ No newline at end of file
diff --git a/doc/source/reference/gen/lux.core.frame.LuxDataFrame.rst b/doc/source/reference/gen/lux.core.frame.LuxDataFrame.rst
index 58fb4652..940caa39 100644
--- a/doc/source/reference/gen/lux.core.frame.LuxDataFrame.rst
+++ b/doc/source/reference/gen/lux.core.frame.LuxDataFrame.rst
@@ -41,6 +41,7 @@
       ~LuxDataFrame.combine
       ~LuxDataFrame.combine_first
       ~LuxDataFrame.compare
+      ~LuxDataFrame.compute_metadata
       ~LuxDataFrame.convert_dtypes
       ~LuxDataFrame.copy
       ~LuxDataFrame.copy_intent
@@ -176,6 +177,7 @@
       ~LuxDataFrame.set_intent_as_vis
       ~LuxDataFrame.set_intent_on_click
       ~LuxDataFrame.shift
+      ~LuxDataFrame.show_all_column_vis
       ~LuxDataFrame.skew
       ~LuxDataFrame.slice_shift
       ~LuxDataFrame.sort_index
diff --git a/doc/source/reference/gen/lux.executor.Executor.Executor.rst b/doc/source/reference/gen/lux.executor.Executor.Executor.rst
index c45473c6..9a0dbbc5 100644
--- a/doc/source/reference/gen/lux.executor.Executor.Executor.rst
+++ b/doc/source/reference/gen/lux.executor.Executor.Executor.rst
@@ -1,4 +1,4 @@
-lux.executor.Executor.Executor
+﻿lux.executor.Executor.Executor
 ==============================
 
 .. currentmodule:: lux.executor.Executor
@@ -19,6 +19,7 @@ lux.executor.Executor.Executor
       ~Executor.compute_data_type
       ~Executor.compute_stats
       ~Executor.execute
+      ~Executor.execute_2D_binning
       ~Executor.execute_aggregate
       ~Executor.execute_binning
       ~Executor.execute_filter
diff --git a/doc/source/reference/gen/lux.executor.PandasExecutor.PandasExecutor.rst b/doc/source/reference/gen/lux.executor.PandasExecutor.PandasExecutor.rst
index a65b633e..c464971e 100644
--- a/doc/source/reference/gen/lux.executor.PandasExecutor.PandasExecutor.rst
+++ b/doc/source/reference/gen/lux.executor.PandasExecutor.PandasExecutor.rst
@@ -1,4 +1,4 @@
-lux.executor.PandasExecutor.PandasExecutor
+﻿lux.executor.PandasExecutor.PandasExecutor
 ==========================================
 
 .. currentmodule:: lux.executor.PandasExecutor
@@ -23,6 +23,7 @@ lux.executor.PandasExecutor.PandasExecutor
       ~PandasExecutor.execute
       ~PandasExecutor.execute_2D_binning
       ~PandasExecutor.execute_aggregate
+      ~PandasExecutor.execute_approx_sample
       ~PandasExecutor.execute_binning
       ~PandasExecutor.execute_filter
       ~PandasExecutor.execute_sampling
diff --git a/lux/_config/config.py b/lux/_config/config.py
index 4b9bacc7..ed598037 100644
--- a/lux/_config/config.py
+++ b/lux/_config/config.py
@@ -25,16 +25,26 @@ def __init__(self):
         # flags whether or not an action has been registered or removed and should be re-rendered by frame.py
         self.update_actions: Dict[str, bool] = {}
         self.update_actions["flag"] = False
-        self._sampling_start = 10000
-        self._sampling_cap = 30000
-        self._sampling_flag = True
-        self._heatmap_flag = True
         self._plotting_backend = "vegalite"
         self._topk = 15
         self._sort = "descending"
         self._pandas_fallback = True
         self._interestingness_fallback = True
         self.heatmap_bin_size = 40
+        #####################################
+        #### Optimization Configurations ####
+        #####################################
+        self._sampling_start = 100000
+        self._sampling_cap = 1000000
+        self._sampling_flag = True
+        self._heatmap_flag = True
+        self.lazy_maintain = True
+        self.early_pruning = True
+        self.early_pruning_sample_cap = 30000
+        # Apply sampling only if the dataset is 150% larger than the sample cap
+        self.early_pruning_sample_start = self.early_pruning_sample_cap * 1.5
+        self.streaming = False
+        self.render_widget = True
 
     @property
     def topk(self):
diff --git a/lux/_version.py b/lux/_version.py
index 71ffb3e0..01dd139d 100644
--- a/lux/_version.py
+++ b/lux/_version.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
 # coding: utf-8
 
-version_info = (0, 2, 3)
+version_info = (0, 3, 0)
 __version__ = ".".join(map(str, version_info))
diff --git a/lux/action/default.py b/lux/action/default.py
index e8c2e5c9..30b61644 100644
--- a/lux/action/default.py
+++ b/lux/action/default.py
@@ -6,6 +6,7 @@ def register_default_actions():
     from lux.action.enhance import enhance
     from lux.action.filter import add_filter
     from lux.action.generalize import generalize
+    from lux.action.temporal import temporal
 
     # display conditions for default actions
     no_vis = lambda ldf: (ldf.current_vis is None) or (
@@ -18,7 +19,7 @@ def register_default_actions():
     lux.config.register_action("correlation", correlation, no_vis)
     lux.config.register_action("distribution", univariate, no_vis, "quantitative")
     lux.config.register_action("occurrence", univariate, no_vis, "nominal")
-    lux.config.register_action("temporal", univariate, no_vis, "temporal")
+    lux.config.register_action("temporal", temporal, no_vis)
     lux.config.register_action("geographical", univariate, no_vis, "geographical")
 
     lux.config.register_action("Enhance", enhance, one_current_vis)
diff --git a/lux/action/enhance.py b/lux/action/enhance.py
index be3cd290..0f469543 100644
--- a/lux/action/enhance.py
+++ b/lux/action/enhance.py
@@ -52,7 +52,7 @@ def enhance(ldf):
             "long_description": f"Enhance adds an additional attribute as the color to break down the {intended_attrs} distribution",
         }
     # if there are too many column attributes, return don't generate Enhance recommendations
-    elif len(attr_specs) > 2:
+    else:
         recommendation = {"action": "Enhance"}
         recommendation["collection"] = []
         return recommendation
diff --git a/lux/action/filter.py b/lux/action/filter.py
index 44b1019a..5b6c2f1e 100644
--- a/lux/action/filter.py
+++ b/lux/action/filter.py
@@ -39,7 +39,7 @@ def add_filter(ldf):
     filter_values = []
     output = []
     # if fltr is specified, create visualizations where data is filtered by all values of the fltr's categorical variable
-    column_spec = utils.get_attrs_specs(ldf.current_vis[0].intent)
+    column_spec = utils.get_attrs_specs(ldf._intent)
     column_spec_attr = list(map(lambda x: x.attribute, column_spec))
     if len(filters) == 1:
         # get unique values for all categorical values specified and creates corresponding filters
diff --git a/lux/action/temporal.py b/lux/action/temporal.py
new file mode 100644
index 00000000..6bf831ce
--- /dev/null
+++ b/lux/action/temporal.py
@@ -0,0 +1,128 @@
+#  Copyright 2019-2020 The Lux Authors.
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import lux
+from lux.vis.VisList import VisList
+from lux.vis.Vis import Vis
+import pandas as pd
+from lux.core.frame import LuxDataFrame
+from lux.interestingness.interestingness import interestingness
+from lux.utils import utils
+
+
+def temporal(ldf):
+    """
+    Generates line charts for temporal fields at different granularities.
+    Parameters
+    ----------
+    ldf : lux.core.frame
+            LuxDataFrame with underspecified intent.
+    Returns
+    -------
+    recommendations : Dict[str,obj]
+            Object with a collection of visualizations that result from the Temporal action.
+    """
+    vlist = []
+    recommendation = {
+        "action": "Temporal",
+        "description": "Show trends over <p class='highlight-descriptor'>time-related</p> attributes.",
+        "long_description": "Temporal displays line charts for all attributes related to datetimes in the dataframe.",
+    }
+    for c in ldf.columns:
+        if ldf.data_type[c] == "temporal":
+            try:
+                generated_vis = create_temporal_vis(ldf, c)
+                vlist.extend(generated_vis)
+            except:
+                pass
+
+    # If no temporal visualizations were generated via parsing datetime, fallback to default behavior.
+    if len(vlist) == 0:
+        intent = [lux.Clause("?", data_type="temporal")]
+        intent.extend(utils.get_filter_specs(ldf._intent))
+        vlist = VisList(intent, ldf)
+        for vis in vlist:
+            vis.score = interestingness(vis, ldf)
+    else:
+        vlist = VisList(vlist)
+        recommendation["long_description"] += (
+            " Lux displays the overall temporal trend first,"
+            + " followed by trends across other timescales (e.g., year, month, week, day)."
+        )
+
+    # Doesn't make sense to generate a line chart if there is less than 3 datapoints (pre-aggregated)
+    if len(ldf) < 3:
+        recommendation["collection"] = []
+        return recommendation
+    vlist.sort()
+    recommendation["collection"] = vlist
+    return recommendation
+
+
+def create_temporal_vis(ldf, col):
+    """
+    Creates and populates Vis objects for different timescales in the provided temporal column.
+    Parameters
+    ----------
+    ldf : lux.core.frame
+            LuxDataFrame with underspecified intent.
+
+    col : str
+            Name of temporal column.
+
+    Returns
+    -------
+    vlist : [Vis]
+            Collection of Vis objects.
+    """
+    formatted_date = pd.to_datetime(ldf[col], format="%Y-%m-%d")
+
+    overall_vis = Vis([lux.Clause(col, data_type="temporal")], source=ldf, score=5)
+
+    year_col = col + " (year)"
+    year_df = LuxDataFrame({year_col: pd.to_datetime(formatted_date.dt.year, format="%Y")})
+    year_vis = Vis([lux.Clause(year_col, data_type="temporal")], source=year_df, score=4)
+
+    month_col = col + " (month)"
+    month_df = LuxDataFrame({month_col: formatted_date.dt.month})
+    month_vis = Vis(
+        [lux.Clause(month_col, data_type="temporal", timescale="month")], source=month_df, score=3
+    )
+
+    day_col = col + " (day)"
+    day_df = LuxDataFrame({day_col: formatted_date.dt.day})
+    day_df.set_data_type(
+        {day_col: "nominal"}
+    )  # Since day is high cardinality 1-31, it can get recognized as quantitative
+    day_vis = Vis([lux.Clause(day_col, data_type="temporal", timescale="day")], source=day_df, score=2)
+
+    week_col = col + " (day of week)"
+    week_df = lux.LuxDataFrame({week_col: formatted_date.dt.dayofweek})
+    week_vis = Vis(
+        [lux.Clause(week_col, data_type="temporal", timescale="day of week")], source=week_df, score=1
+    )
+
+    unique_year_values = len(year_df[year_col].unique())
+    unique_month_values = len(month_df[month_col].unique())
+    unique_week_values = len(week_df[week_col].unique())
+    vlist = []
+
+    vlist.append(overall_vis)
+    if unique_year_values != 1:
+        vlist.append(year_vis)
+    if unique_month_values != 1:
+        vlist.append(month_vis)
+    if unique_week_values != 1:
+        vlist.append(week_vis)
+    return vlist
diff --git a/lux/action/univariate.py b/lux/action/univariate.py
index e94af9bd..1e6d357e 100644
--- a/lux/action/univariate.py
+++ b/lux/action/univariate.py
@@ -89,17 +89,6 @@ def univariate(ldf, *args):
             "description": "Show choropleth maps of <p class='highlight-descriptor'>geographic</p> attributes",
             "long_description": f"Occurence displays choropleths of averages for some geographic attribute{examples}. Visualizations are ranked by diversity of the geographic attribute.",
         }
-    elif data_type_constraint == "temporal":
-        intent = [lux.Clause("?", data_type="temporal")]
-        intent.extend(filter_specs)
-        recommendation = {
-            "action": "Temporal",
-            "description": "Show trends over <p class='highlight-descriptor'>time-related</p> attributes.",
-            "long_description": "Temporal displays line charts for all attributes related to datetimes in the dataframe.",
-        }
-        # Doesn't make sense to generate a line chart if there is less than 3 datapoints (pre-aggregated)
-        if len(ldf) < 3:
-            ignore_rec_flag = True
     if ignore_rec_flag:
         recommendation["collection"] = []
         return recommendation
diff --git a/lux/core/frame.py b/lux/core/frame.py
index 8d291b9c..98fb8fee 100644
--- a/lux/core/frame.py
+++ b/lux/core/frame.py
@@ -79,6 +79,7 @@ def __init__(self, *args, **kw):
             lux.config.executor = SQLExecutor()
 
         self._sampled = None
+        self._approx_sample = None
         self._toggle_pandas_display = True
         self._message = Message()
         self._pandas_only = False
@@ -115,47 +116,56 @@ def data_type(self):
             self.maintain_metadata()
         return self._data_type
 
-    def maintain_metadata(self):
+    def compute_metadata(self) -> None:
+        """
+        Compute dataset metadata and statistics
+        """
+        if len(self) > 0:
+            if lux.config.executor.name != "SQLExecutor":
+                lux.config.executor.compute_stats(self)
+            lux.config.executor.compute_dataset_metadata(self)
+            self._infer_structure()
+            self._metadata_fresh = True
+
+    def maintain_metadata(self) -> None:
+        """
+        Maintain dataset metadata and statistics (Compute only if needed)
+        """
         is_sql_tbl = lux.config.executor.name == "SQLExecutor"
         if lux.config.SQLconnection != "" and is_sql_tbl:
             from lux.executor.SQLExecutor import SQLExecutor
 
             lux.config.executor = SQLExecutor()
+        if lux.config.lazy_maintain:
+            # Check that metadata has not yet been computed
+            if not hasattr(self, "_metadata_fresh") or not self._metadata_fresh:
+                # only compute metadata information if the dataframe is non-empty
+                self.compute_metadata()
+        else:
+            self.compute_metadata()
 
-        # Check that metadata has not yet been computed
-        if not hasattr(self, "_metadata_fresh") or not self._metadata_fresh:
-            # only compute metadata information if the dataframe is non-empty
-            if is_sql_tbl:
-                lux.config.executor.compute_dataset_metadata(self)
-                self._infer_structure()
-                self._metadata_fresh = True
-            else:
-                if len(self) > 0:
-                    lux.config.executor.compute_stats(self)
-                    lux.config.executor.compute_dataset_metadata(self)
-                    self._infer_structure()
-                    self._metadata_fresh = True
-
-    def expire_recs(self):
+    def expire_recs(self) -> None:
         """
         Expires and resets all recommendations
         """
-        self._recs_fresh = False
-        self._recommendation = {}
-        self._widget = None
-        self._rec_info = None
-        self._sampled = None
+        if lux.config.lazy_maintain:
+            self._recs_fresh = False
+            self._recommendation = {}
+            self._widget = None
+            self._rec_info = None
+            self._sampled = None
 
-    def expire_metadata(self):
+    def expire_metadata(self) -> None:
         """
         Expire all saved metadata to trigger a recomputation the next time the data is required.
         """
-        self._metadata_fresh = False
-        self._data_type = None
-        self.unique_values = None
-        self.cardinality = None
-        self._min_max = None
-        self.pre_aggregated = None
+        if lux.config.lazy_maintain:
+            self._metadata_fresh = False
+            self._data_type = None
+            self.unique_values = None
+            self.cardinality = None
+            self._min_max = None
+            self.pre_aggregated = None
 
     #####################
     ## Override Pandas ##
@@ -344,12 +354,20 @@ def _append_rec(self, rec_infolist, recommendations: Dict):
         if recommendations["collection"] is not None and len(recommendations["collection"]) > 0:
             rec_infolist.append(recommendations)
 
+    def show_all_column_vis(self):
+        if self.intent == [] or self.intent is None:
+            vis = Vis(list(self.columns), self)
+            if vis.mark != "":
+                vis._all_column = True
+                self.current_vis = VisList([vis])
+
     def maintain_recs(self, is_series="DataFrame"):
         # `rec_df` is the dataframe to generate the recommendations on
         # check to see if globally defined actions have been registered/removed
         if lux.config.update_actions["flag"] == True:
             self._recs_fresh = False
         show_prev = False  # flag indicating whether rec_df is showing previous df or current self
+
         if self._prev is not None:
             rec_df = self._prev
             rec_df._message = Message()
@@ -387,8 +405,14 @@ def maintain_recs(self, is_series="DataFrame"):
 
         rec_df._prev = None  # reset _prev
 
+        # If lazy, check that recs has not yet been computed
+        lazy_but_not_computed = lux.config.lazy_maintain and (
+            not hasattr(rec_df, "_recs_fresh") or not rec_df._recs_fresh
+        )
+        eager = not lux.config.lazy_maintain
+
         # Check that recs has not yet been computed
-        if not hasattr(rec_df, "_recs_fresh") or not rec_df._recs_fresh:
+        if lazy_but_not_computed or eager:
             is_sql_tbl = lux.config.executor.name == "SQLExecutor"
             rec_infolist = []
             from lux.action.row_group import row_group
@@ -418,10 +442,14 @@ def maintain_recs(self, is_series="DataFrame"):
                 if len(vlist) > 0:
                     rec_df._recommendation[action_type] = vlist
             rec_df._rec_info = rec_infolist
-            self._widget = rec_df.render_widget()
+            rec_df.show_all_column_vis()
+            if lux.config.render_widget:
+                self._widget = rec_df.render_widget()
         # re-render widget for the current dataframe if previous rec is not recomputed
         elif show_prev:
-            self._widget = rec_df.render_widget()
+            rec_df.show_all_column_vis()
+            if lux.config.render_widget:
+                self._widget = rec_df.render_widget()
         self._recs_fresh = True
 
     #######################################################
@@ -697,6 +725,10 @@ def current_vis_to_JSON(vlist, input_current_vis=""):
             current_vis_spec = vlist[0].to_code(language=lux.config.plotting_backend, prettyOutput=False)
         elif numVC > 1:
             pass
+        if vlist[0]._all_column:
+            current_vis_spec["allcols"] = True
+        else:
+            current_vis_spec["allcols"] = False
         return current_vis_spec
 
     @staticmethod
diff --git a/lux/core/series.py b/lux/core/series.py
index 2b902730..75e7602d 100644
--- a/lux/core/series.py
+++ b/lux/core/series.py
@@ -105,6 +105,25 @@ def to_pandas(self) -> pd.Series:
 
         return lux.core.originalSeries(self, copy=False)
 
+    def unique(self):
+        """
+        Overridden method for pd.Series.unique with cached results.
+        Return unique values of Series object.
+        Uniques are returned in order of appearance. Hash table-based unique,
+        therefore does NOT sort.
+        Returns
+        -------
+        ndarray or ExtensionArray
+            The unique values returned as a NumPy array.
+        See Also
+        --------
+        https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.unique.html
+        """
+        if self.unique_values and self.name in self.unique_values.keys():
+            return np.array(self.unique_values[self.name])
+        else:
+            return super(LuxSeries, self).unique()
+
     def _ipython_display_(self):
         from IPython.display import display
         from IPython.display import clear_output
diff --git a/lux/executor/Executor.py b/lux/executor/Executor.py
index 563b0e7f..b430304f 100644
--- a/lux/executor/Executor.py
+++ b/lux/executor/Executor.py
@@ -11,8 +11,9 @@
 #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
-
+from lux.core.frame import LuxDataFrame
 from lux.vis.VisList import VisList
+from lux.vis.Vis import Vis
 from lux.utils import utils
 
 
@@ -28,19 +29,38 @@ def __repr__(self):
         return f"<Executor>"
 
     @staticmethod
-    def execute(vis_collection: VisList, ldf):
+    def execute(vislist: VisList, ldf: LuxDataFrame, approx: bool = False):
+        """
+        Given a VisList, fetch the data required to render the vis.
+        """
         return NotImplemented
 
     @staticmethod
-    def execute_aggregate(vis, ldf):
+    def execute_aggregate(vis: Vis, ldf: LuxDataFrame):
+        """
+        Aggregate data points on an axis for bar or line charts
+        """
         return NotImplemented
 
     @staticmethod
-    def execute_binning(vis, ldf):
+    def execute_binning(ldf: LuxDataFrame, vis: Vis):
+        """
+        Binning of data points for generating histograms
+        """
         return NotImplemented
 
     @staticmethod
-    def execute_filter(vis, ldf):
+    def execute_filter(vis: Vis):
+        """
+        Apply a Vis's filter to vis.data
+        """
+        return NotImplemented
+
+    @staticmethod
+    def execute_2D_binning(vis: Vis):
+        """
+        Apply 2D binning (heatmap) to vis.data
+        """
         return NotImplemented
 
     @staticmethod
@@ -51,10 +71,6 @@ def compute_stats(self):
     def compute_data_type(self):
         return NotImplemented
 
-    # @staticmethod
-    # def compute_data_model(self):
-    #     return NotImplemented
-
     def mapping(self, rmap):
         group_map = {}
         for val in ["quantitative", "id", "nominal", "temporal", "geographical"]:
diff --git a/lux/executor/PandasExecutor.py b/lux/executor/PandasExecutor.py
index 4ecfb675..71872096 100644
--- a/lux/executor/PandasExecutor.py
+++ b/lux/executor/PandasExecutor.py
@@ -38,7 +38,19 @@ def __repr__(self):
 
     @staticmethod
     def execute_sampling(ldf: LuxDataFrame):
-        # General Sampling for entire dataframe
+        """
+        Compute and cache a sample for the overall dataframe
+
+        - When # of rows exceeds lux.config.sampling_start, take 75% df as sample
+        - When # of rows exceeds lux.config.sampling_cap, cap the df at {lux.config.sampling_cap} rows
+
+        lux.config.sampling_start = 100k rows
+        lux.config.sampling_cap = 1M rows
+
+        Parameters
+        ----------
+        ldf : LuxDataFrame
+        """
         SAMPLE_FLAG = lux.config.sampling
         SAMPLE_START = lux.config.sampling_start
         SAMPLE_CAP = lux.config.sampling_cap
@@ -48,21 +60,39 @@ def execute_sampling(ldf: LuxDataFrame):
             if ldf._sampled is None:  # memoize unfiltered sample df
                 ldf._sampled = ldf.sample(n=SAMPLE_CAP, random_state=1)
             ldf._message.add_unique(
-                f"Large dataframe detected: Lux is only visualizing a random sample capped at {SAMPLE_CAP} rows.",
+                f"Large dataframe detected: Lux is only visualizing a sample capped at {SAMPLE_CAP} rows.",
                 priority=99,
             )
         elif SAMPLE_FLAG and len(ldf) > SAMPLE_START:
             if ldf._sampled is None:  # memoize unfiltered sample df
                 ldf._sampled = ldf.sample(frac=SAMPLE_FRAC, random_state=1)
             ldf._message.add_unique(
-                f"Large dataframe detected: Lux is only visualizing a random sample of {len(ldf._sampled)} rows.",
+                f"Large dataframe detected: Lux is visualizing a sample of {SAMPLE_FRAC}% of the dataframe ({len(ldf._sampled)} rows).",
                 priority=99,
             )
         else:
             ldf._sampled = ldf
 
     @staticmethod
-    def execute(vislist: VisList, ldf: LuxDataFrame):
+    def execute_approx_sample(ldf: LuxDataFrame):
+        """
+        Compute and cache an approximate sample of the overall dataframe
+        for the purpose of early pruning of the visualization search space
+
+        Parameters
+        ----------
+        ldf : LuxDataFrame
+        """
+        if ldf._approx_sample is None:
+            if len(ldf._sampled) > lux.config.early_pruning_sample_start:
+                ldf._approx_sample = ldf._sampled.sample(
+                    n=lux.config.early_pruning_sample_cap, random_state=1
+                )
+            else:
+                ldf._approx_sample = ldf._sampled
+
+    @staticmethod
+    def execute(vislist: VisList, ldf: LuxDataFrame, approx=False):
         """
         Given a VisList, fetch the data required to render the vis.
         1) Apply filters
@@ -85,6 +115,12 @@ def execute(vislist: VisList, ldf: LuxDataFrame):
         for vis in vislist:
             # The vis data starts off being original or sampled dataframe
             vis._vis_data = ldf._sampled
+            # Approximating vis for early pruning
+            if approx:
+                vis._original_df = vis._vis_data
+                PandasExecutor.execute_approx_sample(ldf)
+                vis._vis_data = ldf._approx_sample
+                vis.approx = True
             filter_executed = PandasExecutor.execute_filter(vis)
             # Select relevant data based on attribute information
             attributes = set([])
@@ -98,16 +134,14 @@ def execute(vislist: VisList, ldf: LuxDataFrame):
                 PandasExecutor.execute_aggregate(vis, isFiltered=filter_executed)
             elif vis.mark == "histogram":
                 PandasExecutor.execute_binning(ldf, vis)
-            elif vis.mark == "scatter":
-                HBIN_START = 5000
-                if lux.config.heatmap and len(ldf) > HBIN_START:
-                    vis._postbin = True
-                    ldf._message.add_unique(
-                        f"Large scatterplots detected: Lux is automatically binning scatterplots to heatmaps.",
-                        priority=98,
-                    )
-                    # vis._mark = "heatmap"
-                    # PandasExecutor.execute_2D_binning(vis) # Lazy Evaluation (Early pruning based on interestingness)
+            elif vis.mark == "heatmap":
+                # Early pruning based on interestingness of scatterplots
+                if approx:
+                    vis._mark = "scatter"
+                else:
+                    vis._mark = "heatmap"
+                    PandasExecutor.execute_2D_binning(vis)
+            vis.data.clear_intent()  # Ensure that intent is not propogated to the vis data
 
     @staticmethod
     def execute_aggregate(vis: Vis, isFiltered=True):
@@ -259,7 +293,7 @@ def execute_aggregate(vis: Vis, isFiltered=True):
             vis._vis_data = vis._vis_data.drop(columns="index")
 
     @staticmethod
-    def execute_binning(ldf, vis: Vis):
+    def execute_binning(ldf: LuxDataFrame, vis: Vis):
         """
         Binning of data points for generating histograms
 
@@ -296,7 +330,19 @@ def execute_binning(ldf, vis: Vis):
         vis._vis_data = pd.DataFrame(binned_result, columns=[bin_attr, "Number of Records"])
 
     @staticmethod
-    def execute_filter(vis: Vis):
+    def execute_filter(vis: Vis) -> bool:
+        """
+        Apply a Vis's filter to vis.data
+
+        Parameters
+        ----------
+        vis : Vis
+
+        Returns
+        -------
+        bool
+            Boolean flag indicating if any filter was applied
+        """
         assert (
             vis.data is not None
         ), "execute_filter assumes input vis.data is populated (if not, populate with LuxDataFrame values)"
@@ -358,7 +404,14 @@ def apply_filter(df: pd.DataFrame, attribute: str, op: str, val: object) -> pd.D
         return df
 
     @staticmethod
-    def execute_2D_binning(vis: Vis):
+    def execute_2D_binning(vis: Vis) -> None:
+        """
+        Apply 2D binning (heatmap) to vis.data
+
+        Parameters
+        ----------
+        vis : Vis
+        """
         pd.reset_option("mode.chained_assignment")
         with pd.option_context("mode.chained_assignment", None):
             x_attr = vis.get_attr_by_channel("x")[0].attribute
diff --git a/lux/executor/SQLExecutor.py b/lux/executor/SQLExecutor.py
index a2061d11..c8917c69 100644
--- a/lux/executor/SQLExecutor.py
+++ b/lux/executor/SQLExecutor.py
@@ -48,7 +48,7 @@ def execute_sampling(tbl: LuxSQLTable):
         )
 
     @staticmethod
-    def execute(view_collection: VisList, tbl: LuxSQLTable):
+    def execute(view_collection: VisList, tbl: LuxSQLTable, approx: bool = False):
         """
         Given a VisList, fetch the data required to render the view
         1) Generate Necessary WHERE clauses
@@ -134,12 +134,6 @@ def add_quotes(var_name):
             query = "SELECT {} FROM {} {}".format(required_variables, tbl.table_name, where_clause)
         data = pandas.read_sql(query, lux.config.SQLconnection)
         view._vis_data = utils.pandas_to_lux(data)
-        # view._vis_data.length = list(length_query["length"])[0]
-
-        tbl._message.add_unique(
-            f"Large scatterplots detected: Lux is automatically binning scatterplots to heatmaps.",
-            priority=98,
-        )
 
     @staticmethod
     def execute_aggregate(view: Vis, tbl: LuxSQLTable, isFiltered=True):
diff --git a/lux/interestingness/interestingness.py b/lux/interestingness/interestingness.py
index 53725bc6..068fa61a 100644
--- a/lux/interestingness/interestingness.py
+++ b/lux/interestingness/interestingness.py
@@ -150,10 +150,9 @@ def interestingness(vis: Vis, ldf: LuxDataFrame) -> int:
                 color_cardinality = ldf.cardinality[color_column]
                 groupby_cardinality = ldf.cardinality[groupby_column]
                 # scale down score based on number of categories
-                chi2_score = chi2_contingency(contingency_tbl)[0] * 0.9 ** (
+                score = chi2_contingency(contingency_tbl)[0] * 0.9 ** (
                     color_cardinality + groupby_cardinality
                 )
-                score = min(0.01, chi2_score)
             except (ValueError, KeyError):
                 # ValueError results if an entire column of the contingency table is 0, can happen if an applied filter results in a category having no counts
                 score = -1
@@ -312,10 +311,7 @@ def unevenness(vis: Vis, ldf: LuxDataFrame, measure_lst: list, dimension_lst: li
     v_flat = pd.Series([1 / C] * len(v))
     if is_datetime(v):
         v = v.astype("int")
-    try:
-        return D * euclidean(v, v_flat)
-    except (ValueError):
-        return 0.01
+    return D * euclidean(v, v_flat)
 
 
 def mutual_information(v_x: list, v_y: list) -> int:
diff --git a/lux/processor/Compiler.py b/lux/processor/Compiler.py
index 2cb227b5..b0c36f35 100644
--- a/lux/processor/Compiler.py
+++ b/lux/processor/Compiler.py
@@ -366,6 +366,15 @@ def line_or_bar_or_geo(ldf, dimension: Clause, measure: Clause):
             for attr in relevant_attributes
             if attr != "Record" and attr in ldf._min_max
         )
+        # Replace scatterplot with heatmap
+        HBIN_START = 5000
+        if vis.mark == "scatter" and lux.config.heatmap and len(ldf) > HBIN_START:
+            vis._postbin = True
+            ldf._message.add_unique(
+                f"Large scatterplots detected: Lux is automatically binning scatterplots to heatmaps.",
+                priority=98,
+            )
+            vis._mark = "heatmap"
         vis._min_max = relevant_min_max
         if auto_channel != {}:
             vis = Compiler.enforce_specified_channel(vis, auto_channel)
diff --git a/lux/utils/date_utils.py b/lux/utils/date_utils.py
index 66086f1c..69d32c3c 100644
--- a/lux/utils/date_utils.py
+++ b/lux/utils/date_utils.py
@@ -83,7 +83,8 @@ def compute_date_granularity(date_column: pd.core.series.Series):
             A str specifying the granularity of dates for the inspected temporal column
     """
     # supporting a limited set of Vega-Lite TimeUnit (https://vega.github.io/vega-lite/docs/timeunit.html)
-    date_fields = ["day", "month", "year"]
+    # corresponding to Pandas timescales
+    date_fields = ["day", "month", "year", "dayofweek"]
     date_index = pd.DatetimeIndex(date_column)
     for field in date_fields:
         # can be changed to sum(getattr(date_index, field)) != 0
diff --git a/lux/vis/Clause.py b/lux/vis/Clause.py
index fcaf71d3..19a8d134 100644
--- a/lux/vis/Clause.py
+++ b/lux/vis/Clause.py
@@ -33,6 +33,7 @@ def __init__(
         bin_size: int = 0,
         weight: float = 1,
         sort: str = "",
+        timescale: str = "",
         exclude: typing.Union[str, list] = "",
     ):
         """
@@ -67,6 +68,9 @@ def __init__(
                 Number of bins for histograms, by default 0
         weight : float, optional
                 A number between 0 and 1 indicating the importance of this Clause, by default 1
+        timescale : str, optional
+                If data type is temporal, indicate whether temporal associated with timescale (if empty, then plot overall).
+                If timescale is present, the line chart axis is based on ordinal data type (non-date axis).
         sort : str, optional
                 Specifying whether and how the bar chart should be sorted
                 Possible values: 'ascending', 'descending', by default ""
@@ -86,6 +90,7 @@ def __init__(
         self.bin_size = bin_size
         self.weight = weight
         self.sort = sort
+        self.timescale = timescale
         self.exclude = exclude
 
     def get_attr(self):
diff --git a/lux/vis/Vis.py b/lux/vis/Vis.py
index c1c7dfbe..3fd5ccea 100644
--- a/lux/vis/Vis.py
+++ b/lux/vis/Vis.py
@@ -35,6 +35,8 @@ def __init__(self, intent, source=None, title="", score=0.0):
         self._postbin = None
         self.title = title
         self.score = score
+        self._all_column = False
+        self.approx = False
         self.refresh_source(self._source)
 
     def __repr__(self):
diff --git a/lux/vis/VisList.py b/lux/vis/VisList.py
index d1746f68..5a6b4617 100644
--- a/lux/vis/VisList.py
+++ b/lux/vis/VisList.py
@@ -303,6 +303,7 @@ def refresh_source(self, ldf):
             self._source = ldf
             self._source.maintain_metadata()
             if len(self._input_lst) > 0:
+                approx = False
                 if self._is_vis_input():
                     compiled_collection = []
                     for vis in self._collection:
@@ -315,4 +316,15 @@ def refresh_source(self, ldf):
                     self._inferred_intent = Parser.parse(self._intent)
                     Validator.validate_intent(self._inferred_intent, ldf)
                     self._collection = Compiler.compile_intent(ldf, self._inferred_intent)
-                lux.config.executor.execute(self._collection, ldf)
+
+                # Early pruning determination criteria
+                width_criteria = len(self._collection) > (lux.config.topk + 3)
+                length_criteria = len(ldf) > lux.config.early_pruning_sample_start
+                if lux.config.early_pruning and width_criteria and length_criteria:
+                    # print("Apply approx to this VisList")
+                    ldf._message.add_unique(
+                        "Large search space detected: Lux is approximating the interestingness of recommended visualizations.",
+                        priority=1,
+                    )
+                    approx = True
+                lux.config.executor.execute(self._collection, ldf, approx=approx)
diff --git a/lux/vislib/altair/AltairRenderer.py b/lux/vislib/altair/AltairRenderer.py
index 2c1ab206..2137e9e7 100644
--- a/lux/vislib/altair/AltairRenderer.py
+++ b/lux/vislib/altair/AltairRenderer.py
@@ -50,10 +50,13 @@ def create_vis(self, vis, standalone=True):
                 Output Altair Chart Object
         """
         # Lazy Evaluation for 2D Binning
-        if vis.mark == "scatter" and vis._postbin:
-            if lux.config.executor.name == "PandasExecutor":
+        if vis.approx:
+            if vis.mark == "scatter" and vis._postbin:
                 vis._mark = "heatmap"
                 lux.config.executor.execute_2D_binning(vis)
+            else:
+                # Exactly recompute the selected vis (e.g., top k) to display
+                lux.config.executor.execute([vis], vis._original_df, approx=False)
         # If a column has a Period dtype, or contains Period objects, convert it back to Datetime
         if vis.data is not None:
             for attr in list(vis.data.columns):
diff --git a/lux/vislib/altair/LineChart.py b/lux/vislib/altair/LineChart.py
index ae589030..78523616 100644
--- a/lux/vislib/altair/LineChart.py
+++ b/lux/vislib/altair/LineChart.py
@@ -43,6 +43,11 @@ def initialize_chart(self):
         x_attr_abv = str(x_attr.attribute)
         y_attr_abv = str(y_attr.attribute)
 
+        if x_attr.timescale != "":
+            x_attr.data_type = "ordinal"
+        if y_attr.timescale != "":
+            y_attr.data_type = "ordinal"
+
         if len(x_attr_abv) > 25:
             x_attr_abv = x_attr.attribute[:15] + "..." + x_attr.attribute[-10:]
         if len(y_attr_abv) > 25:
@@ -72,7 +77,7 @@ def initialize_chart(self):
                 axis=alt.Axis(title=y_attr_abv),
             )
             x_attr_field_code = f"alt.X('{x_attr.attribute}', type = '{x_attr.data_type}', axis=alt.Axis(title='{x_attr_abv}'))"
-            y_attr_fieldCode = f"alt.Y('{y_attr.attribute}', type= '{y_attr.data_type}', title='{agg_title}', axis=alt.Axis(title='{y_attr_abv}')"
+            y_attr_field_code = f"alt.Y('{y_attr.attribute}', type= '{y_attr.data_type}', title='{agg_title}', axis=alt.Axis(title='{y_attr_abv}'))"
         else:
             agg_title = get_agg_title(x_attr)
             x_attr_spec = alt.X(
@@ -84,14 +89,14 @@ def initialize_chart(self):
             y_attr_spec = alt.Y(
                 str(y_attr.attribute), type=y_attr.data_type, axis=alt.Axis(title=y_attr_abv)
             )
-            x_attr_field_code = f"alt.X('{x_attr.attribute}', type = '{x_attr.data_type}', title='{agg_title}', axis=alt.Axis(title='{x_attr_abv}')"
-            y_attr_fieldCode = f"alt.Y('{y_attr.attribute}', type= '{y_attr.data_type}', axis=alt.Axis(title='{y_attr_abv}')"
+            x_attr_field_code = f"alt.X('{x_attr.attribute}', type = '{x_attr.data_type}', title='{agg_title}', axis=alt.Axis(title='{x_attr_abv}'))"
+            y_attr_field_code = f"alt.Y('{y_attr.attribute}', type= '{y_attr.data_type}', axis=alt.Axis(title='{y_attr_abv}'))"
         self.data = AltairChart.sanitize_dataframe(self.data)
         chart = alt.Chart(self.data).mark_line().encode(x=x_attr_spec, y=y_attr_spec)
         chart = chart.interactive()  # Enable Zooming and Panning
         self.code += f"""
 		chart = alt.Chart(visData).mark_line().encode(
-		    y = {y_attr_fieldCode},
+		    y = {y_attr_field_code},
 		    x = {x_attr_field_code},
 		)
 		chart = chart.interactive() # Enable Zooming and Panning
diff --git a/lux/vislib/matplotlib/ScatterChart.py b/lux/vislib/matplotlib/ScatterChart.py
index 6829edc9..66dc8297 100644
--- a/lux/vislib/matplotlib/ScatterChart.py
+++ b/lux/vislib/matplotlib/ScatterChart.py
@@ -48,7 +48,7 @@ def initialize_chart(self):
         if len(y_attr.attribute) > 25:
             y_attr_abv = y_attr.attribute[:15] + "..." + y_attr.attribute[-10:]
 
-        df = self.data
+        df = self.data.dropna()
 
         x_pts = df[x_attr.attribute]
         y_pts = df[y_attr.attribute]
diff --git a/requirements-dev.txt b/requirements-dev.txt
index dd3b9615..660d5020 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -4,8 +4,8 @@ sphinx-automodapi>=0.13
 Sphinx>=3.0.2
 sphinx-rtd-theme>=0.4.3
 xlrd
-black
+black==20.8b1
 # Install to use SQLExecutor
 psycopg2>=2.8.5
 psycopg2-binary>=2.8.5
-lxml
\ No newline at end of file
+lxml


Test patch:
diff --git a/tests/test_action.py b/tests/test_action.py
index 73889715..b1d5ddbd 100644
--- a/tests/test_action.py
+++ b/tests/test_action.py
@@ -19,6 +19,56 @@
 from lux.vis.Vis import Vis
 
 
+def test_temporal_action(global_var):
+    airbnb_df = pd.read_csv(
+        "https://raw.githubusercontent.com/lux-org/lux-datasets/master/data/airbnb_nyc.csv"
+    )
+    flights_df = pd.read_csv(
+        "https://raw.githubusercontent.com/lux-org/lux-datasets/master/data/flights.csv"
+    )
+    date_df = pd.DataFrame(
+        {
+            "date": [
+                "2019-01",
+                "2014-02",
+                "2020-03",
+                "2013-04",
+                "2012-05",
+                "2019-01",
+                "2020-02",
+                "2013-03",
+                "2020-04",
+                "2000-05",
+                "2000-01",
+                "2000-02",
+                "2004-12",
+                "2004-06",
+                "2020-05",
+                "2020-01",
+                "2020-02",
+                "2020-03",
+                "2020-04",
+                "2020-05",
+            ]
+        }
+    )
+    test_data = [airbnb_df, flights_df, date_df, pytest.car_df, pytest.olympic]
+    test_data_vis_count = [4, 4, 2, 1, 1]
+    for entry in zip(test_data, test_data_vis_count):
+        df, num_vis = entry[0], entry[1]
+        df._repr_html_()
+        assert ("Temporal" in df.recommendation, "Temporal visualizations should be generated.")
+        recommended = df.recommendation["Temporal"]
+        assert (len(recommended) == num_vis, "Incorrect number of temporal visualizations generated.")
+        temporal_col = [c for c in df.columns if df.data_type[c] == "temporal"]
+        overall_vis = [
+            vis.get_attr_by_channel("x")[0].attribute
+            for vis in recommended
+            if vis.score == 4 or vis.score == 5
+        ]
+        assert temporal_col.sort() == overall_vis.sort()
+
+
 def test_vary_filter_val(global_var):
     lux.config.set_executor_type("Pandas")
     df = pytest.olympic
@@ -202,6 +252,7 @@ def test_year_filter_value(global_var):
 
 
 def test_similarity(global_var):
+    lux.config.early_pruning = False
     df = pytest.car_df
     df["Year"] = pd.to_datetime(df["Year"], format="%Y")
     df.set_intent(
@@ -229,6 +280,7 @@ def test_similarity(global_var):
     )[0]
     assert japan_vis.score > europe_vis.score
     df.clear_intent()
+    lux.config.early_pruning = True
 
 
 def test_similarity2():
diff --git a/tests/test_config.py b/tests/test_config.py
index 34e2de83..701b4c6e 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -221,14 +221,18 @@ def change_color_make_transparent_add_title(chart):
 
 
 def test_sampling_flag_config():
-    df = pd.read_csv("https://raw.githubusercontent.com/lux-org/lux-datasets/master/data/airbnb_nyc.csv")
-    df._ipython_display_()
-    assert df.recommendation["Correlation"][0].data.shape[0] == 30000
-    lux.config.sampling = False
-    df = df.copy()
-    df._ipython_display_()
-    assert df.recommendation["Correlation"][0].data.shape[0] == 48895
+    lux.config.heatmap = False
     lux.config.sampling = True
+    lux.config.early_pruning = False
+    import numpy as np
+
+    N = int(1.1 * lux.config.sampling_cap)
+    df = pd.DataFrame({"col1": np.random.rand(N), "col2": np.random.rand(N)})
+    df.maintain_recs()
+    assert len(df.recommendation["Correlation"][0].data) == lux.config.sampling_cap
+    lux.config.sampling = True
+    lux.config.heatmap = True
+    lux.config.early_pruning = True
 
 
 def test_sampling_parameters_config():
@@ -245,6 +249,7 @@ def test_sampling_parameters_config():
 
 
 def test_heatmap_flag_config():
+    lux.config.heatmap = True
     df = pd.read_csv("https://raw.githubusercontent.com/lux-org/lux-datasets/master/data/airbnb_nyc.csv")
     df._ipython_display_()
     assert df.recommendation["Correlation"][0]._postbin
diff --git a/tests/test_maintainence.py b/tests/test_maintainence.py
index 447fff6f..a158cd5e 100644
--- a/tests/test_maintainence.py
+++ b/tests/test_maintainence.py
@@ -81,3 +81,23 @@ def test_recs_inplace_operation(global_var):
     df._ipython_display_()
     assert len(df.recommendation["Occurrence"]) == 5
     assert df._recs_fresh == True, "Failed to maintain recommendation after display df"
+
+
+def test_intent_cleared_after_vis_data():
+    df = pd.read_csv(
+        "https://github.com/lux-org/lux-datasets/blob/master/data/real_estate_tutorial.csv?raw=true"
+    )
+    df["Month"] = pd.to_datetime(df["Month"], format="%m")
+    df["Year"] = pd.to_datetime(df["Year"], format="%Y")
+    df.intent = [
+        lux.Clause("Year"),
+        lux.Clause("PctForeclosured"),
+        lux.Clause("City=Crofton"),
+    ]
+    df._ipython_display_()
+
+    vis = df.recommendation["Similarity"][0]
+    vis.data._ipython_display_()
+    all_column_vis = vis.data.current_vis[0]
+    assert all_column_vis.get_attr_by_channel("x")[0].attribute == "Year"
+    assert all_column_vis.get_attr_by_channel("y")[0].attribute == "PctForeclosured"
diff --git a/tests/test_nan.py b/tests/test_nan.py
index 29efcb72..13ca88e9 100644
--- a/tests/test_nan.py
+++ b/tests/test_nan.py
@@ -134,9 +134,10 @@ def test_numeric_with_nan():
         len(a.recommendation["Distribution"]) == 2
     ), "Testing a numeric columns with NaN, check that histograms are displayed"
     assert "contains missing values" in a._message.to_html(), "Warning message for NaN displayed"
-    a = a.dropna()
-    a._ipython_display_()
-    assert (
-        len(a.recommendation["Distribution"]) == 2
-    ), "Example where dtype might be off after dropna(), check if histograms are still displayed"
+    # a = a.dropna()
+    # # TODO: Needs to be explicitly called, possible problem with metadata prpogation
+    # a._ipython_display_()
+    # assert (
+    #     len(a.recommendation["Distribution"]) == 2
+    # ), "Example where dtype might be off after dropna(), check if histograms are still displayed"
     assert "" in a._message.to_html(), "No warning message for NaN should be displayed"
diff --git a/tests/test_pandas_coverage.py b/tests/test_pandas_coverage.py
index fc8451c0..7e7cd8c4 100644
--- a/tests/test_pandas_coverage.py
+++ b/tests/test_pandas_coverage.py
@@ -405,7 +405,12 @@ def test_loc(global_var):
     assert len(new_df.cardinality) == 6
     new_df = df.loc[0:10, "Displacement":"Origin"]
     new_df._ipython_display_()
-    assert list(new_df.recommendation.keys()) == ["Correlation", "Distribution", "Occurrence"]
+    assert list(new_df.recommendation.keys()) == [
+        "Correlation",
+        "Distribution",
+        "Occurrence",
+        "Temporal",
+    ]
     assert len(new_df.cardinality) == 6
     new_df = df.loc[0:10, "Displacement":"Horsepower"]
     new_df._ipython_display_()
@@ -434,7 +439,12 @@ def test_iloc(global_var):
     assert len(new_df.cardinality) == 6
     new_df = df.iloc[0:11, 3:9]
     new_df._ipython_display_()
-    assert list(new_df.recommendation.keys()) == ["Correlation", "Distribution", "Occurrence"]
+    assert list(new_df.recommendation.keys()) == [
+        "Correlation",
+        "Distribution",
+        "Occurrence",
+        "Temporal",
+    ]
     assert len(new_df.cardinality) == 6
     new_df = df.iloc[0:11, 3:5]
     new_df._ipython_display_()
@@ -503,7 +513,7 @@ def compare_vis(vis1, vis2):
     assert len(vis1._inferred_intent) == len(vis2._inferred_intent)
     for j in range(len(vis1._inferred_intent)):
         compare_clauses(vis1._inferred_intent[j], vis2._inferred_intent[j])
-    assert vis1._source == vis2._source
+    compare_df(vis1._source, vis2._source)
     assert vis1._code == vis2._code
     assert vis1._mark == vis2._mark
     assert vis1._min_max == vis2._min_max
@@ -511,6 +521,11 @@ def compare_vis(vis1, vis2):
     assert vis1.score == vis2.score
 
 
+def compare_df(df1, df2):
+    if df1 is not None and df2 is not None:
+        assert df1.head().to_dict() == df2.head().to_dict()
+
+
 def test_index(global_var):
     # testing set_index and reset_index functions
     # setting a column as an index should remove it from the dataframe's column list
diff --git a/tests/test_performance.py b/tests/test_performance.py
index fca6a1c6..c268d69b 100644
--- a/tests/test_performance.py
+++ b/tests/test_performance.py
@@ -20,24 +20,43 @@
 
 # To run the script and see the printed result, run:
 # python -m pytest -s tests/test_performance.py
-def test_q1_performance_census(global_var):
+def test_lazy_maintain_performance_census(global_var):
+    lux.config.lazy_maintain = True
     df = pd.read_csv("https://github.com/lux-org/lux-datasets/blob/master/data/census.csv?raw=true")
     tic = time.perf_counter()
-    df._ipython_display_()
+    df.maintain_recs()
     toc = time.perf_counter()
     delta = toc - tic
-    df._ipython_display_()
+    df.maintain_recs()
     toc2 = time.perf_counter()
     delta2 = toc2 - toc
     print(f"1st display Performance: {delta:0.4f} seconds")
     print(f"2nd display Performance: {delta2:0.4f} seconds")
     assert (
-        delta < 4.5
+        delta < 4
     ), "The recommendations on Census dataset took a total of {delta:0.4f} seconds, longer than expected."
     assert (
-        delta2 < 0.15 < delta
+        delta2 < 0.1 < delta
     ), "Subsequent display of recommendations on Census dataset took a total of {delta2:0.4f} seconds, longer than expected."
 
+    lux.config.lazy_maintain = False
+    tic = time.perf_counter()
+    df.maintain_recs()
+    toc = time.perf_counter()
+    delta = toc - tic
+    df.maintain_recs()
+    toc2 = time.perf_counter()
+    delta2 = toc2 - toc
+    print(f"1st display Performance: {delta:0.4f} seconds")
+    print(f"2nd display Performance: {delta2:0.4f} seconds")
+
+    assert (
+        delta > 2
+    ), "The recompute of recommendations on Census dataset took a total of {delta:0.4f} seconds, shorter than expected."
+    assert (
+        delta2 > 2
+    ), "Subsequent recompute of recommendations on Census dataset took a total of {delta2:0.4f} seconds, shorter than expected."
+
     assert df.data_type == {
         "age": "quantitative",
         "workclass": "nominal",
@@ -55,3 +74,27 @@ def test_q1_performance_census(global_var):
         "native-country": "nominal",
         "income": "nominal",
     }
+
+
+def test_early_prune_performance_spotify():
+    df = pd.read_csv("https://github.com/lux-org/lux-datasets/blob/master/data/spotify.csv?raw=True")
+    df.maintain_metadata()
+    # With Early Pruning
+    lux.config.early_pruning = True
+    df.clear_intent()
+    start = time.time()
+    df.maintain_recs()
+    end = time.time()
+    with_prune_time = end - start
+    assert "Large search space detected" in df._message.to_html()
+    # Without Early Pruning
+    lux.config.early_pruning = False
+    df.clear_intent()
+    start = time.time()
+    df.maintain_recs()
+    end = time.time()
+    without_prune_time = end - start
+    assert "Large search space detected" not in df._message.to_html()
+    assert (
+        without_prune_time > with_prune_time
+    ), "Early pruning should speed up Spotify dataset recommendations"
diff --git a/tests/test_vis.py b/tests/test_vis.py
index 15c75017..8f49d292 100644
--- a/tests/test_vis.py
+++ b/tests/test_vis.py
@@ -547,3 +547,39 @@ def test_matplotlib_heatmap_flag_config():
     assert not df.recommendation["Correlation"][0]._postbin
     lux.config.heatmap = True
     lux.config.plotting_backend = "vegalite"
+
+
+def test_all_column_current_vis():
+    df = pd.read_csv(
+        "https://raw.githubusercontent.com/koldunovn/python_for_geosciences/master/DelhiTmax.txt",
+        delimiter=r"\s+",
+        parse_dates=[[0, 1, 2]],
+        header=None,
+    )
+    df.columns = ["Date", "Temp"]
+    df._ipython_display_()
+    assert df.current_vis != None
+
+
+def test_all_column_current_vis_filter():
+    df = pd.read_csv("https://raw.githubusercontent.com/lux-org/lux-datasets/master/data/car.csv")
+    df["Year"] = pd.to_datetime(df["Year"], format="%Y")
+    two_col_df = df[["Year", "Displacement"]]
+    two_col_df._ipython_display_()
+    assert two_col_df.current_vis != None
+    assert two_col_df.current_vis[0]._all_column
+    three_col_df = df[["Year", "Displacement", "Origin"]]
+    three_col_df._ipython_display_()
+    assert three_col_df.current_vis != None
+    assert three_col_df.current_vis[0]._all_column
+
+
+def test_intent_override_all_column():
+    df = pytest.car_df
+    df = df[["Year", "Displacement"]]
+    df.intent = ["Year"]
+    df._ipython_display_()
+    current_vis_code = df.current_vis[0].to_altair()
+    assert (
+        "y = alt.Y('Record', type= 'quantitative', title='Number of Records'" in current_vis_code
+    ), "All column not overriden by intent"